diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..f731f7194a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +# root = true + +# Unix-style newlines with a newline ending every file +# [*] +# indent_style = tab +# indent_size = 4 diff --git a/.gitignore b/.gitignore index a45279fb21..85bd82b7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -276,7 +276,9 @@ $RECYCLE.BIN/ *-symdir/ [Ee]xternal[Ff]iles/ /src/Plugins/*-sym +/src/Tests/*-sym /src/Presentation/SmartStore.Web/Themes/*-sym +/src/Presentation/SmartStore.Web/Themes/HP ########### @@ -296,10 +298,14 @@ Kopie von* /src/Presentation/SmartStore.Web/App_Data/_temp/ /src/Presentation/SmartStore.Web/App_Data/ExportProfiles/ /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 /src/Presentation/SmartStore.Web/Media +/src/**/App_Data/Tenants/ # Import/Export /src/Presentation/SmartStore.Web/Content/files/[Ee]xport[Ii]mport/*.* @@ -315,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 88b2d0a6be..5ea4c4a6fb 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -1,11 +1,8 @@ SmartStore.NET -Copyright 1999-2014 SmartStore AG +Copyright 1999-2018 SmartStore AG http://www.smartstore.com | https://github.com/smartstoreag/SmartStoreNET -SmartStore.NET is a fork of the ASP.NET open source e-commerce solution -nopCommerce (http://www.nopcommerce.com). - SmartStore.NET includes works distributed under the licenses listed below. Please refer to the specific resources for more detailed information about the authors, copyright notices, and licenses. @@ -14,7 +11,7 @@ copyright notices, and licenses. 51Degrees --------------------------------------------- WebSite: http://51degrees.mobi/ -Copyright: Copyright 2010 - 2013 51Degrees.mobi Limited +Copyright: Copyright 2010 - 2018 51Degrees.mobi Limited License: Mozilla Public License 2.0 (MPL-2.0) @@ -28,7 +25,7 @@ License: BSD License ASP.NET MVC --------------------------------------------- -Website: http://aspnet.codeplex.com/wikipage?title=MVC&referringTitle=Home +Website: https://github.com/aspnet/AspNetWebStack Copyright: Copyright (c) 2008-2011 Microsoft Corporation License: MICROSOFT ASP.NET MODEL VIEW CONTROLLER 3 EULA http://go.microsoft.com/fwlink/?LinkID=207621 @@ -36,26 +33,32 @@ License: MICROSOFT ASP.NET MODEL VIEW CONTROLLER 3 EULA Autofac --------------------------------------------- -Website: http://code.google.com/p/autofac/ -Copyright: Copyright (c) 2007-2014 Autofac Contributors - http://code.google.com/p/autofac/wiki/Contributing +Website: https://autofac.org/ +Copyright: Copyright (c) 2007-2018 Autofac Contributors License: MIT AutoMapper --------------------------------------------- WebSite: http://www.automapper.org -Copyright: Copyright 2008-2013 Jimmy Bogard and other contributors +Copyright: Copyright 2008-2018 Jimmy Bogard and other contributors License: MIT Bundle Transformer --------------------------------------------- -Website: https://bundletransformer.codeplex.com +Website: https://github.com/Taritsyn/BundleTransformer Copyright: Copyright Andrey Taritsyn 2014 License: Apache License 2.0 (Apache) +summernote +--------------------------------------------- +WebSite: https://summernote.org/ +Copyright: Copyright (c) 2015~ Summernote Team (https://github.com/orgs/summernote/people) +License: MIT + + DotNetOpenAuth --------------------------------------------- Website: http://dotnetopenauth.net/ @@ -65,34 +68,28 @@ License: MS-PL DotNetZip --------------------------------------------- -Website: http://dotnetzip.codeplex.com/ +Website: https://archive.codeplex.com/?p=dotnetzip Copyright: License: MS-PL Entity Framework --------------------------------------------- -Website: https://entityframework.codeplex.com -Copyright: Copyright (c) 2012-2014 Microsoft Corporation +Website: https://github.com/aspnet/EntityFramework6 +Copyright: Copyright (c) 2012-2018 Microsoft Corporation License: Apache License 2.0 (Apache) EPPlus --------------------------------------------- -Website: https://epplus.codeplex.com/ +Website: https://github.com/JanKallman/EPPlus Copyright: Copyright (C) 2011 Jan Kllman License: GNU Library General Public License (LGPL) -Fasterflect ---------------------------------------------- -Website: https://fasterflect.codeplex.com/ -License: Apache License 2.0 (Apache) - - Fluent Validation --------------------------------------------- -Website: https://fluentvalidation.codeplex.com/ +Website: https://github.com/JeremySkinner/FluentValidation License: Apache License 2.0 (Apache) @@ -103,11 +100,11 @@ Copyright: Copyright (C) 2014 Glimpse contributors (http://getglimpse.com/Co License: Apache License 2.0 (Apache) -ImageResizer.NET +ImageProcessor --------------------------------------------- -Website: https://imageresizing.net/ -Copyright: Copyright (c) 2012 Imazen -License: Freedom License +Website: http://imageprocessor.org/ +Copyright: Copyright (c) 2018 James Jackson-South +License: Apache License 2.0 (Apache) JavaScriptEngineSwitcher @@ -131,16 +128,9 @@ Copyright: Copyright (c) 2007-2009 Ariel Flesler License: MIT -jQuery UI (Core, Widget, Datepicker) ---------------------------------------------- -Website: http://docs.jquery.com/UI -Copyright: Copyright (c) 2010 lib/jquery.ui/AUTHORS.txt (http://jqueryui.com/about) -License: MIT - - -JSON.NET +Json.NET --------------------------------------------- -Website: http://james.newtonking.com/ +Website: https://www.newtonsoft.com/json Copyright: Copyright (c) 2007 James Newton-King License: MIT @@ -152,6 +142,13 @@ Copyright: Copyright (c) 2014 Maxmind License: Creative Commons Attribution-ShareAlike 3.0 Unported License +Linearicons by Perxis +--------------------------------------------- +Website: https://linearicons.com +Copyright: Copyright (c) 2015 Linearicons.com (https://perxis.com/) +License: Dual (CC BY-SA 4.0 and commercial) + + Microsoft SQL Server Compact 4.0 --------------------------------------------- Website: http://www.microsoft.com @@ -173,35 +170,49 @@ License: MIT NuGet --------------------------------------------- -Website: http://nuget.codeplex.com -Copyright: Copyright 2010-2011 Outercurve Foundation +Website: https://github.com/nuget/home +Copyright: Copyright 2010-2018 Outercurve Foundation License: Apache Software Foundation License 2.0 nUnit --------------------------------------------- -Website: http://www.nunit.org/index.php +Website: http://nunit.org/ Copyright: Copyright (c) 2002-2007 NUnit.org - Portions Copyright (c) 2002-2008 Charlie Poole or Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright (c) 2000-2002 Philip A. Craig + Portions Copyright (c) 2002-2018 Charlie Poole or Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov or Copyright (c) 2000-2002 Philip A. Craig License: Derived from zlib: http://nunit.org/index.php?p=license&r=2.4 +PhotoSwipe +--------------------------------------------- +Website: http://photoswipe.com/ +Copyright: Copyright (c) 2014-2018 Dmitry Semenov, http://dimsemenov.com +License: MIT + + +slick carousel +--------------------------------------------- +Website: http://kenwheeler.github.io/slick/ +Copyright: Copyright (c) 2018 Ken Wheeler +License: MIT + + Telerik MVC Extensions --------------------------------------------- Website: http://www.telerik.com/ License: GPL v3.0 -TinyMCE +Twitter Bootstrap --------------------------------------------- -WebSite: http://tinymce.moxiecode.com/ -Copyright: Copyright (c) 2003-2009 Moxiecode Systems AB -License: LGPL 2.1 +Website: https://getbootstrap.com/ +License: Apache License v2.0 -Twitter Bootstrap +ua_parser --------------------------------------------- -Website: http://twitter.github.io/bootstrap/ +Website: https://github.com/ua-parser/uap-csharp +Copyright: Copyright 2012 Sren Enemrke License: Apache License v2.0 diff --git a/README.md b/README.md index a7a83aee3a..f8e84aa9bb 100644 --- a/README.md +++ b/README.md @@ -1,23 +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 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 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. -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... totally FREE! +**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 delivers a beautiful and configurable shop front-end out-of-the-box**, built with a design approach on the highest level, including components like `Twitter Bootstrap`, `Less CSS` and others. The innovative ShopBar, a powerful layered navigation, reviews & ratings, an optimized checkout process... they all provide a perfect shopping experience for your buyers. +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. -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 Demoshop +

+
+ +**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) -* **Documentation:** [SmartStore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home) +* **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) + +

 

+ ## Highlights @@ -25,22 +62,25 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC * State of the art architecture thanks to `ASP.NET 4.5`, `ASP.NET MVC 5`, `Entity Framework 6` and Domain Driven Design * Easy to extend and extremely flexible thanks to modular design -* (NEW) A powerful theming engine lets you create themes & skins with minimum effort thanks to theme inheritance -* Consistent and sophisticated use of modern components such as `jQuery`, `Twitter Bootstrap`, `LESS` & more in the front and back end. -* Easy shop management thanks to modern and clean UI -* Out of the box shop design on the highest level -* Revolutionary Shopbar for the best shopping experience +* Highly scalable thanks to full page caching and web farm support +* A powerful theming engine lets you create themes & skins with minimum effort thanks to theme inheritance * Point&Click Theme configuration +* Highly professional search framework based on Lucene.NET, delivering ultra fast faceted search results +* Consistent and sophisticated use of modern components such as `jQuery`, `Bootstrap 4`, `Sass` & more in the front and back end. +* Easy shop management thanks to modern and clean UI ### 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 -* 100% Mobile optimized -* Perfectly optimized for search engines +* 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. * Trusted Shops precertification * 100% compliant with German jurisdiction * Sales-, Customer- & Inventory-management @@ -53,44 +93,47 @@ 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 V2.5.0 has been released on March 03, 2016. The highlights are: - * New import/export framework (profiles, filters, mapping, projections, scheduling, deployment... just everything!) - * TaskScheduler: Rewritten from scratch to be suitable for Web Farms (including support for cron expressions) - * Payment and shipping methods by customer roles - * Restrict payment methods to countries - * Restrict payment methods to shipping methods - * Email attachment support for message templates - * Attach order invoice PDF automatically to order notification emails - * Overall performance increase - * Lots of bug fixes +Smartstore.NET V3.2.0 has been released on 10 May, 2019. The highlights are: +* (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. +## 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. * [**Frontend**](http://frontend.smartstore.net/en) (User: demo, PWD: 1234) * [**Backend**](http://backend.smartstore.net/en/login) (User: demo, PWD: 1234) -##How to install + + +## How to install * Download the latest stable release from the download tab and unzip it to your web folder * Setup a website in IIS and point the file directory to your unzipped folder * Fire up your browser and follow the installation instructions * Enjoy ;-) -###System requirements +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 * IIS 7+ -* ASP.NET 4.5 -* ASP.NET MVC 5 +* ASP.NET 4.5+ * MS SQL Server 2008 Express (or higher) OR MS SQL Server Compact 4 +* Visual C++ Redistributable für Visual Studio 2015 ([Download](https://www.microsoft.com/en-US/download/details.aspx?id=52685)) * 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 f184eaa7eb..080c78d497 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -1,5 +1,5 @@ - + @@ -19,7 +19,6 @@ $(RootFolder)\buildtasks $(RootFolder)\artifacts $(LibFolder)\sqlce - $(LibFolder)\ClearScript.V8 $(ArtifactsFolder)\Source $(ArtifactsFolder)\MsDeploy $(ArtifactsFolder)\Gallery @@ -28,13 +27,9 @@ $(WebFolder)\Plugins $(WebFolder)\Themes - $(BuildFolder)\Compile - $(CompileFolder)\_PublishedWebsites $(BuildFolder)\Web $(BuildFolder)\MsDeploy $(BuildFolder)\Precompiled - $(BuildFolder)\Profiling - $(BuildFolder)\Gallery $(WebFolder)\Web.config $(WebFolder)\Web.$(Configuration).config @@ -44,11 +39,11 @@ x86 $(BUILD_NUMBER) - 2.6.0 + 3.2.2 $(StageFolder) .$(Version) - .Web + .Community @@ -95,17 +90,20 @@ - - - - - + + + + + + + + @@ -126,7 +124,8 @@ - + + @@ -137,13 +136,13 @@ Properties="WebProjectOutputDir=$(StageFolder)\Administration\; OutDir=$(StageFolder)\Administration\bin\;Configuration=$(Configuration)" /> - - - - + + + + - + @@ -153,17 +152,22 @@ - + - - - - + - + + + + + + + + + @@ -176,7 +180,19 @@ - + + + + + + + + $(StageFolder)\bin + + + + + diff --git a/SmartStoreNET.proj b/SmartStoreNET.proj index a16c5d597d..fc4c42fe07 100644 --- a/SmartStoreNET.proj +++ b/SmartStoreNET.proj @@ -1,5 +1,5 @@ - + 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/assets/smnet3-devices-sm.png b/assets/smnet3-devices-sm.png new file mode 100644 index 0000000000..2165a8ca6e Binary files /dev/null and b/assets/smnet3-devices-sm.png differ diff --git a/build.bat b/build.bat index b58175809a..94a6c72519 100644 --- a/build.bat +++ b/build.bat @@ -1,24 +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 + 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 ( - "%VS140COMNTOOLS%..\..\VC\vcvarsall.bat" - "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" - "%ProgramFiles%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" - - "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" - "%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" - "%ProgramFiles%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" - - "%VS110COMNTOOLS%..\..\VC\vcvarsall.bat" - "%ProgramFiles(x86)%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" - "%ProgramFiles%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" + "%VS150COMNTOOLS%\vsvars32.bat" + "%VS140COMNTOOLS%\vsvars32.bat" + "%VS120COMNTOOLS%\vsvars32.bat" + "%VS110COMNTOOLS%\vsvars32.bat" ) do ( if exist %%b ( - call %%b x86 - goto build + echo "Using MSBuild from %%b" + call %%b + set msbuild="msbuild" + goto build ) ) - + echo "Unable to detect suitable environment. Build may not succeed." :build -msbuild SmartStoreNET.proj /p:DebugSymbols=false /p:DebugType=None /P:SlnName=SmartStoreNET /maxcpucount %* \ No newline at end of file +cd /d %~dp0 + +echo "Restoring NuGet packages" +lib\nuget\nuget.exe restore "src\SmartStoreNET.sln" + +%msbuild% SmartStoreNET.proj /p:SlnName=SmartStoreNET /m /p:DebugSymbols=false /p:DebugType=None /maxcpucount %* + +:end diff --git a/changelog.md b/changelog.md index 0dac88a9fd..81357cca18 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,636 @@ -# 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 + +### Highlights +* **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 + * Added endpoint to complete an order + * #1364 Added endpoints for MeasureWeight and MeasureDimension +* Added options to include option names of specification and product attributes in the search index +* #441 added option to specify that additional shipping surcharges are considered only once. +* #1295 Sales tracking (tracking pixel) for Billiger.de +* XML and CSV export of shopping cart and wishlist items +* #1363 Make storing of IP addresses optional +* #729 Option for automatic order amount capturing when the shipping status changed to "shipped" +* (Dev) ILocalizationFileResolver: responsible for finding localization files for client scripts +* #998 GMC: Find a way to map attribute combination values to feed export values +* Added Instagram icon to social media icons in footer + +### Improvements +* Target .NET Framework changed: 4.5.2 > 4.6.1. +* Lower memory consumption +* #649 Media FileSystem provider: segmenting files in subfolders to increase IO perf with huge amount of files +* #1141 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 +* Moved RoundPricesDuringCalculation setting to currency entity +* #1100 Use new "Login with Amazon" services to initialize an Amazon payment +* #1285 Copy product: Add option to add more than one copy +* (Perf) Many improvements in hooking framework +* #1294 Swiss PostFinance: External payment page too small on mobile devices. Added setting for mobile device template URL, pre-configured with PostFinance template. +* #1143 Make shipping methods suitable for multi-stores +* #1320 Image import: Find out the content type of image URLs by response header rather than file extension (which is sometimes missing) +* #1219 Recently viewed products list should respect setting to hide manufacturer names +* Import and export product quantity step +* Add bundle item information to order messages +* #1031 Enable offline payment methods to have brand icons +* DevTools Plugin: Added example for cached output invalidation + +### Bugfixes +* #1268 Data importer always inserts new pictures and does not detect equal pictures while importing +* OutputCache computes ambiguous cache keys for blog pages +* #1142 Customer import creates role multiple times +* #1244 Variant query model binder cannot handle types text and datepicker +* #1273 Attribute formatter should consider setting CatalogSettings.ShowVariantCombinationPriceAdjustment +* Product entity picker should use the wildcard search to find products +* Hook framework should run hooks with `ImportantAttribute` when hooking was disabled per scope +* #1297 Web API: Parsing the timestamp may fail due to the different accuracy of the milliseconds +* Debitoor: VAT amount could be transmitted as miscellaneous for deliveries abroad. +* Prices with discounts limited to categories and customer groups were shown to all users in product lists +* #1330 MegaSearch: Missing variant facets if the variant value is not unique +* Back-in-stock subscription form was already submitted when opening the popup dialog +* Associated products of a grouped product were displayed in the wrong order +* Payment-Filter: Fixed "The cast to value type 'System.Decimal' failed because the materialized value is null" +* 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 +* #1232 Scheduled tasks are being 'randomly' set inactive +* #1245 Existing product does not get updated during import under certain circumstances +* #1207 No low stock activity when bulk editing the product quantity +* #1208 Missing inventory tab when switching from bundle to simple product +* #1210 Clear asset cache when a theme setting has changed +* Fixed name of deleted manufacturers displayed in product lists +* Fixed non-system category export profile always exports categories of current store +* Fixed picture assignment for variant attribute combinations of product copies which weren't assigned correctly +* #1218 CopyProductService does not copy picture of ProductVariantAttributeValue +* Fixed hidden checkout button when there are two or more shopping cart items and one gets removed from basket during checkout +* Fixed physical gift cards cannot be moved to cart +* Fixed tax rate wasn't properly formatted on product detail page +* #1228 Text entered in frontend for attributes or checkout attributes are truncated by comma +* Fixed shipping computation method ignoring deactivated PricesIncludeTax setting +* **Debitoor**: Fixed missing tax rates on Debitoor invoice for net prices +* #1224 Notifier wasn't working in plugin controllers +* #1205 Server cannot append header after +* +* headers have been sent +* #1154 Left offcanvas navigation does not open when in checkout progress +* #1212 Export: FTP publishing should consider directory structure +* #1253 Product PDF exporter only exports one picture and ignores the picture number profile setting +* Configuration of some plugins not saved in Firefox browser + +### Improvements +* Apply img-fluid class to all images within html editor content +* System name in privacy info topic URL should be lowercase +* If no checkout attribute is required checking out will be possible +* **OpenTrans**: optimizations for Lexware import +* #496 show bonus points in ordersummary on first checkout page +* Discounts of plugins weren't displayed in product lists + +### New Features +* #1076 Add support for percental tier price adjustments +* **Trusted Shops**: added review import +* (Dev) SSL redirection on localhost will be bypassed +* #783 Added option for "Email a friend" which disables the form field "Your email address" so it can't be changed + +## SmartStore.NET 3.0.2 +### Breaking changes +* MegaSearch: The index must be re-created because of changes to the price filter. + +### New Features +* New setting indicates whether to include or exclude unavailable products in search results by default. +* (Dev) New Asset Cache + +### Improvements +* PayPal Standard: New settings "UsePayPalAddress" and "IsShippingAddressRequired" to avoid payment rejection due to address validation. +* More widget zones for the footer +* #1177 Implement new setting for product search sorting +* More fault tolerant task scheduler + +### Bugfixes +* Fixed memory leak in MegaSearch plugin, which caused HTTP 503 +* RSS: Item URLs in feeds have ignored SSL settings +* Fixed "The view 'ProfileImportResult' or its master was not found" +* #1187 Search: Do not hide multi-selectable filters of the same group if there are no hits +* Fixed System.ArgumentNullException in ProductVariantQueryFactory +* PayPal PLUS: Fixed HTTP 401 "Unauthorized" when calling PatchShipping +* #1189 MegaSearch: Boosts are ignored in prefix and wildcard queries by default +* 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 +* +* 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 +* Fixed null reference exception when copying attribute option set with image(s) (file system storage only) +* Product price sometimes was wrong when entering a numeric value in an attribute text box +* Added missing code for customer privacy agreement +* Clicking the login link wasn't working correctly in offcanvas my-account menu +* #1158 Currency and language selectors weren't working in OffCanvas menu +* Fixed "Server cannot append header after HTTP headers have been sent" + +## SmartStore.NET 3.0.1 +### Improvements +* (Perf) Much faster application startup +* Microsoft Visual C++ 2015 Redistributable no longer required to be installed +* **BeezUP**: + * Exports up to 9 product images + * Export stock quantity when stock is managed by attributes + * Export parent child relation data when exporting attribute combinations as products + * Flatten and append attribute data when exporting attribute combinations as products +* Instant search should search manufacturer/brand name +* HTTP 301 redirects for legacy media URLs +* (Dev) New 'ApplicationStarted' event +* (Dev) Enabled C# 6 features in plugin views +* (GMC) Added missing database indexes. +* (WebApi) Added endpoints for blog post and blog comment +* Added more sortable columns to backend product grids +* #1160 Append suffix 'Picture (1...n)' to thumbnail's alt-attribute on product detail pages +* (Perf) Faster catalog indexing thanks to new database indexes +* (Perf) Faster dynamic view compilation thanks to *Roslyn* compiler +* Added the widget zone 'productdetails_pictures_bottom' +* Added config setting *sm:PdfEngineBaseUrl*. There are cases where the PDF converter exits with a network error, when it is unable to load automatically resolved URLs. +* (Dev) Added *Retry* utility class +* #1176 Admin > Product Search: It ain't possible to search for parts of a product name + +### Bugfixes +* #1145: Fixed HTTP 404 after switching language +* Fixed null reference exception in product lists if sorting is not allowed +* The file manager did not work correctly under a virtual folder +* Item row in list style product list should not wrap on small devices +* Item row in list style product list squashed on Android system browser +* Linq search: Filter for manufacturers and categories were not limited by store +* (GMC) Fixes duplicate occurrence of application path in absolute product URL +* Products with stock managed by attributes sometimes not displayed in category lists +* Fixed System.MissingMethodException for SmartStore.Core.Search.Facets.FacetGroup +* Fixed uploaded image paths in HTML fields (Media/Uploaded > Media//Uploaded) +* (MegaSearch) Product category order, product manufacturer order and product order were not recognized (sorting) +* Standard search: Fixed sorting of recently added products +* Alias for search filters couldn't be set if there's only one language +* #1168 Cart: MinOrderAmount sometimes ignored +* Moving products from editable wishlist to cart was out of function +* Fixed several issues with parallelly executed data export tasks +* Fixed several issues with *PayPal* payment providers +* Facebook login out of function due to Facebook API changes (always returns "Unknown error") +* Fixed 'no picture available' watermark on some product pictures +* #1153 Removing already applied gift card causes error +* (Theming) *Drift* zoom uses static zoomFactor. Made it dynamic. +* (Theming) IE11: simple menu dropdowns weren't positioned correctly +* Removed "trust" element from web.config (causes problems on some shared hosting spaces) +* Fixed ThumbZoomer unproportional rescale issue in product grid +* #1134 JavaScript error (IE 11 in VS debug mode) after removing the last item in shopping cart +* (Theming) Dozens of layout fs for ixemobile devices (especially iOS and Android native browser) +* Better error logging for TaskScheduler +* Fixed several queries which caused problems with SQL CE +* Fixed missing CurrencyCode exception in product exports +* #1179 Error while downloading a downloadable product +* Mega Menu: If a dropdown contains two level hierarchy categories only, they are not wrapped to the next column +* Hide sort dropdown when option is off +* HTTP 301 redirects for legacy media URLs +* Added missing resources of broken migration 'AddressEnhancement' again +* Checkout: Title and salutation were missing when entering new addresses +* #1163 ContentSlider: pictures won't be displayed when placed on top or bottom on IOS + +## SmartStore.NET 3.0 + +### Highlights +* **Flex**: New mobile-first responsive Theme based on Bootstrap 4 +* **Mega Search**: highly professional search framework based on Lucene.NET (commercial plugin exclusively bundled with Pro Edition) + * Ultra fast search results, even with millions of items + * Faceted search + * Synonyms + * Compound word splitting +* **Mega Menu**: highly customizable catalog menu widgets (commercial plugin exclusively bundled with Pro Edition) +* **Content Slider**: creates eye-catching content to boost sales (commercial plugin exclusively bundled with Pro Edition) +* **Output Cache** with "donut hole caching" for maximum speed and scalability (commercial plugin exclusively bundled with Premium Edition) +* **Microsoft AZURE** provider for media storage (commercial plugin exclusively bundled with Premium Edition) +* Web Farms: **REDIS** providers for Business Cache, Output Cache and Session State (commercial plugin exclusively bundled with Enterprise Edition) +* Product variant **option sets** +* New product specification attribute type: **numeric range** +* Image support for variant attributes + +### Breaking changes +* Removed old **Content Slider** module from open source core +* Removed **Web API** plugin from open source core (now exclusively bundled with Premium Edition) + + +### Other New Features +* Message Bus for inter-process messaging between servers (commercial plugin) +* Configurable media storage path for web farms +* (Dev) log4net integration +* (Dev) proper plugin view debugging +* (Dev) detect changes to static plugin files (css, js, etc.) and immediately apply them to running project +* (Dev) Theming: plugins now support implicitly imported SASS files (Content/[public|admin].scss) +* XML Sitemap optimized for very large catalogs: + * Partitions for very large sitemaps (> 50.000 nodes or > 10 MB) + * Generated in a background task. No instant invalidation anymore. +* Added option to skip shipping method selection in checkout process if only one shipping method is active +* Added options to capture salutation and title in addresses and customer info +* Added projection to control the export of individually visible associated products +* #966 Implement new tax calculation logic for shipping and payment fees (calculate with rate of highest cart amount) +* #922 New option to specify whether to include the weight of free shipping products in shipping by weight calculation +* #724 Allow discounts to be applied to manufacturers +* Option to display manufacturers sorted in alphabetical order +* #1093 New product properties required for selling abroad +* Web API: + * #1002 Add support for addresses and customer roles navigation property of customer entity + * #1062 Add filter options for user grid on configuration page + * #1072 Add support for TaxCategory + * #1073 Settings for maximum pagesize ($top) and maximum expansion depth ($expand) + * #1074 Extend product image upload to allow updating of images + * #1064 Deleting all product categories/manufacturers per product in one go + * #1063 Adding product category/manufacturer ignores any other property like DisplayOrder + * Added endpoint "Infos" for order and order item entity for additional information like aggregated data. + * Swagger integration +* Added setting to specify whether the product manufacturer should be displayed in product detail +* #271 Implemented support for attribute images/icons +* #330 Implement 'attribute option sets' +* Two themes Flex Black and Flex Blue which are derived from Flex theme + + +### Improvements +* Updated .NET Target Framework from 4.5.1 to 4.5.2 +* Added order message token for accepting third party email handover +* ECB currency exchange rate provider now cross calculates rates based on euro rates +* BeezUP: Exports the product weight +* Updated Elmar shop info XML from version 1.1 to 2.0 +* (Perf) Application start faster by ~20% +* (Perf) Lower memory consumption +* (Perf) #1098 Reduce number of created guest accounts (more reliable bot detection) +* #1008 Export: Add support for description projection to all product exporting providers +* #1015 Implement Entity Picker in discount requirements +* Debitoor improvements: + * Company name send as customer name if present + * Option to append customer email address to invoice address + * Additional order notes for tax exemption within and outside EU +* #1048 Add meta tags to contact us page +* Order XML export includes more customer data like title and salutation +* #1080 Do not resolve Order.OrderURLForCustomer token for guest customers +* #1051 Display EAN in PDF packaging slip +* #806 User couldn't be created in the admin section without "Manage Customer Roles" permission +* #491 Implemented option to determine a default delivery time +* #1078 Implemented options for "Add to Cart" quantity input field on product level. Quantity control can now be hidden and can be configured to be increased and decreased in steps. +* #696 Added textual resources for meta title and description for blog per month and by tag pages and enriched them with corresponding month plus year respectivly tag name +* #1025 Added token for customer number to MessageTokenProvider +* #1016 Implemented choosing of MessageTokens for newsletter campaigns with dropdown menu +* #1107 OpenTrans: Added option to exclude long description of products from export +* BMEcat: Implemented download of pictures according to MimeRoot-Info +* Payone: + * Replace client API by Payone iFrame solution. Allows credit card payment compliant with PCI DSS, SAQ A. + * #1123 Add order note with payment info for prepayment and invoice payment. + + +### Bugfixes +* Currency wasn't displayed for shipping estimation +* SKU, EAN, MPN of last attribute combination were exported for all combinations +* GMC: + * Id should be unique when exporting attribute combinations as products + * Attribute price adjustments were ignored when exporting attribute combinations as products + * Associated products that are not individually visible are not exported anymore. GMC rejects them because the frontend redirects to the grouped product. +* Export: + * #999 Projected customer id was ignored during price calculation + * #1104 Language projection was ignored when creating product details URL + * #1030: Orders didn't include data of attribute combinations + * Orders didn't include shipping address +* Awarded reward points for a placed order were occasionally calculated wrong +* PayPal PLUS: + * Changed shipping address/costs were not transmitted to PayPal + * Skip payment if cart total is zero + * Do not display payment wall if method is filtered +* PayPal Express: Fixed net price issue. +* Bundle item cannot be deleted if it's in a shopping cart +* Fixed SSL issue for news items in RSS feed +* Filter shows wrong number of products if "Include products from subcategories" is activated +* Guest user cannot be deleted if he has a private message +* #1029 Biz-Importer: Imports fixed tax rates as zero percentage +* #1055 Checkout: Localized shipping method names are not displayed if shipping by total is activated +* #1071 NewsLetterSubscription.ActivationUrl not working if the store if SSL secured +* #1086 Gift cards can be earned in one store and applied in another +* Whishlist: Products with minimum order amount greater then 1 can be added to the whishlist without any warning +* #1102: Discounts not transmitted when the total amount is zero +* #1101 Clickatell: Sending SMS not working anymore ## SmartStore.NET 2.6 @@ -130,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 @@ -750,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/ClearScript.V8/Native/amd64/msvcp110.dll b/lib/ClearScript.V8/Native/amd64/msvcp110.dll deleted file mode 100644 index 7e278956df..0000000000 Binary files a/lib/ClearScript.V8/Native/amd64/msvcp110.dll and /dev/null differ diff --git a/lib/ClearScript.V8/Native/amd64/msvcr110.dll b/lib/ClearScript.V8/Native/amd64/msvcr110.dll deleted file mode 100644 index dd484a5885..0000000000 Binary files a/lib/ClearScript.V8/Native/amd64/msvcr110.dll and /dev/null differ diff --git a/lib/ClearScript.V8/Native/x86/msvcp110.dll b/lib/ClearScript.V8/Native/x86/msvcp110.dll deleted file mode 100644 index 93fab5683b..0000000000 Binary files a/lib/ClearScript.V8/Native/x86/msvcp110.dll and /dev/null differ diff --git a/lib/ClearScript.V8/Native/x86/msvcr110.dll b/lib/ClearScript.V8/Native/x86/msvcr110.dll deleted file mode 100644 index 1ce960d750..0000000000 Binary files a/lib/ClearScript.V8/Native/x86/msvcr110.dll and /dev/null differ diff --git a/lib/ClearScript.V8/lib/ClearScriptV8-32.dll b/lib/ClearScript.V8/lib/ClearScriptV8-32.dll deleted file mode 100644 index c8c9a30b3f..0000000000 Binary files a/lib/ClearScript.V8/lib/ClearScriptV8-32.dll and /dev/null differ diff --git a/lib/ClearScript.V8/lib/ClearScriptV8-64.dll b/lib/ClearScript.V8/lib/ClearScriptV8-64.dll deleted file mode 100644 index 450033d364..0000000000 Binary files a/lib/ClearScript.V8/lib/ClearScriptV8-64.dll and /dev/null differ diff --git a/lib/ClearScript.V8/lib/v8-ia32.dll b/lib/ClearScript.V8/lib/v8-ia32.dll deleted file mode 100644 index fcc2956bcb..0000000000 Binary files a/lib/ClearScript.V8/lib/v8-ia32.dll and /dev/null differ diff --git a/lib/ClearScript.V8/lib/v8-x64.dll b/lib/ClearScript.V8/lib/v8-x64.dll deleted file mode 100644 index a1b9ca7c8e..0000000000 Binary files a/lib/ClearScript.V8/lib/v8-x64.dll and /dev/null differ diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll index 8acdf39c24..17bf4e2ad9 100644 Binary files a/lib/SmartStore.Licensing/SmartStore.Licensing.dll and b/lib/SmartStore.Licensing/SmartStore.Licensing.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-console-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-console-l1-1-0.dll new file mode 100644 index 0000000000..b469ccbd58 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-console-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-datetime-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-datetime-l1-1-0.dll new file mode 100644 index 0000000000..d0cdea4bb6 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-datetime-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-debug-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-debug-l1-1-0.dll new file mode 100644 index 0000000000..11283e3db3 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-debug-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-errorhandling-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-errorhandling-l1-1-0.dll new file mode 100644 index 0000000000..1069800f35 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-errorhandling-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-file-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-file-l1-1-0.dll new file mode 100644 index 0000000000..b608d0c7aa Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-file-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-file-l1-2-0.dll b/lib/msvcp140/x64/api-ms-win-core-file-l1-2-0.dll new file mode 100644 index 0000000000..620539cb6e Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-file-l1-2-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-file-l2-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-file-l2-1-0.dll new file mode 100644 index 0000000000..16f3d88c6b Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-file-l2-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-handle-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-handle-l1-1-0.dll new file mode 100644 index 0000000000..cbd019ace3 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-handle-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-heap-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-heap-l1-1-0.dll new file mode 100644 index 0000000000..963f042adc Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-heap-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-interlocked-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-interlocked-l1-1-0.dll new file mode 100644 index 0000000000..62d0d8ab33 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-interlocked-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-libraryloader-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-libraryloader-l1-1-0.dll new file mode 100644 index 0000000000..054fca5544 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-libraryloader-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-localization-l1-2-0.dll b/lib/msvcp140/x64/api-ms-win-core-localization-l1-2-0.dll new file mode 100644 index 0000000000..474775600f Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-localization-l1-2-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-memory-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-memory-l1-1-0.dll new file mode 100644 index 0000000000..41b3a5bbc0 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-memory-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-namedpipe-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-namedpipe-l1-1-0.dll new file mode 100644 index 0000000000..c89a74c7bb Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-namedpipe-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-processenvironment-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-processenvironment-l1-1-0.dll new file mode 100644 index 0000000000..a820084ebe Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-processenvironment-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-0.dll new file mode 100644 index 0000000000..debbd17a3c Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-1.dll b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-1.dll new file mode 100644 index 0000000000..f7a2395b80 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-processthreads-l1-1-1.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-profile-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-profile-l1-1-0.dll new file mode 100644 index 0000000000..99d2336dad Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-profile-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-rtlsupport-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-rtlsupport-l1-1-0.dll new file mode 100644 index 0000000000..2352c908c4 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-rtlsupport-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-string-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-string-l1-1-0.dll new file mode 100644 index 0000000000..2af9f458f9 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-string-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-synch-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-synch-l1-1-0.dll new file mode 100644 index 0000000000..86a893a946 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-synch-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-synch-l1-2-0.dll b/lib/msvcp140/x64/api-ms-win-core-synch-l1-2-0.dll new file mode 100644 index 0000000000..70d2f4b705 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-synch-l1-2-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-sysinfo-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-sysinfo-l1-1-0.dll new file mode 100644 index 0000000000..400883dd7d Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-sysinfo-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-timezone-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-timezone-l1-1-0.dll new file mode 100644 index 0000000000..3543bf6b86 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-timezone-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-core-util-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-core-util-l1-1-0.dll new file mode 100644 index 0000000000..3932409597 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-core-util-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-conio-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-conio-l1-1-0.dll new file mode 100644 index 0000000000..fee9507d8c Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-conio-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-convert-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-convert-l1-1-0.dll new file mode 100644 index 0000000000..e398c3b6da Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-convert-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-environment-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-environment-l1-1-0.dll new file mode 100644 index 0000000000..ee25a451bc Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-environment-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-filesystem-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-filesystem-l1-1-0.dll new file mode 100644 index 0000000000..d8e225f362 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-filesystem-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-heap-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-heap-l1-1-0.dll new file mode 100644 index 0000000000..f568d6d5b1 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-heap-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-locale-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-locale-l1-1-0.dll new file mode 100644 index 0000000000..d11e4ba9ab Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-locale-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-math-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-math-l1-1-0.dll new file mode 100644 index 0000000000..514e2d4530 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-math-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-multibyte-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-multibyte-l1-1-0.dll new file mode 100644 index 0000000000..1234fb96b8 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-multibyte-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-private-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-private-l1-1-0.dll new file mode 100644 index 0000000000..95c2067a08 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-private-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-process-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-process-l1-1-0.dll new file mode 100644 index 0000000000..abbbf7cabf Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-process-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-runtime-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-runtime-l1-1-0.dll new file mode 100644 index 0000000000..87f9144ab5 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-runtime-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-stdio-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-stdio-l1-1-0.dll new file mode 100644 index 0000000000..a03d17c160 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-stdio-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-string-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-string-l1-1-0.dll new file mode 100644 index 0000000000..19fff9dae1 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-string-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-time-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-time-l1-1-0.dll new file mode 100644 index 0000000000..faf52046b6 Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-time-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/api-ms-win-crt-utility-l1-1-0.dll b/lib/msvcp140/x64/api-ms-win-crt-utility-l1-1-0.dll new file mode 100644 index 0000000000..772b23e15e Binary files /dev/null and b/lib/msvcp140/x64/api-ms-win-crt-utility-l1-1-0.dll differ diff --git a/lib/msvcp140/x64/concrt140.dll b/lib/msvcp140/x64/concrt140.dll new file mode 100644 index 0000000000..915a13a3a1 Binary files /dev/null and b/lib/msvcp140/x64/concrt140.dll differ diff --git a/lib/msvcp140/x64/msvcp140.dll b/lib/msvcp140/x64/msvcp140.dll new file mode 100644 index 0000000000..4a5ca4f47a Binary files /dev/null and b/lib/msvcp140/x64/msvcp140.dll differ diff --git a/lib/msvcp140/x64/ucrtbase.dll b/lib/msvcp140/x64/ucrtbase.dll new file mode 100644 index 0000000000..4c0f926221 Binary files /dev/null and b/lib/msvcp140/x64/ucrtbase.dll differ diff --git a/lib/msvcp140/x64/vccorlib140.dll b/lib/msvcp140/x64/vccorlib140.dll new file mode 100644 index 0000000000..d3cc8e00f1 Binary files /dev/null and b/lib/msvcp140/x64/vccorlib140.dll differ diff --git a/lib/msvcp140/x64/vcruntime140.dll b/lib/msvcp140/x64/vcruntime140.dll new file mode 100644 index 0000000000..52fe06bd61 Binary files /dev/null and b/lib/msvcp140/x64/vcruntime140.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-console-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-console-l1-1-0.dll new file mode 100644 index 0000000000..eaedb39ccb Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-console-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-datetime-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-datetime-l1-1-0.dll new file mode 100644 index 0000000000..211c00fabf Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-datetime-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-debug-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-debug-l1-1-0.dll new file mode 100644 index 0000000000..204a23e2c9 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-debug-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-errorhandling-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-errorhandling-l1-1-0.dll new file mode 100644 index 0000000000..7312ab3827 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-errorhandling-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-file-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-file-l1-1-0.dll new file mode 100644 index 0000000000..a345dc6e71 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-file-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-file-l1-2-0.dll b/lib/msvcp140/x86/api-ms-win-core-file-l1-2-0.dll new file mode 100644 index 0000000000..8dfc671c82 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-file-l1-2-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-file-l2-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-file-l2-1-0.dll new file mode 100644 index 0000000000..5000625959 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-file-l2-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-handle-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-handle-l1-1-0.dll new file mode 100644 index 0000000000..8e4d1ac4a7 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-handle-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-heap-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-heap-l1-1-0.dll new file mode 100644 index 0000000000..2f8dba8767 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-heap-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-interlocked-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-interlocked-l1-1-0.dll new file mode 100644 index 0000000000..abf946796e Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-interlocked-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-libraryloader-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-libraryloader-l1-1-0.dll new file mode 100644 index 0000000000..ad39dbc73b Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-libraryloader-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-localization-l1-2-0.dll b/lib/msvcp140/x86/api-ms-win-core-localization-l1-2-0.dll new file mode 100644 index 0000000000..df914950ca Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-localization-l1-2-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-memory-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-memory-l1-1-0.dll new file mode 100644 index 0000000000..7f6cd01ce6 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-memory-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-namedpipe-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-namedpipe-l1-1-0.dll new file mode 100644 index 0000000000..d1d0e7771d Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-namedpipe-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-processenvironment-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-processenvironment-l1-1-0.dll new file mode 100644 index 0000000000..88a0809e05 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-processenvironment-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-0.dll new file mode 100644 index 0000000000..99dbfe6024 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-1.dll b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-1.dll new file mode 100644 index 0000000000..2c8315bdeb Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-processthreads-l1-1-1.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-profile-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-profile-l1-1-0.dll new file mode 100644 index 0000000000..fbdc2fa98a Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-profile-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-rtlsupport-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-rtlsupport-l1-1-0.dll new file mode 100644 index 0000000000..5538941b0c Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-rtlsupport-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-string-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-string-l1-1-0.dll new file mode 100644 index 0000000000..ef94ad4d27 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-string-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-synch-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-synch-l1-1-0.dll new file mode 100644 index 0000000000..f3abf477c5 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-synch-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-synch-l1-2-0.dll b/lib/msvcp140/x86/api-ms-win-core-synch-l1-2-0.dll new file mode 100644 index 0000000000..239ee9f913 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-synch-l1-2-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-sysinfo-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-sysinfo-l1-1-0.dll new file mode 100644 index 0000000000..7b4e1c9858 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-sysinfo-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-timezone-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-timezone-l1-1-0.dll new file mode 100644 index 0000000000..feb24fc43f Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-timezone-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-core-util-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-core-util-l1-1-0.dll new file mode 100644 index 0000000000..fedd4038bd Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-core-util-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-conio-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-conio-l1-1-0.dll new file mode 100644 index 0000000000..08f68be2c2 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-conio-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-convert-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-convert-l1-1-0.dll new file mode 100644 index 0000000000..5b4e62cc1d Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-convert-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-environment-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-environment-l1-1-0.dll new file mode 100644 index 0000000000..8009912352 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-environment-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-filesystem-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-filesystem-l1-1-0.dll new file mode 100644 index 0000000000..a4414560b5 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-filesystem-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-heap-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-heap-l1-1-0.dll new file mode 100644 index 0000000000..2584aad585 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-heap-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-locale-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-locale-l1-1-0.dll new file mode 100644 index 0000000000..9dcda52733 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-locale-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-math-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-math-l1-1-0.dll new file mode 100644 index 0000000000..d916a4df87 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-math-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-multibyte-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-multibyte-l1-1-0.dll new file mode 100644 index 0000000000..a6f53c2a62 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-multibyte-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-private-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-private-l1-1-0.dll new file mode 100644 index 0000000000..7337a284a4 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-private-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-process-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-process-l1-1-0.dll new file mode 100644 index 0000000000..e949c9aa8d Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-process-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-runtime-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-runtime-l1-1-0.dll new file mode 100644 index 0000000000..a0930f4199 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-runtime-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-stdio-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-stdio-l1-1-0.dll new file mode 100644 index 0000000000..7a47fa3aa1 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-stdio-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-string-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-string-l1-1-0.dll new file mode 100644 index 0000000000..a00fd9eee2 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-string-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-time-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-time-l1-1-0.dll new file mode 100644 index 0000000000..4b3c00b6d2 Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-time-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/api-ms-win-crt-utility-l1-1-0.dll b/lib/msvcp140/x86/api-ms-win-crt-utility-l1-1-0.dll new file mode 100644 index 0000000000..b285e9cafc Binary files /dev/null and b/lib/msvcp140/x86/api-ms-win-crt-utility-l1-1-0.dll differ diff --git a/lib/msvcp140/x86/concrt140.dll b/lib/msvcp140/x86/concrt140.dll new file mode 100644 index 0000000000..4f9ed11ade Binary files /dev/null and b/lib/msvcp140/x86/concrt140.dll differ diff --git a/lib/msvcp140/x86/msvcp140.dll b/lib/msvcp140/x86/msvcp140.dll new file mode 100644 index 0000000000..8501b34b3c Binary files /dev/null and b/lib/msvcp140/x86/msvcp140.dll differ diff --git a/lib/msvcp140/x86/ucrtbase.dll b/lib/msvcp140/x86/ucrtbase.dll new file mode 100644 index 0000000000..92c99d0cab Binary files /dev/null and b/lib/msvcp140/x86/ucrtbase.dll differ diff --git a/lib/msvcp140/x86/vccorlib140.dll b/lib/msvcp140/x86/vccorlib140.dll new file mode 100644 index 0000000000..48f5789c67 Binary files /dev/null and b/lib/msvcp140/x86/vccorlib140.dll differ diff --git a/lib/msvcp140/x86/vcruntime140.dll b/lib/msvcp140/x86/vcruntime140.dll new file mode 100644 index 0000000000..21c18f7e82 Binary files /dev/null and b/lib/msvcp140/x86/vcruntime140.dll differ diff --git a/lib/nuget/nuget.exe b/lib/nuget/nuget.exe new file mode 100644 index 0000000000..9f8781de0d Binary files /dev/null and b/lib/nuget/nuget.exe differ diff --git a/lib/sqlce/amd64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest b/lib/sqlce/x64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest similarity index 100% rename from lib/sqlce/amd64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest rename to lib/sqlce/x64/Microsoft.VC90.CRT/Microsoft.VC90.CRT.manifest diff --git a/lib/sqlce/amd64/Microsoft.VC90.CRT/README_ENU.txt b/lib/sqlce/x64/Microsoft.VC90.CRT/README_ENU.txt similarity index 100% rename from lib/sqlce/amd64/Microsoft.VC90.CRT/README_ENU.txt rename to lib/sqlce/x64/Microsoft.VC90.CRT/README_ENU.txt diff --git a/lib/sqlce/amd64/Microsoft.VC90.CRT/msvcr90.dll b/lib/sqlce/x64/Microsoft.VC90.CRT/msvcr90.dll similarity index 100% rename from lib/sqlce/amd64/Microsoft.VC90.CRT/msvcr90.dll rename to lib/sqlce/x64/Microsoft.VC90.CRT/msvcr90.dll diff --git a/lib/sqlce/amd64/sqlceca40.dll b/lib/sqlce/x64/sqlceca40.dll similarity index 100% rename from lib/sqlce/amd64/sqlceca40.dll rename to lib/sqlce/x64/sqlceca40.dll diff --git a/lib/sqlce/amd64/sqlcecompact40.dll b/lib/sqlce/x64/sqlcecompact40.dll similarity index 100% rename from lib/sqlce/amd64/sqlcecompact40.dll rename to lib/sqlce/x64/sqlcecompact40.dll diff --git a/lib/sqlce/amd64/sqlceer40EN.dll b/lib/sqlce/x64/sqlceer40EN.dll similarity index 100% rename from lib/sqlce/amd64/sqlceer40EN.dll rename to lib/sqlce/x64/sqlceer40EN.dll diff --git a/lib/sqlce/amd64/sqlceme40.dll b/lib/sqlce/x64/sqlceme40.dll similarity index 100% rename from lib/sqlce/amd64/sqlceme40.dll rename to lib/sqlce/x64/sqlceme40.dll diff --git a/lib/sqlce/amd64/sqlceqp40.dll b/lib/sqlce/x64/sqlceqp40.dll similarity index 100% rename from lib/sqlce/amd64/sqlceqp40.dll rename to lib/sqlce/x64/sqlceqp40.dll diff --git a/lib/sqlce/amd64/sqlcese40.dll b/lib/sqlce/x64/sqlcese40.dll similarity index 100% rename from lib/sqlce/amd64/sqlcese40.dll rename to lib/sqlce/x64/sqlcese40.dll diff --git a/lib/vswhere/vswhere.exe b/lib/vswhere/vswhere.exe new file mode 100644 index 0000000000..3d91a17dc4 Binary files /dev/null and b/lib/vswhere/vswhere.exe 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 f74f0edf78..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("2.6.0.0")] +[assembly: AssemblyVersion("3.2.2.0")] -[assembly: AssemblyFileVersion("2.6.0.0")] -[assembly: AssemblyInformationalVersion("2.6.0.0")] +[assembly: AssemblyFileVersion("3.2.2.0")] +[assembly: AssemblyInformationalVersion("3.2.2.0")] diff --git a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs index b02ea8dd58..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,114 +9,166 @@ namespace SmartStore.Core.Async { + using EventQueue = ConcurrentQueue>; - public static class AsyncRunner + 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.ArgumentNotNull(() => action); - Guard.ArgumentNotNull(() => scheduler); + Guard.NotNull(action, nameof(action)); 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; @@ -123,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.ArgumentNotNull(() => state); - Guard.ArgumentNotNull(() => action); - Guard.ArgumentNotNull(() => scheduler); + Guard.NotNull(state, nameof(state)); + Guard.NotNull(action, nameof(action)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew((o) => { @@ -146,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) + public static Task Run( + Func function, + CancellationToken cancellationToken = default(CancellationToken), + TaskCreationOptions options = TaskCreationOptions.LongRunning, + TaskScheduler scheduler = null) { - 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) - { - Guard.ArgumentNotNull(() => function); - Guard.ArgumentNotNull(() => scheduler); + Guard.NotNull(function, nameof(function)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew(() => { @@ -195,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.ArgumentNotNull(() => state); - Guard.ArgumentNotNull(() => function); - Guard.ArgumentNotNull(() => scheduler); + Guard.NotNull(state, nameof(state)); + Guard.NotNull(function, nameof(function)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew((o) => { @@ -218,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; } @@ -241,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(); } @@ -257,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(); - } } } @@ -292,7 +358,7 @@ internal class BackgroundWorkHost : IRegisteredObject { private readonly CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource(); private int _numRunningWorkItems; - + public BackgroundWorkHost() { HostingEnvironment.RegisterObject(this); @@ -338,7 +404,7 @@ public void Register(Task work, CancellationToken cancellationToken) } _numRunningWorkItems++; } - + work.ContinueWith( WorkItemComplete, CancellationToken.None, @@ -354,7 +420,7 @@ private void WorkItemComplete(Task work) lock (this) { num = --_numRunningWorkItems; - isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested; + isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested; } if (num == 0 && isCancellationRequested) { @@ -368,5 +434,4 @@ private void FinalShutdown() } } - } diff --git a/src/Libraries/SmartStore.Core/Async/AsyncState.cs b/src/Libraries/SmartStore.Core/Async/AsyncState.cs deleted file mode 100644 index ce99542612..0000000000 --- a/src/Libraries/SmartStore.Core/Async/AsyncState.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Caching; -using System.Threading; - -namespace SmartStore.Core.Async -{ - - public class AsyncState - { - private static readonly AsyncState s_instance = new AsyncState(); - private readonly MemoryCache _cache = new MemoryCache("SmartStore.AsyncState"); - - private AsyncState() - { - } - - public static AsyncState Current - { - get { return s_instance; } - } - - public bool Exists(string name = null) - { - var key = BuildKey(name); - return _cache.Contains(key) && !object.Equals(((StateInfo)_cache[key]).Progress, default(T)); - } - - public T Get(string name = null) - { - CancellationTokenSource cancelTokenSource; - return Get(out cancelTokenSource, name); - } - - public IEnumerable GetAll() - { - var keyPrefix = BuildKey(null); - foreach (var kvp in _cache) - { - if (kvp.Key.StartsWith(keyPrefix)) - { - var value = kvp.Value as StateInfo; - if (value != null && value.Progress != null) - { - yield return (T)value.Progress; - } - } - } - } - - public CancellationTokenSource GetCancelTokenSource(string name = null) - { - CancellationTokenSource cancelTokenSource; - Get(out cancelTokenSource, name); - return cancelTokenSource; - } - - private T Get(out CancellationTokenSource cancelTokenSource, string name = null) - { - cancelTokenSource = null; - var key = BuildKey(name); - - var value = _cache.Get(key) as StateInfo; - - if (value != null) - { - cancelTokenSource = value.CancellationTokenSource; - return (T)value.Progress; - } - - return default(T); - } - - public void Set(T state, string name = null, bool neverExpires = false) - { - Guard.ArgumentNotNull(() => state); - Set(state, null, name, neverExpires); - } - - public void Update(Action update, string name = null) - { - Guard.ArgumentNotNull(() => update); - - var key = BuildKey(typeof(T), name); - - var value = _cache.Get(key) as StateInfo; - if (value != null) - { - var state = (T)value.Progress; - if (state != null) - { - update(state); - } - } - } - - public void SetCancelTokenSource(CancellationTokenSource cancelTokenSource, string name = null) - { - Guard.ArgumentNotNull(() => cancelTokenSource); - - Set(default(T), cancelTokenSource, name); - } - - private void Set(T state, CancellationTokenSource cancelTokenSource, string name = null, bool neverExpires = false) - { - var key = BuildKey(typeof(T), name); - - var value = _cache.Get(key) as StateInfo; - - if (value != null) - { - // exists already, so update - if (state != null) - { - value.Progress = state; - } - if (cancelTokenSource != null && value.CancellationTokenSource == null) - { - value.CancellationTokenSource = cancelTokenSource; - } - } - - var policy = new CacheItemPolicy { SlidingExpiration = neverExpires ? TimeSpan.Zero : TimeSpan.FromMinutes(15) }; - - _cache.Set( - key, - value ?? new StateInfo { Progress = state, CancellationTokenSource = cancelTokenSource }, - policy); - } - - public bool Remove(string name = null) - { - var key = BuildKey(name); - var value = _cache.Remove(key) as StateInfo; - - if (value != null) - { - if (value.CancellationTokenSource != null) - { - value.CancellationTokenSource.Dispose(); - } - return true; - } - - return false; - } - - private string BuildKey(string name) - { - return BuildKey(typeof(T), name); - } - - private string BuildKey(Type type, string name) - { - return "{0}:{1}:{2}".FormatInvariant("AsyncStateKey", type.FullName, name.EmptyNull()); - } - - class StateInfo - { - public object Progress - { - get; - set; - } - - public CancellationTokenSource CancellationTokenSource - { - get; - set; - } - } - } - -} diff --git a/src/Libraries/SmartStore.Core/Async/AsyncStateInfo.cs b/src/Libraries/SmartStore.Core/Async/AsyncStateInfo.cs new file mode 100644 index 0000000000..8bc3898736 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Async/AsyncStateInfo.cs @@ -0,0 +1,35 @@ +using System; + +namespace SmartStore.Core.Async +{ + [Serializable] + public class AsyncStateInfo + { + // used for serialization compatibility + public static readonly string Version = "1"; + + public object Progress + { + get; + set; + } + + public DateTime CreatedOnUtc + { + get; + set; + } + + public DateTime LastAccessUtc + { + get; + set; + } + + public TimeSpan Duration + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Async/IAsyncState.cs b/src/Libraries/SmartStore.Core/Async/IAsyncState.cs new file mode 100644 index 0000000000..bbfa0d3266 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Async/IAsyncState.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SmartStore.Core.Async +{ + public interface IAsyncState + { + bool Exists(string name = null); + + T Get(string name = null); + IEnumerable GetAll(); + + void Set(T state, string name = null, bool neverExpires = false); + void Update(Action update, string name = null); + bool Remove(string name = null); + + CancellationTokenSource GetCancelTokenSource(string name = null); + void SetCancelTokenSource(CancellationTokenSource cancelTokenSource, string name = null); + bool Cancel(string name = null); + } +} diff --git a/src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs b/src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs new file mode 100644 index 0000000000..a8fa0ceb0f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Async/LocalAsyncState.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Caching; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SmartStore.Core.Async +{ + public partial class LocalAsyncState : IAsyncState + { + private readonly MemoryCache _states = new MemoryCache("SmartStore.AsyncState.Progress"); + private readonly MemoryCache _cancelTokens = new MemoryCache("SmartStore.AsyncState.CancelTokenSources"); + + public virtual bool Exists(string name = null) + { + var value = GetStateInfo(name); + return value != null && !object.Equals(value.Progress, default(T)); + } + + public virtual T Get(string name = null) + { + var value = GetStateInfo(name); + + if (value != null) + { + return (T)value.Progress; + } + + return default(T); + } + + public virtual IEnumerable GetAll() + { + var keyPrefix = BuildKey(null); + return _states + .Where(x => x.Key.StartsWith(keyPrefix)) + .Select(x => x.Value) + .OfType() + .Select(x => x.Progress) + .OfType(); + } + + + public virtual void Set(T state, string name = null, bool neverExpires = false) + { + Guard.NotNull(state, nameof(state)); + + var value = GetStateInfo(name); + + if (value != null) + { + // exists already, so update + if (state != null) + { + value.Progress = state; + } + } + else + { + // add new entry + var duration = neverExpires ? TimeSpan.Zero : TimeSpan.FromMinutes(15); + var policy = new CacheItemPolicy { SlidingExpiration = duration }; + var key = BuildKey(name); + + // On expiration or removal: remove corresponding cancel token also. + policy.RemovedCallback = (x) => OnRemoveCancelTokenSource(key); + + _states.Set(key, new AsyncStateInfo { Progress = state, Duration = duration }, policy); + } + } + + public virtual void Update(Action update, string name = null) + { + Guard.NotNull(update, nameof(update)); + + var value = GetStateInfo(name); + + if (value != null) + { + var state = (T)value.Progress; + if (state != null) + { + update(state); + } + } + } + + public virtual bool Remove(string name = null) + { + var key = BuildKey(name); + + if (OnRemoveStateInfo(key)) + { + OnRemoveCancelTokenSource(key); + return true; + } + + return false; + } + + protected virtual bool OnRemoveStateInfo(string key) + { + return _states.Remove(key) != null; + } + + protected virtual bool OnRemoveCancelTokenSource(string key, bool successive = false) + { + Guard.NotEmpty(key, nameof(key)); + + var token = _cancelTokens.Remove(key) as CancellationTokenSource; + + if (token != null) + { + token.Dispose(); + return true; + } + + return false; + } + + + public CancellationTokenSource GetCancelTokenSource(string name = null) + { + return OnGetCancelTokenSource(BuildKey(name)); + } + + protected virtual CancellationTokenSource OnGetCancelTokenSource(string key, bool successive = false) + { + Guard.NotEmpty(key, nameof(key)); + + var token = _cancelTokens.Get(key); + + if (token != null) + { + return (CancellationTokenSource)token; + } + + return null; + } + + public virtual void SetCancelTokenSource(CancellationTokenSource cancelTokenSource, string name = null) + { + Guard.NotNull(cancelTokenSource, nameof(cancelTokenSource)); + + var key = BuildKey(name); + + if (Exists(name)) + { + OnRemoveCancelTokenSource(key); + } + + var policy = new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(15) }; + + _cancelTokens.Set(key, cancelTokenSource, policy); + } + + public bool Cancel(string name = null) + { + return OnCancel(BuildKey(name)); + } + + protected virtual bool OnCancel(string key, bool successive = false) + { + Guard.NotEmpty(key, nameof(key)); + + var cts = OnGetCancelTokenSource(key); + + if (cts != null) + { + cts.Cancel(); + return true; + } + + return false; + } + + + protected virtual AsyncStateInfo GetStateInfo(string name = null) + { + var key = BuildKey(name); + var value = _states.Get(key) as AsyncStateInfo; + + if (value != null) + { + // ensures that sliding expiration gets updated on cancel token + _cancelTokens.Get(key); + } + + return value; + } + + protected string BuildKey(string name) + { + return BuildKey(typeof(T), name); + } + + protected virtual string BuildKey(Type type, string name) + { + return "{0}{1}".FormatInvariant(type.FullName, name.HasValue() ? ":" + name : ""); + } + } +} diff --git a/src/Libraries/SmartStore.Core/BaseEntity.cs b/src/Libraries/SmartStore.Core/BaseEntity.cs index 0c90dc9a9a..3fd0016909 100644 --- a/src/Libraries/SmartStore.Core/BaseEntity.cs +++ b/src/Libraries/SmartStore.Core/BaseEntity.cs @@ -1,18 +1,18 @@ 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 /// [DataContract] - public abstract partial class BaseEntity + public abstract partial class BaseEntity : IEquatable { /// /// Gets or sets the entity identifier @@ -21,20 +21,30 @@ public abstract partial class BaseEntity [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; - } - return t; - } + #region Old + //var t = GetType(); + //if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity.")) + //{ + // // it's a proxied type + // t = t.BaseType; + //} + + //return t; + #endregion + + return ObjectContext.GetObjectType(GetType()); + } /// - /// Transient objects are not associated with an item already in storage. For instance, + /// Transient objects are not associated with an item already in storage. For instance, /// a Product entity is transient if its Id is 0. /// public virtual bool IsTransientRecord() @@ -44,28 +54,33 @@ public virtual bool IsTransientRecord() public override bool Equals(object obj) { - return Equals(obj as BaseEntity); + return this.Equals(obj as BaseEntity); } - protected virtual bool Equals(BaseEntity other) - { - if (other == null) - return false; + bool IEquatable.Equals(BaseEntity other) + { + return this.Equals(other); + } - if (ReferenceEquals(this, other)) - return true; + protected virtual bool Equals(BaseEntity other) + { + if (other == null) + return false; - if (HasSameNonDefaultIds(other)) - { - var otherType = other.GetUnproxiedType(); - var thisType = GetUnproxiedType(); - return thisType.Equals(otherType); - } + if (ReferenceEquals(this, other)) + return true; - return false; - } + if (HasSameNonDefaultIds(other)) + { + var otherType = other.GetUnproxiedType(); + var thisType = GetUnproxiedType(); + return thisType.Equals(otherType); + } + + return false; + } - [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] public override int GetHashCode() { if (IsTransientRecord()) @@ -99,5 +114,5 @@ private bool HasSameNonDefaultIds(BaseEntity other) { return !this.IsTransientRecord() && !other.IsTransientRecord() && this.Id == other.Id; } - } + } } 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/DisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/DisplayControl.cs deleted file mode 100644 index df06bd390a..0000000000 --- a/src/Libraries/SmartStore.Core/Caching/DisplayControl.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Caching -{ - public partial class DisplayControl : IDisplayControl - { - private readonly HashSet _entities = new HashSet(); - - private bool? _isUncacheableRequest; - - public void Announce(BaseEntity entity) - { - if (entity != null) - { - _entities.Add(entity); - } - } - - public bool IsDisplayed(BaseEntity entity) - { - if (entity == null) - return false; - - return _entities.Contains(entity); - } - - public void MarkRequestAsUncacheable() - { - // First wins: subsequent calls should not be able to cancel this - _isUncacheableRequest = true; - } - - public bool IsUncacheableRequest - { - get - { - return _isUncacheableRequest.GetValueOrDefault() == true; - } - } - - public string GetCacheControlTagFor(BaseEntity entity) - { - Guard.ArgumentNotNull(() => entity); - - var typeName = entity.GetUnproxiedType().Name.ToLowerInvariant(); - string prefix = null; - - switch (typeName) - { - case "product": - prefix = "p"; - break; - case "category": - prefix = "c"; - break; - case "picture": - prefix = "pic"; - break; - } - - if (prefix != null) - { - return prefix + entity.Id.ToString(); - } - - return null; - } - - public IEnumerable GetCacheControlTags() - { - var entities = _entities.Where(x => x.Id > 0); - - foreach (var entity in entities) - { - var tag = GetCacheControlTagFor(entity); - - if (tag != null) - { - yield return tag; - } - } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs b/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs index bbc6b4ebd2..855349106f 100644 --- a/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs +++ b/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace SmartStore.Core.Caching { @@ -13,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 @@ -24,8 +26,30 @@ 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 - + /// adds the item obtained from the asynchronous acquirer . + /// + /// The type of the item to get or add + /// 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, 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, Func> acquirer = null); /// /// Adds a cache item with the specified key @@ -33,7 +57,10 @@ public interface ICacheManager /// Key /// Value /// Absolute expiration time - void Set(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 @@ -49,21 +76,43 @@ public interface ICacheManager void Remove(string key); /// - /// Scans for all all keys in the underlying cache + /// Scans for all keys matching the input pattern /// /// A key pattern. Can be null. - /// The sequence of matching keys - string[] Keys(string pattern); + /// An array of matching key names + /// + /// Supported glob-style patterns: + /// - h?llo matches hello, hallo and hxllo + /// - h*llo matches hllo and heeeello + /// - h[ae]llo matches hello and hallo, but not hillo + /// - h[^e]llo matches hallo, hbllo, ... but not hello + /// - h[a-b]llo matches hallo and hbllo + /// + IEnumerable Keys(string pattern); /// - /// Removes items by pattern + /// Removes all entries with keys matching the input pattern /// - /// pattern - void RemoveByPattern(string pattern); + /// Glob pattern + /// Count of removed cache items + /// + /// Supported glob-style patterns: + /// - h?llo matches hello, hallo and hxllo + /// - h*llo matches hllo and heeeello + /// - h[ae]llo matches hello and hallo, but not hillo + /// - h[^e]llo matches hallo, hbllo, ... but not hello + /// - h[a-b]llo matches hallo and hbllo + /// + int RemoveByPattern(string pattern); /// /// Clear all cache data /// void Clear(); + + /// + /// Gets a value indicating whether the cache is distributed (e.g. Redis) + /// + bool IsDistributedCache { get; } } } 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/IRequestCache.cs b/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs index 1306d176e1..2ecd14f0b8 100644 --- a/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/IRequestCache.cs @@ -31,7 +31,7 @@ public interface IRequestCache /// /// Key /// Value - void Set(string key, object value); + void Put(string key, object value); /// /// Gets a value indicating whether the value associated with the specified key is cached @@ -86,7 +86,11 @@ public T Get(string key) public T Get(string key, Func acquirer) { - return default(T); + if (acquirer == null) + { + return default(T); + } + return acquirer(); } public void Remove(string key) @@ -97,7 +101,7 @@ public void RemoveByPattern(string pattern) { } - public void Set(string key, object value) + public void Put(string key, object value) { } } diff --git a/src/Libraries/SmartStore.Core/Caching/ISet.cs b/src/Libraries/SmartStore.Core/Caching/ISet.cs new file mode 100644 index 0000000000..d0bff72bd7 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/ISet.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +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); + bool Move(string destinationKey, string item); + + long UnionWith(params string[] keys); + long IntersectWith(params string[] keys); + long ExceptWith(params string[] keys); + + int Count { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs b/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs index 783abf328a..a00224784d 100644 --- a/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs +++ b/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs @@ -1,25 +1,48 @@ using System; -using System.Text.RegularExpressions; using System.Linq; using System.Runtime.Caching; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Threading; +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. - private const string FakeNull = "__[NULL]__"; + 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 bool TryGet(string key, out T value) + private MemoryCache CreateCache() + { + return new MemoryCache("SmartStore"); + } + + public bool IsDistributedCache + { + get { return false; } + } + + private bool TryGet(string key, bool independent, out T value) { value = default(T); @@ -27,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; @@ -39,40 +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, independent, out T value)) + { + return value; + } + + if (_scopeAccessor.Value.HasScope(key)) + { + throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key)); + } - if (TryGet(key, out value)) + // 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)) + { + 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, bool independent = false) + { + if (TryGet(key, independent, out T value)) { return value; } - lock (_cache) + if (_scopeAccessor.Value.HasScope(key)) + { + throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key)); + } + + // Get the async (semaphore) locker specific to this key + using (await KeyedLock.LockAsync("cache:" + key, TimeSpan.FromMinutes(1))) { - // atomic operation must be outer locked - if (!TryGet(key, out value)) + if (!TryGet(key, independent, out value)) { - value = acquirer(); - Set(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 Set(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) @@ -85,9 +148,9 @@ public void Remove(string key) _cache.Remove(key); } - public string[] Keys(string pattern) + public IEnumerable Keys(string pattern) { - Guard.ArgumentNotEmpty(() => pattern); + Guard.NotEmpty(pattern, nameof(pattern)); var keys = _cache.AsParallel().Select(x => x.Key); @@ -96,36 +159,54 @@ public string[] Keys(string pattern) return keys.ToArray(); } - var matcher = CreateMatcher(pattern); - - return keys.Where(x => matcher.IsMatch(x)).ToArray(); + var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase); + return keys.Where(x => wildcard.IsMatch(x)).ToArray(); } - public void RemoveByPattern(string pattern) + public int RemoveByPattern(string pattern) { - var keysToRemove = Keys(pattern); - lock (_cache) { + var keysToRemove = Keys(pattern); + int count = 0; + // lock atomic operation foreach (string key in keysToRemove) { _cache.Remove(key); + 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(); + } - private static Regex CreateMatcher(string pattern) + public virtual ISet GetHashSet(string key, Func> acquirer = null) { - return new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); + 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; @@ -133,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 new file mode 100644 index 0000000000..c88cacd703 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/MemorySet.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Core.Caching +{ + [Serializable] + internal class MemorySet : HashSet, ISet + { + private readonly ICacheManager _cache; + + 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)) + { + var target = _cache.GetHashSet(destinationKey); + return target.Add(item); + } + + return false; + } + + public long ExceptWith(params string[] keys) + { + return Combine(x => this.Except(x), keys); + } + + public long IntersectWith(params string[] keys) + { + return Combine(x => this.Intersect(x), keys); + } + + public long UnionWith(params string[] keys) + { + return Combine(x => this.Union(x), keys); + } + + private long Combine(Func, IEnumerable> func, params string[] keys) + { + if (keys.Length == 0) + return 0; + + var other = keys.SelectMany(x => _cache.GetHashSet(x)).Distinct(); + var result = func(other); + + this.Clear(); + this.AddRange(other); + + return this.Count; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/NullCache.cs b/src/Libraries/SmartStore.Core/Caching/NullCache.cs index 640b161bb6..e75abfd91d 100644 --- a/src/Libraries/SmartStore.Core/Caching/NullCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/NullCache.cs @@ -2,6 +2,7 @@ using SmartStore.Utilities; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace SmartStore.Core.Caching { @@ -17,12 +18,17 @@ public static ICacheManager Instance get { return s_instance; } } - public T Get(string key) + public bool IsDistributedCache + { + get { return false; } + } + + 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) { @@ -31,8 +37,22 @@ public T Get(string key, Func acquirer, TimeSpan? duration = null) return acquirer(); } + public Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false) + { + if (acquirer == null) + { + return Task.FromResult(default(T)); + } + return acquirer(); + } + + + public ISet GetHashSet(string key, Func> acquirer = null) + { + return new MemorySet(this); + } - public void Set(string key, object value, TimeSpan? duration = null) + public void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null) { } @@ -45,13 +65,14 @@ public void Remove(string key) { } - public string[] Keys(string pattern) + public IEnumerable Keys(string pattern) { return new string[0]; } - public void RemoveByPattern(string pattern) + public int RemoveByPattern(string pattern) { + return 0; } public void Clear() diff --git a/src/Libraries/SmartStore.Core/Caching/NullOutputCacheProvider.cs b/src/Libraries/SmartStore.Core/Caching/NullOutputCacheProvider.cs deleted file mode 100644 index ece37802f2..0000000000 --- a/src/Libraries/SmartStore.Core/Caching/NullOutputCacheProvider.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SmartStore.Core.Plugins; - -namespace SmartStore.Core.Caching -{ - [SystemName("NullOutputCacheProvider")] - [FriendlyName("Idle")] - public class NullOutputCacheProvider : IOutputCacheProvider - { - public IPagedList All(int pageIndex, int pageSize) - { - return new PagedList(new List(), pageIndex, pageSize); - } - - public int Count() - { - return 0; - } - - public bool Exists(string key) - { - return false; - } - - public OutputCacheItem Get(string key) - { - return null; - } - - public int InvalidateByRoute(params string[] routes) - { - return 0; - } - - public int InvalidateByTag(params string[] tags) - { - return 0; - } - - public void Remove(params string[] keys) - { - } - - public void RemoveAll() - { - } - - public void Set(string key, OutputCacheItem item) - { - } - } -} diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs new file mode 100644 index 0000000000..9629fee3c5 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +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 + { + private static readonly ConcurrentDictionary _handlers = new ConcurrentDictionary + { + [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 IComponentContext _componentContext; + + private bool _isIdle; + private bool? _isUncacheableRequest; + + 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) + { + 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; + return new ActionDisposable(() => _isIdle = false); + } + + public virtual void Announce(BaseEntity entity) + { + if (!_isIdle && entity != null) + { + _entities.Add(entity); + } + } + + public bool IsDisplayed(BaseEntity entity) + { + if (entity == null) + return false; + + return _entities.Contains(entity); + } + + public void MarkRequestAsUncacheable() + { + // First wins: subsequent calls should not be able to cancel this + if (!_isIdle) + _isUncacheableRequest = true; + } + + public bool IsUncacheableRequest + { + get + { + return _isUncacheableRequest.GetValueOrDefault() == true; + } + } + + public virtual IEnumerable GetCacheControlTagsFor(BaseEntity entity) + { + var empty = Enumerable.Empty(); + + if (entity == null || entity.IsTransientRecord()) + { + return empty; + } + + var type = entity.GetUnproxiedType(); + + if (!_handlers.TryGetValue(type, out var handler)) + { + return empty; + } + + return handler.Invoke(entity, _componentContext); + } + + public IEnumerable GetAllCacheControlTags() + { + var tags = _entities + .Where(x => x.Id > 0) + .SelectMany(x => GetCacheControlTagsFor(x)) + .Where(x => x != null) + .Distinct() + .ToArray(); + + return tags; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/ICacheableRouteRegistrar.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/ICacheableRouteRegistrar.cs new file mode 100644 index 0000000000..b30feeb1e4 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/ICacheableRouteRegistrar.cs @@ -0,0 +1,21 @@ +using System; + +namespace SmartStore.Core.Caching +{ + public interface ICacheableRouteRegistrar + { + void RegisterCacheableRoute(params string[] routes); + void RemoveCacheableRoute(params string[] routes); + } + + public class NullCacheableRouteRegistrar : ICacheableRouteRegistrar + { + public void RegisterCacheableRoute(params string[] routes) + { + } + + public void RemoveCacheableRoute(params string[] routes) + { + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/IDisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IDisplayControl.cs similarity index 80% rename from src/Libraries/SmartStore.Core/Caching/IDisplayControl.cs rename to src/Libraries/SmartStore.Core/Caching/OutputCache/IDisplayControl.cs index 006318b69e..9646cf649e 100644 --- a/src/Libraries/SmartStore.Core/Caching/IDisplayControl.cs +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IDisplayControl.cs @@ -16,9 +16,11 @@ public interface IDisplayControl bool IsUncacheableRequest { get; } - string GetCacheControlTagFor(BaseEntity entity); + IEnumerable GetCacheControlTagsFor(BaseEntity entity); - IEnumerable GetCacheControlTags(); + IEnumerable GetAllCacheControlTags(); + + IDisposable BeginIdleScope(); } public static class IDisplayControlExtensions diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs new file mode 100644 index 0000000000..234b67ced2 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core.Configuration; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Utilities; + +namespace SmartStore.Core.Caching +{ + public class ObserveEntityContext + { + public IOutputCacheProvider OutputCacheProvider { get; set; } + public IDisplayControl DisplayControl { get; set; } + public BaseEntity Entity { get; set; } + public IHookedEntity EntityEntry { get; set; } + public bool Handled { get; set; } + public ContainerManager ServiceContainer { get; set; } + } + + /// + /// Allows registration of output cache invalidation handlers + /// + public interface IOutputCacheInvalidationObserver + { + /// + /// Registers an entity observer. The passed observer is responsible for invalidating the output cache + /// by calling one of the invalidation methods in the instance. + /// The observer must then set the property to true + /// to signal the framework that it should skip executing subsequent observers. + /// + /// The observer action + /// + /// The implementation of this interface is singleton scoped. + /// Don't use objects with shorter lifetime in your handler as this will lead to memory leaks. + /// If your handler needs to call service methods, resolve required services + /// with . + /// + void ObserveEntity(Action observer); + + IEnumerable> GetEntityObservers(); + + /// + /// Registers a setting key to be observed by the framework. If the value for the passed + /// setting key changes, the framework calls the handler. + /// The key can either be fully qualified - e.g. "CatalogSettings.ShowProductSku" -, + /// or prefixed - e.g. "CatalogSettings.*". The latter calls the invalidator when ANY CatalogSetting changes. + /// + /// + /// The invalidation action handler. If null is passed, the framework + /// uses the default invalidator, which is . + /// + void ObserveSetting(string settingKey, Action invalidationAction); + + Action GetInvalidationActionForSetting(string settingKey); + } + + public class NullOutputCacheInvalidationObserver : IOutputCacheInvalidationObserver + { + private static readonly IOutputCacheInvalidationObserver _instance = new NullOutputCacheInvalidationObserver(); + + public static IOutputCacheInvalidationObserver Instance + { + get { return _instance; } + } + + public void ObserveEntity(Action observer) + { + } + + public IEnumerable> GetEntityObservers() + { + return Enumerable.Empty>(); + } + + public void ObserveSetting(string settingKey, Action invalidationAction) + { + } + + public Action GetInvalidationActionForSetting(string settingKey) + { + return null; + } + } + + public static class IOutputCacheInvalidationObserverExtensions + { + public static void ObserveSetting(this IOutputCacheInvalidationObserver observer, string settingKey) + { + observer.ObserveSetting(settingKey, null); + } + + /// + /// Registers a concrete setting class to be observed by the framework. If any setting property + /// of changes, the framework will purge the cache. + /// + /// The type of the concrete setting class to observe + /// + /// A property observer precedes a class observer. + /// + public static void ObserveSettings(this IOutputCacheInvalidationObserver observer) + where TSetting : ISettings + { + ObserveSettings(observer, null); + } + + /// + /// Registers a concrete setting class to be observed by the framework. If any setting property + /// of changes, the framework will call the handler. + /// + /// The type of the concrete setting class to observe + /// + /// The invalidation action handler. If null is passed, the framework + /// uses the default invalidator, which is . + /// + /// + /// A property observer precedes a class observer. + /// + public static void ObserveSettings( + this IOutputCacheInvalidationObserver observer, + Action invalidationAction) where TSetting : ISettings + { + var key = typeof(TSetting).Name + ".*"; + observer.ObserveSetting(key, invalidationAction); + } + + /// + /// Registers a setting property to be observed by the framework. If the value for the passed + /// property changes, the framework will purge the cache. + /// + /// The type of the concrete setting class which contains the property + /// The property lambda + public static void ObserveSettingProperty( + this IOutputCacheInvalidationObserver observer, + Expression> propertyAccessor) where TSetting : ISettings + { + ObserveSettingProperty(observer, propertyAccessor, null); + } + + /// + /// Registers a setting property to be observed by the framework. If the value for the passed + /// property changes, the framework will call the handler. + /// + /// The type of the concrete setting class which contains the property + /// The property lambda + /// + /// The invalidation action handler. If null is passed, the framework + /// uses the default invalidator, which is . + /// + public static void ObserveSettingProperty( + this IOutputCacheInvalidationObserver observer, + Expression> propertyAccessor, + Action invalidationAction) where TSetting : ISettings + { + Guard.NotNull(propertyAccessor, nameof(propertyAccessor)); + + var key = TypeHelper.NameOf(propertyAccessor, true); + observer.ObserveSetting(key, invalidationAction); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/IOutputCacheProvider.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheProvider.cs similarity index 77% rename from src/Libraries/SmartStore.Core/Caching/IOutputCacheProvider.cs rename to src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheProvider.cs index 5ef669b70f..3e2aead128 100644 --- a/src/Libraries/SmartStore.Core/Caching/IOutputCacheProvider.cs +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheProvider.cs @@ -13,10 +13,11 @@ public interface IOutputCacheProvider : IProvider void Remove(params string[] keys); void RemoveAll(); - IPagedList All(int pageIndex, int pageSize); + IPagedList All(int pageIndex, int pageSize, bool withContent = false); int Count(); int InvalidateByRoute(params string[] routes); + int InvalidateByPrefix(string keyPrefix); int InvalidateByTag(params string[] tags); } } diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/NullOutputCacheProvider.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/NullOutputCacheProvider.cs new file mode 100644 index 0000000000..f9d2ba828a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/NullOutputCacheProvider.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Plugins; + +namespace SmartStore.Core.Caching +{ + [SystemName("OutputCacheProvider.Idle")] + [FriendlyName("Idle")] + public class NullOutputCacheProvider : IOutputCacheProvider + { + private static readonly IOutputCacheProvider s_instance = new NullOutputCacheProvider(); + + public static IOutputCacheProvider Instance + { + get { return s_instance; } + } + + public IPagedList All(int pageIndex, int pageSize, bool withContent = false) + { + return new PagedList(new List(), pageIndex, pageSize); + } + + public int Count() + { + return 0; + } + + public bool Exists(string key) + { + return false; + } + + public OutputCacheItem Get(string key) + { + return null; + } + + public int InvalidateByRoute(params string[] routes) + { + return 0; + } + + public int InvalidateByPrefix(string keyPrefix) + { + return 0; + } + + public int InvalidateByTag(params string[] tags) + { + return 0; + } + + public void Remove(params string[] keys) + { + } + + public void RemoveAll() + { + } + + public void Set(string key, OutputCacheItem item) + { + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCacheItem.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/OutputCacheItem.cs similarity index 76% rename from src/Libraries/SmartStore.Core/Caching/OutputCacheItem.cs rename to src/Libraries/SmartStore.Core/Caching/OutputCache/OutputCacheItem.cs index 6d13594217..6ad1ec3c65 100644 --- a/src/Libraries/SmartStore.Core/Caching/OutputCacheItem.cs +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/OutputCacheItem.cs @@ -5,8 +5,11 @@ namespace SmartStore.Core.Caching { [Serializable] [DebuggerDisplay("{CacheKey}, Url: {Url}, Query: {QueryString}, Duration: {Duration}, Tags: {Tags}")] - public class OutputCacheItem + public class OutputCacheItem : ICloneable { + // used for serialization compatibility + public static readonly string Version = "1"; + public string CacheKey { get; set; } public string RouteKey { get; set; } public DateTime CachedOnUtc { get; set; } @@ -28,7 +31,7 @@ public DateTime ExpiresOnUtc { get { return CachedOnUtc.AddSeconds(Duration); } } - + public bool IsValid(DateTime utcNow) { return utcNow < ExpiresOnUtc; @@ -41,5 +44,15 @@ public string JoinTags() return String.Join(";", Tags); } + + public OutputCacheItem Clone() + { + return (OutputCacheItem)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } } } diff --git a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs index 8f8fa17945..5fb74d2400 100644 --- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs @@ -4,10 +4,11 @@ using System.Linq; using System.Text.RegularExpressions; using System.Web; +using SmartStore.Utilities; namespace SmartStore.Core.Caching { - public class RequestCache : IRequestCache + public class RequestCache : DisposableObject, IRequestCache { const string RegionName = "SmartStoreNET:"; @@ -46,7 +47,7 @@ public T Get(string key, Func acquirer) return default(T); } - public void Set(string key, object value) + public void Put(string key, object value) { var items = GetItems(); @@ -81,7 +82,7 @@ public void RemoveByPattern(string pattern) foreach (string key in keysToRemove) { - items.Remove(key); + items.Remove(BuildKey(key)); } } @@ -97,20 +98,23 @@ public IEnumerable Keys(string pattern) if (items.Count == 0) yield break; - var matcher = pattern == "*" ? null : CreateMatcher(pattern); + var prefixLen = RegionName.Length; + + pattern = pattern.NullEmpty() ?? "*"; + var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase); var enumerator = items.GetEnumerator(); while (enumerator.MoveNext()) { - string key = enumerator.Key as string; - if (key == null) - continue; - if (key.StartsWith(RegionName)) + if (enumerator.Key is string key) { - key = key.Substring(RegionName.Length); - if (matcher == null || matcher.IsMatch(key)) + if (key.StartsWith(RegionName)) { - yield return key; + key = key.Substring(prefixLen); + if (pattern == "*" || wildcard.IsMatch(key)) + { + yield return key; + } } } } @@ -121,9 +125,10 @@ private string BuildKey(string key) return RegionName + key.EmptyNull(); } - private static Regex CreateMatcher(string pattern) + protected override void OnDispose(bool disposing) { - return new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); + if (disposing) + Clear(); } } } diff --git a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs new file mode 100644 index 0000000000..86d5b00249 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Linq; +using Newtonsoft.Json; +using SmartStore.Utilities; +using System.Threading; + +namespace SmartStore.Collections +{ + /// + /// A thread-safe data structure that contains multiple values for each key. + /// + /// The type of key. + /// The type of value. + [JsonConverter(typeof(ConcurrentMultiMapConverter))] + [Serializable] + public class ConcurrentMultimap : IEnumerable>> + { + private readonly ConcurrentDictionary> _dict; + private readonly Func, ICollection> _collectionCreator; + + public ConcurrentMultimap() + : this(null, null, null) + { + } + + public ConcurrentMultimap(IEqualityComparer comparer) + : this(null, comparer, null) + { + } + + public ConcurrentMultimap(IEnumerable>> items) + : this(items, null, null) + { + // for serialization + } + + public ConcurrentMultimap( + IEnumerable>> items, + IEqualityComparer comparer) + : this(items, comparer, null) + { + // 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; + + foreach (var item in items) + { + 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. + /// + public int Count + { + get + { + return _dict.Keys.Count; + } + } + + /// + /// Gets the total count of items in all groups. + /// + public int TotalValueCount + { + get + { + 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 SyncedCollection this[TKey key] + { + get + { + GetOrCreateValues(key, null, out var col); + return col; + } + } + + /// + /// Gets the collection of keys. + /// + public virtual ICollection Keys + { + get { return _dict.Keys; } + } + + /// + /// Gets all value collections. + /// + public virtual ICollection> Values + { + get { return _dict.Values; } + } + + /// + /// Attempts to add the specified value for the specified key. + /// + /// The key. + /// The value. + public virtual void TryAdd(TKey key, TValue value) + { + if (!GetOrCreateValues(key, new[] { value }, out var col)) + { + col.Add(value); + } + } + + /// + /// Attempts to add the specified values to the specified key. + /// + /// The key. + /// The values. + public virtual void TryAddRange(TKey key, IEnumerable values) + { + Guard.NotNull(values, nameof(values)); + + 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 and return all values for the specified key. + /// + /// The key. + /// True if any such values existed; otherwise false. + public virtual bool TryRemoveAll(TKey key, out SyncedCollection collection) + { + return _dict.TryRemove(key, out collection); + } + + /// + /// Removes all values. + /// + public virtual void Clear() + { + _dict.Clear(); + } + + /// + /// Determines whether the multimap contains any values for the specified key. + /// + /// The key. + /// True if the multimap has one or more values for the specified key, otherwise false. + public virtual bool ContainsKey(TKey key) + { + return _dict.ContainsKey(key); + } + + /// + /// Determines whether the multimap contains the specified value for the specified key. + /// + /// The key. + /// The value. + /// True if the multimap contains such a value; otherwise, false. + public virtual bool ContainsValue(TKey key, TValue value) + { + return _dict.ContainsKey(key) && _dict[key].Contains(value); + } + + /// + /// Returns an enumerator that iterates through the multimap. + /// + /// An object that can be used to iterate through the multimap. + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the multimap. + /// + /// An object that can be used to iterate through the multimap. + public virtual IEnumerator>> GetEnumerator() + { + 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) + { + Guard.NotNull(source, nameof(source)); + + var map = new ConcurrentMultimap(); + + foreach (IGrouping group in source) + { + map.TryAddRange(group.Key, group); + } + + return map; + } + + #endregion + } + + public class ConcurrentMultiMapConverter : MultiMapConverter + { + public override bool CanConvert(Type objectType) + { + var canConvert = objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(ConcurrentMultimap<,>); + return canConvert; + } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs index 66b12e67f8..a9014b0c80 100644 --- a/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs +++ b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs @@ -12,8 +12,9 @@ namespace SmartStore.Collections public class LazyMultimap : Multimap { private readonly Func> _load; - private readonly List _loaded; // to avoid database round trips with empty results - private List _collect; + private readonly HashSet _loaded; // to avoid database round trips with empty results + private HashSet _collect; + private bool _fullyLoaded; //private int _roundTripCount; /// @@ -24,40 +25,14 @@ public class LazyMultimap : Multimap public LazyMultimap(Func> load, IEnumerable collect = null) { _load = load; - _loaded = new List(); + _loaded = new HashSet(); - _collect = collect == null ? new List() : new List(collect); + _collect = collect == null ? new HashSet() : new HashSet(collect); } - protected virtual void Load(IEnumerable keys) + public bool FullyLoaded { - if (keys != null) - { - var loadKeys = (_collect.Count == 0 ? keys : _collect.Concat(keys)) - .Distinct() - .Except(_loaded) - .ToArray(); - - _collect.Clear(); // invalidate, do not load again - - if (loadKeys.Any()) - { - //++_roundTripCount; - //Debug.WriteLine("Round trip {0} of {1}: {2}", _roundTripCount, typeof(T).Name, string.Join(",", loadKeys.OrderBy(x => x))); - - var items = _load(loadKeys); - - _loaded.AddRange(loadKeys); - - if (items != null) - { - foreach (var range in items) - { - base.AddRange(range.Key, range.Value); - } - } - } - } + get { return _fullyLoaded; } } /// @@ -65,7 +40,7 @@ protected virtual void Load(IEnumerable keys) /// /// Data key /// Collection of data - public virtual ICollection Load(int key) + public virtual ICollection GetOrLoad(int key) { if (key == 0) { @@ -85,6 +60,43 @@ public virtual ICollection Load(int key) return result; } + public void LoadAll() + { + Load(_collect); + _fullyLoaded = true; + } + + protected virtual void Load(IEnumerable keys) + { + if (keys == null) + return; + + var loadKeys = (_collect.Count == 0 ? keys : _collect.Concat(keys)) + .Distinct() + .Except(_loaded) + .ToArray(); + + _collect.Clear(); // invalidate, do not load again + + if (loadKeys.Any()) + { + //++_roundTripCount; + //Debug.WriteLine("Round trip {0} of {1}: {2}", _roundTripCount, typeof(T).Name, string.Join(",", loadKeys.OrderBy(x => x))); + + var items = _load(loadKeys); + + _loaded.AddRange(loadKeys); + + if (items != null) + { + foreach (var range in items) + { + base.AddRange(range.Key, range.Value); + } + } + } + } + /// /// Collect keys for combined loading /// @@ -94,12 +106,13 @@ public virtual void Collect(IEnumerable keys) { if (keys != null && keys.Any()) { - _collect = _collect.Union(keys).ToList(); + //_collect = _collect.Union(keys).ToList(); + _collect.UnionWith(keys); } } /// - /// Collect single key for combinde loading + /// Collect single key for combined loading /// /// Data key public virtual void Collect(int key) @@ -114,6 +127,7 @@ public override void Clear() { _loaded.Clear(); _collect.Clear(); + _fullyLoaded = false; //_roundTripCount = 0; base.Clear(); 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 1ae76884f8..738ffab5fa 100644 --- a/src/Libraries/SmartStore.Core/Collections/MultiMap.cs +++ b/src/Libraries/SmartStore.Core/Collections/MultiMap.cs @@ -1,63 +1,63 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Concurrent; using System.Linq; using System.Linq.Expressions; -using System.Threading; - -using SmartStore.Utilities.Threading; -using System.Collections.ObjectModel; +using Newtonsoft.Json; namespace SmartStore.Collections { - /// - /// A data structure that contains multiple values for each key. - /// - /// The type of key. - /// The type of value. - public class Multimap : IEnumerable>> + /// + /// A data structure that contains multiple values for each key. + /// + /// The type of key. + /// The type of value. + [JsonConverter(typeof(MultiMapConverter))] + [Serializable] + public class Multimap : IEnumerable>> { - private readonly IDictionary> _items; - private readonly Func> _listCreator; + private readonly IDictionary> _dict; + private readonly Func, ICollection> _collectionCreator; private readonly bool _isReadonly = false; - public Multimap() - : this(false) + internal readonly static Func, ICollection> DefaultCollectionCreator = + x => new List(x ?? Enumerable.Empty()); + + public Multimap() + : this(EqualityComparer.Default) { } - internal Multimap(bool threadSafe) + public Multimap(IEqualityComparer comparer) { - if (threadSafe) - { - _items = new ConcurrentDictionary>(); - _listCreator = () => new SynchronizedCollection(); - } - else - { - _items = new Dictionary>(); - _listCreator = () => new List(); - } + _dict = new Dictionary>(comparer ?? EqualityComparer.Default); + _collectionCreator = DefaultCollectionCreator; } - public Multimap(Func> listCreator) - : this(new Dictionary>(), listCreator) + public Multimap(Func, ICollection> collectionCreator) + : this(new Dictionary>(), collectionCreator) { } + public Multimap(IEqualityComparer comparer, Func, ICollection> collectionCreator) + : this(new Dictionary>(comparer ?? EqualityComparer.Default), collectionCreator) + { + } - internal Multimap(IDictionary> dictionary, Func> listCreator) + internal Multimap(IDictionary> dictionary, Func, ICollection> collectionCreator) { - _items = dictionary; - _listCreator = listCreator; + Guard.NotNull(dictionary, nameof(dictionary)); + Guard.NotNull(collectionCreator, nameof(collectionCreator)); + + _dict = dictionary; + _collectionCreator = collectionCreator; } protected Multimap(IDictionary> dictionary, bool isReadonly) { - Guard.ArgumentNotNull(() => dictionary); + Guard.NotNull(dictionary, nameof(dictionary)); - _items = dictionary; + _dict = dictionary; if (isReadonly && dictionary != null) { @@ -70,14 +70,41 @@ protected Multimap(IDictionary> dictionary, bool isRea _isReadonly = isReadonly; } - /// - /// Gets the count of groups/keys. - /// - public int Count + public Multimap(IEnumerable>> items) + : this(items, null) + { + // for serialization + } + + public Multimap(IEnumerable>> items, IEqualityComparer comparer) + { + // for serialization + Guard.NotNull(items, nameof(items)); + + _dict = new Dictionary>(comparer ?? EqualityComparer.Default); + + if (items != null) + { + foreach (var kvp in items) + { + _dict[kvp.Key] = CreateCollection(kvp.Value); + } + } + } + + protected virtual ICollection CreateCollection(IEnumerable values) + { + return (_collectionCreator ?? DefaultCollectionCreator)(values ?? Enumerable.Empty()); + } + + /// + /// Gets the count of groups/keys. + /// + public int Count { get { - return this._items.Keys.Count; + return this._dict.Keys.Count; } } @@ -88,7 +115,7 @@ public int TotalValueCount { get { - return this._items.Values.Sum(x => x.Count); + return this._dict.Values.Sum(x => x.Count); } } @@ -100,15 +127,15 @@ public virtual ICollection this[TKey key] { get { - if (!_items.ContainsKey(key)) + if (!_dict.ContainsKey(key)) { if (!_isReadonly) - _items[key] = _listCreator(); + _dict[key] = CreateCollection(null); else return null; } - return _items[key]; + return _dict[key]; } } @@ -117,25 +144,25 @@ 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) { - Guard.ArgumentNotNull(() => key); - Guard.ArgumentNotNull(() => predicate); + 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(); @@ -160,9 +187,12 @@ 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; - this[key].AddRange(values); + CheckNotReadonly(); + + this[key].AddRange(values); } /// @@ -175,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; } @@ -193,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); } /// @@ -202,7 +232,7 @@ public virtual bool RemoveAll(TKey key) public virtual void Clear() { CheckNotReadonly(); - _items.Clear(); + _dict.Clear(); } /// @@ -212,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); } /// @@ -223,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); } /// @@ -241,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; } @@ -253,16 +283,11 @@ private void CheckNotReadonly() #region Static members - public static Multimap ThreadSafe() - { - return new Multimap(true); - } - public static Multimap CreateFromLookup(ILookup source) { - Guard.ArgumentNotNull(() => source); + Guard.NotNull(source, nameof(source)); - var map = new Multimap(); + var map = new Multimap(); foreach (IGrouping group in source) { @@ -272,6 +297,56 @@ public static Multimap CreateFromLookup(ILookup sour return map; } - #endregion - } + #endregion + } + + public class MultiMapConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + var canConvert = objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Multimap<,>); + return canConvert; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // typeof TKey + var keyType = objectType.GetGenericArguments()[0]; + + // typeof TValue + var valueType = objectType.GetGenericArguments()[1]; + + // typeof IEnumerable> + var sequenceType = typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(keyType, typeof(IEnumerable<>).MakeGenericType(valueType))); + + // serialize JArray to sequenceType + var list = serializer.Deserialize(reader, sequenceType); + + if (keyType == typeof(string)) + { + // call constructor Multimap(IEnumerable>> items, IEqualityComparer comparer) + // TBD: we always assume string keys to be case insensitive. Serialize it somehow and fetch here! + return Activator.CreateInstance(objectType, new object[] { list, StringComparer.OrdinalIgnoreCase }); + } + else + { + // call constructor Multimap(IEnumerable>> items) + return Activator.CreateInstance(objectType, new object[] { list }); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartArray(); + { + var enumerable = value as IEnumerable; + foreach (var item in enumerable) + { + // Json.Net uses a converter for KeyValuePair here + serializer.Serialize(writer, item); + } + } + writer.WriteEndArray(); + } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Collections/Querystring.cs b/src/Libraries/SmartStore.Core/Collections/Querystring.cs index 9df23fc868..ba5a3cc895 100644 --- a/src/Libraries/SmartStore.Core/Collections/Querystring.cs +++ b/src/Libraries/SmartStore.Core/Collections/Querystring.cs @@ -1,14 +1,14 @@ -using System.Text; -using System.Web; -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Web; namespace SmartStore.Collections { - /// - /// http://weblogs.asp.net/bradvincent/archive/2008/10/27/helper-class-querystring-builder-chainable.aspx - /// - public class QueryString : NameValueCollection + /// + /// http://weblogs.asp.net/bradvincent/archive/2008/10/27/helper-class-querystring-builder-chainable.aspx + /// + public class QueryString : NameValueCollection { public QueryString() { @@ -19,17 +19,27 @@ public QueryString(string queryString) FillFromString(queryString); } - public static QueryString Current + public QueryString(NameValueCollection queryString) + : base(queryString) + { + } + + public static QueryString Current { get { return new QueryString().FromCurrent(); } } - /// - /// extracts a querystring from a full URL - /// - /// the string to extract the querystring from - /// a string representing only the querystring - [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] + public static QueryString CurrentUnvalidated + { + get { return new QueryString().FromCurrent(true); } + } + + /// + /// Extracts a querystring from a full URL + /// + /// the string to extract the querystring from + /// a string representing only the querystring + [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public static string ExtractQuerystring(string s) { if (!string.IsNullOrEmpty(s)) @@ -39,6 +49,7 @@ public static string ExtractQuerystring(string s) return s.Substring(s.IndexOf("?") + 1); } } + return s; } @@ -71,12 +82,20 @@ public QueryString FillFromString(string s, bool urlDecode = false) /// /// returns a QueryString object based on the current querystring of the request /// + /// true to get values from the unvalidated query string. /// the QueryString object - public QueryString FromCurrent() + public QueryString FromCurrent(bool unvalidated = false) { if (HttpContext.Current != null) { - return FillFromString(HttpContext.Current.Request.QueryString.ToString(), true); + if (unvalidated) + { + return FillFromString(HttpContext.Current.Request.Unvalidated.QueryString.ToString(), true); + } + else + { + return FillFromString(HttpContext.Current.Request.QueryString.ToString(), true); + } } base.Clear(); return this; @@ -94,7 +113,7 @@ public QueryString FromCurrent() } /// - /// adds a name value pair to the collection + /// Adds a name value pair to the collection /// /// the name /// the value associated to the name @@ -115,15 +134,16 @@ public QueryString Add(string name, string value, bool isUnique) { base[name] += "," + HttpUtility.UrlEncode(value); } + return this; } - /// - /// removes a name value pair from the querystring collection - /// - /// name of the querystring value to remove - /// the QueryString object - public new QueryString Remove(string name) + /// + /// Removes a name value pair from the querystring collection + /// + /// name of the querystring value to remove + /// the QueryString object + public new QueryString Remove(string name) { string existingValue = base[name]; if (!string.IsNullOrEmpty(existingValue)) @@ -175,24 +195,46 @@ public bool Contains(string name) } /// - /// outputs the querystring object to a string + /// Outputs the querystring object to a string /// /// the encoded querystring as it would appear in a browser public override string ToString() { - var builder = new StringBuilder(); - for (var i = 0; i < base.Keys.Count; i++) - { - if (!string.IsNullOrEmpty(base.Keys[i])) - { - foreach (string val in base[base.Keys[i]].EmptyNull().Split(',')) - { - builder.Append((builder.Length == 0) ? "?" : "&").Append( - HttpUtility.UrlEncode(base.Keys[i])).Append("=").Append(val); - } - } - } - return builder.ToString(); + return ToString(true); } - } + + /// + /// Outputs the querystring object to a string + /// + /// Whether to create entries for each comma-separated value + /// the encoded querystring as it would appear in a browser + public string ToString(bool splitValues) + { + var builder = new StringBuilder(); + for (var i = 0; i < base.Keys.Count; i++) + { + var key = base.Keys[i]; + var value = base[key]; + + if (!string.IsNullOrEmpty(key)) + { + builder.Append((builder.Length == 0) ? "?" : "&"); + + if (splitValues) + { + foreach (string val in value.EmptyNull().Split(',')) + { + builder.Append(HttpUtility.UrlEncode(key)).Append("=").Append(val); + } + } + else + { + builder.Append(HttpUtility.UrlEncode(key)).Append("=").Append(value); + } + } + } + + return builder.ToString(); + } + } } 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/TopologicalSorter.cs b/src/Libraries/SmartStore.Core/Collections/TopologicalSorter.cs index 5fc2b6818a..e8902302c1 100644 --- a/src/Libraries/SmartStore.Core/Collections/TopologicalSorter.cs +++ b/src/Libraries/SmartStore.Core/Collections/TopologicalSorter.cs @@ -22,7 +22,7 @@ public static ITopologicSortable[] SortTopological(this ITopologicSortable public static ITopologicSortable[] SortTopological(this ITopologicSortable[] items, IEqualityComparer comparer) { - Guard.ArgumentNotNull(() => items); + Guard.NotNull(items, nameof(items)); var sortedIndexes = SortIndexesTopological(items, comparer); var sortedList = new List>(sortedIndexes.Length); @@ -43,7 +43,7 @@ public static int[] SortIndexesTopological(this ITopologicSortable[] items public static int[] SortIndexesTopological(this ITopologicSortable[] items, IEqualityComparer comparer) { - Guard.ArgumentNotNull(() => items); + Guard.NotNull(items, nameof(items)); if (items.Length == 0) { diff --git a/src/Libraries/SmartStore.Core/Collections/TreeNode.cs b/src/Libraries/SmartStore.Core/Collections/TreeNode.cs index c434c6ad3c..00034d0778 100644 --- a/src/Libraries/SmartStore.Core/Collections/TreeNode.cs +++ b/src/Libraries/SmartStore.Core/Collections/TreeNode.cs @@ -1,439 +1,258 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Linq.Expressions; using System.Text; +using Newtonsoft.Json; +using SmartStore.ComponentModel; namespace SmartStore.Collections { - public class TreeNode : ICloneable> - { - private readonly LinkedList> _children = new LinkedList>(); - private int? _depth = null; - - public TreeNode(T value) - { - Value = value; - } - - #region Properties - - public TreeNode Parent { get; private set; } - - public T Value { get; set; } - - public TreeNode this[int i] - { - get - { - return _children.ElementAt(i); - } - } - - public IEnumerable> Children - { - get - { - return _children; - } - } - - public IEnumerable> LeafNodes - { - get - { - return _children.Where(x => x.IsLeaf); - } - } - - public IEnumerable> NonLeafNodes - { - get - { - return _children.Where(x => !x.IsLeaf); - } - } - - - public TreeNode FirstChild - { - get - { - var first = _children.First; - if (first != null) - return first.Value; - return null; - } - } - - public TreeNode LastChild - { - get - { - var last = _children.Last; - if (last != null) - return last.Value; - return null; - } - } - - public bool IsLeaf - { - get - { - return _children.Count == 0; - } - } - - public bool HasChildren - { - get - { - return _children.Count > 0; - } - } - - public bool IsRoot - { - get - { - return Parent == null; - } - } - - public int Depth + [JsonConverter(typeof(TreeNodeConverter))] + public class TreeNode : TreeNodeBase> + { + public TreeNode(TValue value) { - get + Guard.NotNull(value, nameof(value)); + + Value = value; + } + + public TreeNode(TValue value, IEnumerable children) + : this(value) + { + if (children != null && children.Any()) + { + AppendRange(children); + } + } + + public TreeNode(TValue value, IEnumerable> children) + : this(value) + { + // for serialization + if (children != null && children.Any()) + { + AppendRange(children); + } + } + + public TValue Value + { + get; + private set; + } + + protected override TreeNode CreateInstance() + { + TValue value = this.Value; + + if (value is ICloneable) { - if (!_depth.HasValue) + value = ((ICloneable)value).Clone(); + } + + var clonedNode = new TreeNode(value); + + // Assign or clone Metadata + if (_metadata != null && _metadata.Count > 0) + { + foreach (var kvp in _metadata) { - var node = this; - int depth = -1; - while (node != null && !node.IsRoot) + var metadataValue = kvp.Value is ICloneable + ? ((ICloneable)kvp.Value).Clone() + : kvp.Value; + clonedNode.SetMetadata(kvp.Key, metadataValue); + } + } + + if (_id != null) + { + clonedNode._id = _id; + } + + return clonedNode; + } + + public TreeNode Append(TValue value, object id = null) + { + var node = new TreeNode(value); + node._id = id; + this.Append(node); + return node; + } + + public void AppendRange(IEnumerable values) + { + values.Each(x => Append(x)); + } + + public void AppendRange(IEnumerable values, Func idSelector) + { + Guard.NotNull(idSelector, nameof(idSelector)); + + values.Each(x => Append(x, idSelector(x))); + } + + public TreeNode Prepend(TValue value, object id = null) + { + var node = new TreeNode(value); + node._id = id; + this.Prepend(node); + return node; + } + + public IEnumerable Flatten(bool includeSelf = true) + { + return this.Flatten(null, includeSelf); + } + + public IEnumerable Flatten(Expression> expression, bool includeSelf = true) + { + IEnumerable list; + if (includeSelf) + { + list = new[] { this.Value }; + } + else + { + list = Enumerable.Empty(); + } + + if (!HasChildren) + return list; + + var result = list.Union(Children.SelectMany(x => x.Flatten())); + if (expression != null) + { + result = result.Where(expression.Compile()); + } + + return result; + } + } + + public class TreeNodeConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + var canConvert = objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(TreeNode<>); + return canConvert; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var valueType = objectType.GetGenericArguments()[0]; + var sequenceType = typeof(List<>).MakeGenericType(objectType); + + object objValue = null; + object objChildren = null; + string id = null; + Dictionary metadata = null; + + reader.Read(); + while (reader.TokenType == JsonToken.PropertyName) + { + string a = reader.Value.ToString(); + if (string.Equals(a, "Value", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + objValue = serializer.Deserialize(reader, valueType); + } + else if (string.Equals(a, "Metadata", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + metadata = serializer.Deserialize>(reader); + } + else if (string.Equals(a, "Children", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + objChildren = serializer.Deserialize(reader, sequenceType); + } + else if (string.Equals(a, "Id", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + id = serializer.Deserialize(reader); + } + else + { + reader.Skip(); + } + + reader.Read(); + } + + var ctorParams = objChildren != null + ? new object[] { objValue, objChildren } + : new object[] { objValue }; + + var treeNode = Activator.CreateInstance(objectType, ctorParams); + + // Set Metadata + if (metadata != null && metadata.Count > 0) + { + var metadataProp = FastProperty.GetProperty(objectType, "Metadata", PropertyCachingStrategy.Cached); + metadataProp.SetValue(treeNode, metadata); + } + + // Set Id + if (id.HasValue()) + { + var idProp = FastProperty.GetProperty(objectType, "Id", PropertyCachingStrategy.Cached); + idProp.SetValue(treeNode, id); + } + + return treeNode; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartObject(); + { + // Id + if (GetPropValue("Id", value) is object o) + { + writer.WritePropertyName("Id"); + serializer.Serialize(writer, o); + } + + // Value + writer.WritePropertyName("Value"); + serializer.Serialize(writer, GetPropValue("Value", value)); + + // Metadata + if (GetPropValue("Metadata", value) is IDictionary dict && dict.Count > 0) + { + writer.WritePropertyName("Metadata"); + + var typeNameHandling = serializer.TypeNameHandling; + try { - depth++; - node = node.Parent; + serializer.TypeNameHandling = TypeNameHandling.All; + serializer.Serialize(writer, dict); + } + finally + { + serializer.TypeNameHandling = typeNameHandling; } - _depth = depth; } - return _depth.Value; + // Children + if (GetPropValue("HasChildren", value) is bool b && b == true) + { + writer.WritePropertyName("Children"); + serializer.Serialize(writer, GetPropValue("Children", value)); + } } + writer.WriteEndObject(); } - public TreeNode Root - { - get - { - var root = this; - while (root.Parent != null) - { - root = root.Parent; - } - return root; - } - } - - public TreeNode Next - { - get - { - if (this.Parent != null) - { - var self = this.Parent._children.Find(this); - var next = self != null ? self.Next : null; - if (next != null) - return next.Value; - } - return null; - } - } - - public TreeNode Previous - { - get - { - if (this.Parent != null) - { - var self = this.Parent._children.Find(this); - var prev = self != null ? self.Previous : null; - if (prev != null) - return prev.Value; - } - return null; - } - } - - #endregion - - #region Methods/Declarations - - private void AddChild(TreeNode node, bool clone, bool append = true) - { - var newNode = node; - if (clone) - { - newNode = node.Clone(true); - } - newNode.Parent = this; - newNode.TraverseTree(x => x._depth = null); - if (append) - { - _children.AddLast(newNode); - } - else - { - _children.AddFirst(newNode); - } - } - - #region Append - - public TreeNode Append(T value) - { - var node = new TreeNode(value); - this.AddChild(node, false); - return node; - } - - public void Append(TreeNode node, bool clone = true) - { - this.AddChild(node, clone, true); - } - - public ICollection> AppendMany(IEnumerable values) - { - return values.Select(this.Append).AsReadOnly(); - } - - public TreeNode[] AppendMany(params T[] values) - { - return values.Select(this.Append).ToArray(); - } - - public void AppendMany(IEnumerable> values) - { - values.Each(x => this.AddChild(x, true)); - } - - public void AppendChildrenOf(TreeNode node) - { - node._children.Each(x => this.AddChild(x, true)); - } - - #endregion - - #region Prepend - - public TreeNode Prepend(T value) - { - var node = new TreeNode(value); - this.AddChild(node, true, false); - return node; - } - - #endregion - - #region Insert[...] - - public void InsertAfter(TreeNode refNode) - { - this.Insert(refNode, true); - } - - public void InsertBefore(TreeNode refNode) - { - this.Insert(refNode, false); - } - - private void Insert(TreeNode refNode, bool after) - { - Guard.ArgumentNotNull(() => refNode); - - if (refNode.Parent == null) - { - throw Error.Argument("refNode", "The reference node cannot be a root node and must be attached to the tree."); - } - - var refLinkedList = refNode.Parent._children; - var refNodeInternal = refLinkedList.Find(refNode); - - if (this.Parent != null) - { - var thisLinkedList = this.Parent._children; - thisLinkedList.Remove(this); - } - - if (after) - { - refLinkedList.AddAfter(refNodeInternal, this); - } - else - { - refLinkedList.AddBefore(refNodeInternal, this); - } - - this.Parent = refNode.Parent; - this.TraverseTree(x => x._depth = null); - } - - #endregion - - #region Select[...] - - public TreeNode SelectNode(Expression, bool>> predicate) - { - Guard.ArgumentNotNull(() => predicate); - - return this.FlattenNodes(predicate, false).FirstOrDefault(); - } - - /// - /// Selects all nodes (recursively) with match the given predicate, - /// but excluding self - /// - /// The predicate to match against - /// A readonly collection of node matches - public ICollection> SelectNodes(Expression, bool>> predicate) - { - Guard.ArgumentNotNull(() => predicate); - - var result = new List>(); - - var flattened = this.FlattenNodes(predicate, false); - result.AddRange(flattened); - - return result.AsReadOnly(); - } - - #endregion - - public bool RemoveNode(TreeNode node) - { - node.TraverseTree(x => x._depth = null); - return _children.Remove(node); - } - - - public void Clear() - { - _children.Clear(); - } - - public void Traverse(Action action) - { - action(Value); - foreach (var child in _children) - child.Traverse(action); - } - - public void TraverseTree(Action> action) - { - action(this); - foreach (var child in _children) - child.TraverseTree(action); - } - - public IEnumerable Flatten(bool includeSelf = true) - { - return this.Flatten(null, includeSelf); - } - - public IEnumerable Flatten(Expression> expression, bool includeSelf = true) - { - IEnumerable list; - if (includeSelf) - { - list = new[] { Value }; - } - else - { - list = Enumerable.Empty(); - } - - var result = list.Union(_children.SelectMany(x => x.Flatten())); - if (expression != null) - { - result = result.Where(expression.Compile()); - } - - return result; - } - - internal IEnumerable> FlattenNodes(bool includeSelf = true) - { - return this.FlattenNodes(null, includeSelf); - } - - internal IEnumerable> FlattenNodes(Expression, bool>> expression, bool includeSelf = true) - { - IEnumerable> list; - if (includeSelf) - { - list = new[] { this }; - } - else - { - list = Enumerable.Empty>(); - } - - var result = list.Union(_children.SelectMany(x => x.FlattenNodes())); - if (expression != null) - { - result = result.Where(expression.Compile()); - } - - return result; - } - - public TreeNode Find(T value) - { - //Guard.ArgumentNotNull(value, "value"); - - if (this.Value.Equals(value)) - { - return this; - } - - TreeNode item = null; - - foreach (var child in _children) { - item = child.Find(value); - if (item != null) - break; - } - - return item; - } - - public TreeNode Clone() - { - return Clone(true); - } - - public TreeNode Clone(bool deep) - { - T value = this.Value; - - if (value is ICloneable) - { - value = (T)((ICloneable)value).Clone(); - } - - var clone = new TreeNode(value); - if (deep) - { - clone.AppendChildrenOf(this); - } - return clone; - } - - object ICloneable.Clone() - { - return Clone(true); - } - - #endregion - } + private object GetPropValue(string name, object instance) + { + return FastProperty.GetProperty(instance.GetType(), name, PropertyCachingStrategy.Cached).GetValue(instance); + } + } } diff --git a/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs b/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs new file mode 100644 index 0000000000..8264db12e1 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs @@ -0,0 +1,767 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Newtonsoft.Json; +using SmartStore.Core.Infrastructure; + +namespace SmartStore.Collections +{ + public abstract class TreeNodeBase where T : TreeNodeBase + { + private T _parent; + private List _children = new List(); + private int? _depth = null; + private int _index = -1; + + protected object _id; + private IDictionary> _idNodeMap; + + protected IDictionary _metadata; + private readonly static ContextState> _contextState = new ContextState>("TreeNodeBase.ThreadMetadata"); + + public TreeNodeBase() + { + } + + #region Id + + public object Id + { + get + { + return _id; + } + set + { + _id = value; + + if (_parent != null) + { + var map = GetIdNodeMap(); + + if (_id != null && map.ContainsKey(_id)) + { + // Remove old id from map + map.Remove(_id); + } + + if (value != null) + { + map[value] = this; + } + } + } + } + + public T SelectNodeById(object id) + { + if (id == null || IsLeaf) + return null; + + var map = GetIdNodeMap(); + var node = (T)map?.Get(id); + + if (node != null && !this.IsAncestorOfOrSelf(node)) + { + // Found node is NOT a child of this node + return null; + } + + return node; + } + + private IDictionary> GetIdNodeMap() + { + var map = this.Root._idNodeMap; + if (map == null) + { + map = this.Root._idNodeMap = new Dictionary>(); + } + + return map; + } + + #endregion + + #region Metadata + + public IDictionary Metadata + { + get + { + return _metadata ?? (_metadata = new Dictionary()); + } + set + { + _metadata = value; + } + } + + public void SetMetadata(string key, object value) + { + Guard.NotEmpty(key, nameof(key)); + + Metadata[key] = value; + } + + public void SetThreadMetadata(string key, object value) + { + Guard.NotEmpty(key, nameof(key)); + + var state = _contextState.GetState(); + if (state == null) + { + state = new Dictionary(); + _contextState.SetState(state); + } + + state[GetContextKey(this, key)] = value; + } + + public TMetadata GetMetadata(string key, bool recursive = true) + { + Guard.NotEmpty(key, nameof(key)); + + object metadata; + + if (!recursive) + { + return TryGetMetadataForNode(this, key, out metadata) ? (TMetadata)metadata : default(TMetadata); + } + + // recursively search for the metadata value in current node and ancestors + var current = this; + while (!TryGetMetadataForNode(current, key, out metadata)) + { + current = current.Parent; + if (current == null) + break; + } + + if (metadata != null) + { + return (TMetadata)metadata; + } + + return default(TMetadata); + } + + private bool TryGetMetadataForNode(TreeNodeBase node, string key, out object metadata) + { + metadata = null; + + var state = _contextState.GetState(); + + if (state != null) + { + var contextKey = GetContextKey(node, key); + if (state.ContainsKey(contextKey)) + { + metadata = state[contextKey]; + return true; + } + } + + if (node._metadata != null && node._metadata.ContainsKey(key)) + { + metadata = node._metadata[key]; + return true; + } + + return false; + } + + private static string GetContextKey(TreeNodeBase node, string key) + { + return node.GetHashCode().ToString() + key; + } + + #endregion + + private List ChildrenInternal + { + get + { + if (_children == null) + { + _children = new List(); + } + return _children; + } + } + + private void AddChild(T node, bool clone, bool append = true) + { + var newNode = node; + if (clone) + { + newNode = node.Clone(true); + } + newNode.AttachTo((T)this, append ? (int?)null : 0); + } + + 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); + } + + if (index == null) + { + newParent.ChildrenInternal.Add((T)this); + _index = newParent.ChildrenInternal.Count - 1; + } + else + { + newParent.ChildrenInternal.Insert(index.Value, (T)this); + _index = index.Value; + FixIndexes(newParent._children, _index + 1, 1); + } + + _parent = newParent; + + 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) + { + // 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; + } + } + } + + [JsonIgnore] + public T Parent + { + get + { + return _parent; + } + } + + public T this[int i] + { + get + { + return _children?[i]; + } + } + + public IReadOnlyList Children + { + get + { + return ChildrenInternal; + } + } + + [JsonIgnore] + public IEnumerable LeafNodes + { + get + { + return _children != null + ? _children.Where(x => x.IsLeaf) + : Enumerable.Empty(); + } + } + + [JsonIgnore] + public IEnumerable NonLeafNodes + { + get + { + return _children != null + ? _children.Where(x => !x.IsLeaf) + : Enumerable.Empty(); + } + } + + + [JsonIgnore] + public T FirstChild + { + get + { + return _children?.FirstOrDefault(); + } + } + + [JsonIgnore] + public T LastChild + { + get + { + return _children?.LastOrDefault(); + } + } + + [JsonIgnore] + public bool IsLeaf + { + get + { + return _children == null || _children.Count == 0; + } + } + + [JsonIgnore] + public bool HasChildren + { + get + { + return _children == null || _children.Count > 0; + } + } + + [JsonIgnore] + public bool IsRoot + { + get + { + return _parent == null; + } + } + + [JsonIgnore] + public int Index + { + get + { + return _index; + } + } + + /// + /// Root starts with 0 + /// + [JsonIgnore] + public int Depth + { + get + { + if (!_depth.HasValue) + { + var node = this; + int depth = 0; + while (node != null && !node.IsRoot) + { + depth++; + node = node.Parent; + } + _depth = depth; + } + + return _depth.Value; + } + } + + [JsonIgnore] + public T Root + { + get + { + var root = this; + while (root._parent != null) + { + root = root._parent; + } + return (T)root; + } + } + + [JsonIgnore] + public T First + { + get + { + return _parent?._children?.FirstOrDefault(); + } + } + + [JsonIgnore] + public T Last + { + get + { + return _parent?._children?.LastOrDefault(); + } + } + + [JsonIgnore] + public T Next + { + get + { + return _parent?._children?.ElementAtOrDefault(_index + 1); + } + } + + [JsonIgnore] + public T Previous + { + get + { + return _parent?._children?.ElementAtOrDefault(_index - 1); + } + } + + public bool IsDescendantOf(T node) + { + var parent = _parent; + while (parent != null) + { + if (parent == node) + { + return true; + } + parent = parent._parent; + } + + return false; + } + + public bool IsDescendantOfOrSelf(T node) + { + if (node == (T)this) + return true; + + return IsDescendantOf(node); + } + + public bool IsAncestorOf(T node) + { + return node.IsDescendantOf((T)this); + } + + public bool IsAncestorOfOrSelf(T node) + { + if (node == (T)this) + return true; + + return node.IsDescendantOf((T)this); + } + + [JsonIgnore] + public IEnumerable Trail + { + get + { + var trail = new List(); + + var node = (T)this; + do + { + trail.Insert(0, node); + node = node._parent; + } while (node != null); + + return trail; + } + } + + /// + /// Gets the first element that matches the predicate by testing the node itself + /// and traversing up through its ancestors in the tree. + /// + /// predicate + /// The closest node + public T Closest(Expression> predicate) + { + Guard.NotNull(predicate, nameof(predicate)); + + var test = predicate.Compile(); + + if (test((T)this)) + { + return (T)this; + } + + var parent = _parent; + + while (parent != null) + { + if (test(parent)) + { + return parent; + } + parent = parent._parent; + } + + return null; + } + + public T Append(T value) + { + this.AddChild(value, false, true); + return value; + } + + public void AppendRange(IEnumerable values) + { + values.Each(x => Append(x)); + } + + public void AppendChildrenOf(T node) + { + if (node?._children != null) + { + node._children.Each(x => this.AddChild(x, true, true)); + } + } + + public T Prepend(T value) + { + this.AddChild(value, false, false); + return value; + } + + public void InsertAfter(int index) + { + var refNode = _children?.ElementAtOrDefault(index); + if (refNode != null) + { + InsertAfter(refNode); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public void InsertAfter(T refNode) + { + this.Insert(refNode, true); + } + + public void InsertBefore(int index) + { + var refNode = _children?.ElementAtOrDefault(index); + if (refNode != null) + { + InsertBefore(refNode); + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public void InsertBefore(T refNode) + { + this.Insert(refNode, false); + } + + private void Insert(T refNode, bool after) + { + Guard.NotNull(refNode, nameof(refNode)); + + var refParent = refNode._parent; + if (refParent == null) + { + throw Error.Argument("refNode", "The reference node cannot be a root node and must be attached to the tree."); + } + + AttachTo(refParent, refNode._index + (after ? 1 : 0)); + } + + public T SelectNode(Expression> predicate, bool includeSelf = false) + { + Guard.NotNull(predicate, nameof(predicate)); + + return this.FlattenNodes(predicate, includeSelf).FirstOrDefault(); + } + + /// + /// Selects all nodes (recursively) witch match the given predicate + /// + /// The predicate to match against + /// A readonly collection of node matches + public IEnumerable SelectNodes(Expression> predicate, bool includeSelf = false) + { + Guard.NotNull(predicate, nameof(predicate)); + + return this.FlattenNodes(predicate, includeSelf); + } + + private void FixIndexes(IList list, int startIndex, int summand = 1) + { + if (startIndex < 0 || startIndex >= list.Count) + return; + + for (var i = startIndex; i < list.Count; i++) + { + list[i]._index += summand; + } + } + + public void Remove(T node) + { + Guard.NotNull(node, nameof(node)); + + if (!node.IsRoot) + { + var list = node._parent?._children; + if (list.Remove(node)) + { + node.FixIdNodeMap(node._parent, null); + + FixIndexes(list, node._index, -1); + + node._index = -1; + node._parent = null; + node.Traverse(x => x._depth = null, true); + } + } + } + + public void Clear() + { + Traverse(x => x._depth = null, false); + if (_children != null) + { + _children.Clear(); + } + + FixIdNodeMap(_parent, null); + } + + public void Traverse(Action action, bool includeSelf = false) + { + Guard.NotNull(action, nameof(action)); + + if (includeSelf) + action((T)this); + + if (_children != null) + { + foreach (var child in _children) + child.Traverse(action, true); + } + } + + public void TraverseParents(Action action, bool includeSelf = false) + { + Guard.NotNull(action, nameof(action)); + + if (includeSelf) + action((T)this); + + var parent = _parent; + + while (parent != null) + { + action(parent); + parent = parent._parent; + } + } + + public IEnumerable FlattenNodes(bool includeSelf = true) + { + return this.FlattenNodes(null, includeSelf); + } + + protected IEnumerable FlattenNodes(Expression> predicate, bool includeSelf = true) + { + IEnumerable list; + if (includeSelf) + { + list = new[] { (T)this }; + } + else + { + list = Enumerable.Empty(); + } + + if (_children == null) + return list; + + var result = list.Union(_children.SelectMany(x => x.FlattenNodes())); + if (predicate != null) + { + result = result.Where(predicate.Compile()); + } + + return result; + } + + public T Clone() + { + return Clone(true); + } + + public virtual T Clone(bool deep) + { + var clone = CreateInstance(); + if (deep) + { + clone.AppendChildrenOf((T)this); + } + return clone; + } + + protected abstract T CreateInstance(); + } +} 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 79a4362f30..85419958d3 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs @@ -13,7 +13,7 @@ public class FastActivator public FastActivator(ConstructorInfo constructorInfo) { - Guard.ArgumentNotNull(() => constructorInfo); + Guard.NotNull(constructorInfo, nameof(constructorInfo)); Constructor = constructorInfo; Invoker = MakeFastInvoker(constructorInfo); @@ -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(); @@ -129,7 +128,7 @@ public static T CreateInstance(params object[] args) /// A reference to the newly created object. public static object CreateInstance(Type type, params object[] args) { - Guard.ArgumentNotNull(type, "type"); + Guard.NotNull(type, nameof(type)); if (args == null || args.Length == 0) { 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 068b84f008..7aafa809cd 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs @@ -56,7 +56,7 @@ public class FastProperty [SuppressMessage("ReSharper", "VirtualMemberCallInContructor")] public FastProperty(PropertyInfo property) { - Guard.ArgumentNotNull(() => property); + Guard.NotNull(property, nameof(property)); Property = property; Name = property.Name; @@ -146,7 +146,7 @@ public void SetValue(object instance, object value) /// the instance to extract property accessors for. /// A cached array of all public property getters from the underlying type of target instance. /// - public static IReadOnlyDictionary GetProperties(object instance, PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) + public static IReadOnlyDictionary GetProperties(object instance) { return GetProperties(instance.GetType()); } @@ -221,12 +221,10 @@ public static FastProperty GetProperty( string propertyName, PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) { - Guard.ArgumentNotNull(() => type); - Guard.ArgumentNotEmpty(() => propertyName); + Guard.NotNull(type, nameof(type)); + Guard.NotEmpty(propertyName, nameof(propertyName)); - FastProperty fastProperty = null; - - if (TryGetCachedProperty(type, propertyName, cachingStrategy == PropertyCachingStrategy.EagerCached, out fastProperty)) + if (TryGetCachedProperty(type, propertyName, cachingStrategy == PropertyCachingStrategy.EagerCached, out var fastProperty)) { return fastProperty; } @@ -234,7 +232,7 @@ public static FastProperty GetProperty( var key = new PropertyKey(type, propertyName); if (!_singlePropertiesCache.TryGetValue(key, out fastProperty)) { - var pi = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase); + var pi = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase); if (pi != null) { fastProperty = CreateInstance(pi); @@ -252,11 +250,9 @@ public static FastProperty GetProperty( PropertyInfo propertyInfo, PropertyCachingStrategy cachingStrategy = PropertyCachingStrategy.Cached) { - Guard.ArgumentNotNull(() => propertyInfo); - - FastProperty fastProperty = null; + Guard.NotNull(propertyInfo, nameof(propertyInfo)); - if (TryGetCachedProperty(propertyInfo.ReflectedType, propertyInfo.Name, cachingStrategy == PropertyCachingStrategy.EagerCached, out fastProperty)) + if (TryGetCachedProperty(propertyInfo.ReflectedType, propertyInfo.Name, cachingStrategy == PropertyCachingStrategy.EagerCached, out var fastProperty)) { return fastProperty; } @@ -446,27 +442,34 @@ public static Action MakeFastPropertySetter(PropertyInfo propert /// instance, then a copy /// is returned. /// + /// Key selector + /// When true, converts all nested objects to dictionaries also /// /// The implementation of FastProperty will cache the property accessors per-type. This is /// faster when the the same type is used multiple times with ObjectToDictionary. /// - public static IDictionary ObjectToDictionary(object value, Func keySelector = null) + public static IDictionary ObjectToDictionary(object value, Func keySelector = null, bool deep = false) { - var dictionary = value as IDictionary; - if (dictionary != null) + if (value is IDictionary dictionary) { return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); } - keySelector = keySelector ?? new Func(key => key); - dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (value != null) { + keySelector = keySelector ?? new Func(key => key); + foreach (var prop in GetProperties(value).Values) { - dictionary[keySelector(prop.Name)] = prop.GetValue(value); + var propValue = prop.GetValue(value); + if (deep && propValue != null && prop.Property.PropertyType.IsPlainObjectType()) + { + propValue = ObjectToDictionary(propValue, deep: true); + } + + dictionary[keySelector(prop.Name)] = propValue; } } @@ -536,12 +539,11 @@ protected static IDictionary GetVisibleProperties( ConcurrentDictionary> allPropertiesCache, ConcurrentDictionary> visiblePropertiesCache) { - IDictionary result; - if (visiblePropertiesCache.TryGetValue(type, out result)) + if (visiblePropertiesCache.TryGetValue(type, out var result)) { 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); @@ -617,18 +619,17 @@ protected static IDictionary GetProperties( // part of the sequence of properties returned by this method. type = Nullable.GetUnderlyingType(type) ?? type; - IDictionary fastProperties; - if (!cache.TryGetValue(type, out fastProperties)) + return cache.GetOrAdd(type, Get); + + IDictionary Get(Type t) { - var candidates = GetCandidateProperties(type); - fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); - cache.TryAdd(type, fastProperties); + var candidates = GetCandidateProperties(t); + var fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + return fastProperties; } - - return fastProperties; } - private static IEnumerable GetCandidateProperties(Type type) + internal static IEnumerable GetCandidateProperties(Type type) { // We avoid loading indexed properties using the Where statement. var properties = type.GetRuntimeProperties().Where(IsCandidateProperty); diff --git a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs index 1049481e35..17d44dbbac 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs @@ -37,10 +37,17 @@ using System.Dynamic; using System.Reflection; using System.Collections; +using SmartStore.Utilities; namespace SmartStore.ComponentModel { - /// + public enum MemberOptMethod + { + Allow, + Disallow + } + + /// /// Class that provides extensible properties and methods to an /// existing object when cast to dynamic. This /// dynamic object stores 'extra' properties in a dictionary or @@ -71,22 +78,33 @@ public class HybridExpando : DynamicObject, IDictionary /// private Type _instanceType; - /// - /// String Dictionary that contains the extra dynamic values - /// stored on this object/instance - /// - /// Using PropertyBag to support XML Serialization of the dictionary - public PropertyBag Properties = new PropertyBag(); + /// + /// Adjusted property list for the wrapped instance type after white/black-list members has been applied. + /// + private IDictionary _instanceProps; + private readonly static IDictionary EmptyProps = new Dictionary(); - /// - /// This constructor just works off the internal dictionary and any - /// public properties of this object. - /// - /// Note you can subclass Expando. - /// - public HybridExpando() + /// + /// String Dictionary that contains the extra dynamic values + /// stored on this object/instance + /// + /// Using PropertyBag to support XML Serialization of the dictionary + public PropertyBag Properties = new PropertyBag(); + + private readonly HashSet _optMembers; + private readonly MemberOptMethod _optMethod; + + private readonly bool _returnNullWhenFalsy; + + /// + /// This constructor just works off the internal dictionary and any + /// public properties of this object. + /// + /// Note you can subclass HybridExpando. + /// + public HybridExpando(bool returnNullWhenFalsy = false) { - Initialize(this); + _returnNullWhenFalsy = returnNullWhenFalsy; } /// @@ -97,16 +115,42 @@ public HybridExpando() /// check native properties and only check the Dictionary! /// /// - public HybridExpando(object instance) + public HybridExpando(object instance, bool returnNullWhenFalsy = false) { - Initialize(instance); + Guard.NotNull(instance, nameof(instance)); + + _returnNullWhenFalsy = returnNullWhenFalsy; + Initialize(instance); } - - protected void Initialize(object instance) + + /// + /// Allows passing in an existing instance variable to 'extend' + /// along with a list of member names to allow or disallow. + /// + /// + public HybridExpando(object instance, IEnumerable optMembers, MemberOptMethod optMethod, bool returnNullWhenFalsy = false) + { + Guard.NotNull(instance, nameof(instance)); + + _returnNullWhenFalsy = returnNullWhenFalsy; + Initialize(instance); + + _optMethod = optMethod; + + if (optMembers is HashSet h) + { + _optMembers = h; + } + else + { + _optMembers = new HashSet(optMembers); + } + } + + protected void Initialize(object instance) { _instance = instance; - if (instance != null) - _instanceType = instance.GetType(); + _instanceType = instance?.GetType(); } protected object WrappedObject @@ -116,19 +160,29 @@ protected object WrappedObject public override IEnumerable GetDynamicMemberNames() { - foreach (var prop in this.GetProperties(false)) - yield return prop.Key; - } + foreach (var kvp in this.Properties.Keys) + { + yield return kvp; + } + + foreach (var kvp in GetInstanceProperties()) + { + if (!this.Properties.ContainsKey(kvp.Key)) + { + yield return kvp.Key; + } + } + } - /// - /// Try to retrieve a member by name first from instance properties - /// followed by the collection entries. - /// - /// - /// - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) + /// + /// Try to retrieve a member by name first from instance properties + /// followed by the collection entries. + /// + /// + /// + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) { return TryGetMemberCore(binder.Name, out result); } @@ -136,27 +190,33 @@ public override bool TryGetMember(GetMemberBinder binder, out object result) protected virtual bool TryGetMemberCore(string name, out object result) { result = null; + bool exists = false; // first check the Properties collection for member - if (Properties.Keys.Contains(name)) + if (Properties.ContainsKey(name)) { result = Properties[name]; - return true; + exists = true; } // Next check for public properties via Reflection - if (_instance != null) + if (!exists && _instance != null) { try { - return GetProperty(_instance, name, out result); + exists = GetProperty(_instance, name, out result); } catch { } } + // Falsy check + if (_returnNullWhenFalsy && result != null && !CommonHelper.IsTruthy(result)) + { + result = null; + } + // failed to retrieve a property - result = null; - return false; + return exists; } @@ -174,7 +234,14 @@ 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; + return true; + } + + // Check to see if there's a native property to set if (_instance != null) { try @@ -198,6 +265,19 @@ protected virtual bool TrySetMemberCore(string name, object value) /// /// /// + public void Override(string name, object value = null) + { + Guard.NotEmpty(name, nameof(name)); + Properties[name] = value; + } + + /// + /// Dynamic invocation method. Currently allows only for Reflection based + /// operation (no ability to add methods dynamically). + /// + /// + /// + /// /// public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { @@ -226,8 +306,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o /// protected bool GetProperty(object instance, string name, out object result) { - var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, PropertyCachingStrategy.EagerCached) : null; - if (fastProp != null) + if (GetInstanceProperties().TryGetValue(name, out var fastProp)) { result = fastProp.GetValue(instance ?? this); return true; @@ -246,12 +325,11 @@ protected bool GetProperty(object instance, string name, out object result) /// protected bool SetProperty(object instance, string name, object value) { - var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, PropertyCachingStrategy.EagerCached) : null; - if (fastProp != null) + if (GetInstanceProperties().TryGetValue(name, out var fastProp)) { fastProp.SetValue(instance ?? this, value); return true; - } + } return false; } @@ -267,7 +345,7 @@ protected bool SetProperty(object instance, string name, object value) protected bool InvokeMethod(object instance, string name, object[] args, out object result) { // Look at the instanceType - var mi = _instanceType != null ? _instanceType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public) : null; + var mi = _instanceType?.GetMethod(name, BindingFlags.Instance | BindingFlags.Public); if (mi != null) { result = mi.Invoke(instance ?? this, args); @@ -302,8 +380,7 @@ public object this[string key] { get { - object result = null; - if (!TryGetMemberCore(key, out result)) + if (!TryGetMemberCore(key, out var result)) { throw new KeyNotFoundException(); } @@ -324,14 +401,14 @@ public object this[string key] /// public IEnumerable> GetProperties(bool includeInstanceProperties = false) { - foreach (var key in this.Properties.Keys) + foreach (var kvp in this.Properties) { - yield return new KeyValuePair(key, this.Properties[key]); + yield return kvp; } - if (includeInstanceProperties && _instance != null) + if (includeInstanceProperties) { - foreach (var prop in FastProperty.GetProperties(_instance).Values) + foreach (var prop in GetInstanceProperties().Values) { if (!this.Properties.ContainsKey(prop.Name)) { @@ -339,9 +416,32 @@ public IEnumerable> GetProperties(bool includeInsta } } } - } + private IDictionary GetInstanceProperties() + { + if (_instance == null) + { + return EmptyProps; + } + + if (_instanceProps == null) + { + var props = FastProperty.GetProperties(_instance) as IDictionary; + + if (_optMembers != null) + { + props = props + .Where(x => _optMethod == MemberOptMethod.Allow ? _optMembers.Contains(x.Key) : !_optMembers.Contains(x.Key)) + .ToDictionary(x => x.Key, x => x.Value); + } + + _instanceProps = props; + } + + return _instanceProps; + } + /// /// Checks whether a property exists in the Property collection /// or as a property on the instance @@ -367,10 +467,10 @@ public bool Contains(string propertyName, bool includeInstanceProperties = false return true; } - if (includeInstanceProperties && _instance != null) + if (includeInstanceProperties) { - return FastProperty.GetProperties(_instance).ContainsKey(propertyName); - } + return GetInstanceProperties().ContainsKey(propertyName); + } return false; } @@ -397,15 +497,9 @@ int ICollection>.Count { get { - var count = Properties.Count; - if (_instanceType != null) - { - count += FastProperty.GetProperties(_instanceType).Count; + return GetDynamicMemberNames().Count(); } - - return count; } - } bool ICollection>.IsReadOnly { diff --git a/src/Libraries/SmartStore.Core/ComponentModel/MiniMapper.cs b/src/Libraries/SmartStore.Core/ComponentModel/MiniMapper.cs new file mode 100644 index 0000000000..40e6412f8f --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/MiniMapper.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using System.Globalization; +using SmartStore.Utilities; + +namespace SmartStore.ComponentModel +{ + /// + /// A very simple object mapper utility which tries to map properties of the same name between two objects. + /// If matched properties has different types, the mapper tries to convert them. + /// If conversion fails, the property is skipped (no exception is thrown). + /// MiniMapper cannot handle sequence and predefined types. + /// + public static class MiniMapper + { + public static TTo Map(TFrom from, CultureInfo culture = null) + where TFrom : class + where TTo : class + { + var to = Map(from, typeof(TTo), culture); + return (TTo)to; + } + + //public static void Map(TFrom from, TTo to, CultureInfo culture = null) + // where TFrom : class + // where TTo : class + //{ + // Map((object)from, (object)to, culture); + //} + + public static object Map(TFrom from, Type toType, CultureInfo culture = null) + where TFrom : class + { + Guard.NotNull(toType, nameof(toType)); + Guard.HasDefaultConstructor(toType); + + var target = Activator.CreateInstance(toType); + + Map(from, target, culture); + return target; + } + + public static void Map(TFrom from, TTo to, CultureInfo culture = null) + where TFrom : class + where TTo : class + { + Guard.NotNull(from, nameof(from)); + Guard.NotNull(to, nameof(to)); + + if (object.ReferenceEquals(from, to)) + { + // Cannot map the same instance + return; + } + + var fromType = from.GetType(); + var toType = to.GetType(); + + ValidateType(fromType); + ValidateType(toType); + + if (culture == null) + { + culture = CultureInfo.CurrentCulture; + } + + var toProps = GetFastPropertiesFor(toType).ToArray(); + + foreach (var toProp in toProps) + { + var fromProp = FastProperty.GetProperty(fromType, toProp.Name, PropertyCachingStrategy.Uncached); + if (fromProp == null) + { + continue; + } + + object value = null; + try + { + // Get the value from source instance and try to convert it to target props type + value = fromProp.GetValue(from).Convert(toProp.Property.PropertyType, culture); + + // Set it + toProp.SetValue(to, value); + } + catch { } + } + } + + private static IEnumerable GetFastPropertiesFor(Type type) + { + return FastProperty.GetCandidateProperties(type) + .Select(pi => FastProperty.GetProperty(pi, PropertyCachingStrategy.Uncached)) + .Where(pi => pi.IsPublicSettable); + } + + private static void ValidateType(Type type) + { + if (type.IsPredefinedType()) + { + throw new InvalidOperationException("Mapping from or to predefined types is not possible. Type was: {0}".FormatInvariant(type.FullName)); + } + + if (type.IsSequenceType()) + { + throw new InvalidOperationException("Mapping from or to sequence types is not possible. Type was: {0}".FormatInvariant(type.FullName)); + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs b/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs index bec72dcea5..328ef76ce0 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs @@ -288,8 +288,7 @@ public void ReadXml(System.Xml.XmlReader reader) /// XML String or Null if it fails public string ToXml() { - string xml = null; - SerializationUtils.SerializeObject(this, out xml); + SerializationUtils.SerializeObject(this, out var xml); return xml; } @@ -306,8 +305,7 @@ public bool FromXml(string xml) if (string.IsNullOrEmpty(xml)) return true; - var result = SerializationUtils.DeSerializeObject(xml, this.GetType()) as PropertyBag; - if (result != null) + if (SerializationUtils.DeSerializeObject(xml, this.GetType()) is PropertyBag result) { foreach (var item in result) { diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/DictionaryConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/DictionaryConverter.cs new file mode 100644 index 0000000000..a7e3c60887 --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/DictionaryConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Globalization; +using System.Web.Routing; +using SmartStore.Utilities; + +namespace SmartStore.ComponentModel +{ + public class DictionaryTypeConverter : TypeConverterBase where T : IDictionary + { + public DictionaryTypeConverter() + : base(typeof(object)) + { + } + + public override bool CanConvertFrom(Type type) + { + return !type.IsPredefinedType() && type.IsClass; + } + + public override bool CanConvertTo(Type type) + { + return !type.IsPredefinedType() && DictionaryConverter.CanCreateType(type); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + // Obj > Dict + var dict = CommonHelper.ObjectToDictionary(value); + var to = typeof(T); + + if (to == typeof(RouteValueDictionary)) + { + return new RouteValueDictionary(dict); + } + if (to == typeof(Dictionary)) + { + return (Dictionary)dict; + } + else if (to == typeof(ExpandoObject)) + { + var expando = new ExpandoObject(); + expando.Merge(dict); + return expando; + } + else + { + return dict; + } + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + // Dict > Obj + if (value is IDictionary dict) + { + return DictionaryConverter.CreateAndPopulate(to, dict, out var problems); + } + + return base.ConvertTo(culture, format, value, to); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EmailAddressConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EmailAddressConverter.cs new file mode 100644 index 0000000000..5a2c45755b --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EmailAddressConverter.cs @@ -0,0 +1,43 @@ +using System; +using System.Globalization; +using SmartStore.Core.Email; + +namespace SmartStore.ComponentModel +{ + public class EmailAddressConverter : TypeConverterBase + { + public EmailAddressConverter() : base(typeof(object)) + { + } + + public override bool CanConvertFrom(Type type) + { + return type == typeof(string); + } + + public override bool CanConvertTo(Type type) + { + return type == typeof(string); + } + + public override object ConvertFrom(CultureInfo culture, object value) + { + if (value is string str && str.HasValue()) + { + return new EmailAddress(str); + } + + return base.ConvertFrom(culture, value); + } + + public override object ConvertTo(CultureInfo culture, string format, object value, Type to) + { + if (to == typeof(string) && value is EmailAddress address) + { + return address.ToString(); + } + + return base.ConvertTo(culture, format, value, to); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs index b4618b78f0..ae60b58488 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs @@ -122,11 +122,10 @@ public override object ConvertTo(CultureInfo culture, string format, object valu if (to == typeof(string)) { string result = string.Empty; - var enumerable = value as IEnumerable; - if (enumerable != null) + 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)); @@ -144,33 +143,11 @@ public override object ConvertTo(CultureInfo culture, string format, object valu protected virtual string[] GetStringArray(string input) { - if (!String.IsNullOrEmpty(input)) - { - var splitBy = "|"; - - if (input.IndexOf(splitBy) < 0) - { - if (input.IndexOf(';') > -1) - { - splitBy = ";"; - } - else if (input.IndexOf(',') > -1) - { - splitBy = ","; - } - else if (input.IndexOf(Environment.NewLine) > -1) - { - splitBy = Environment.NewLine; - } - } + var result = input.SplitSafe(null); - var result = input.Split(new string[] { splitBy }, StringSplitOptions.RemoveEmptyEntries); - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - Array.ForEach(result, s => s.Trim()); - return result; - } + Array.ForEach(result, s => s.Trim()); - return new string[0]; + return result; } } } diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs index d11c53409f..8488e2aa3e 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/NullableConverter.cs @@ -68,7 +68,7 @@ public override bool CanConvertFrom(Type type) public override bool CanConvertTo(Type type) { - Console.WriteLine("NullableConverter can convert to {0}: {1}".FormatInvariant(type.Name, UnderlyingTypeConverter.CanConvertTo(type))); + //Console.WriteLine("NullableConverter can convert to {0}: {1}".FormatInvariant(type.Name, UnderlyingTypeConverter.CanConvertTo(type))); if (type == this.UnderlyingType) { diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs index b79c9a5f2f..54df72e96b 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/ShippingOptionConverter.cs @@ -32,10 +32,9 @@ public override bool CanConvertTo(Type type) public override object ConvertFrom(CultureInfo culture, object value) { - if (value is string) + if (value is string str) { object result = null; - string str = value as string; if (!String.IsNullOrEmpty(str)) { try diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs index 958cd9274c..24c7c382cd 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterBase.cs @@ -11,7 +11,7 @@ public abstract class TypeConverterBase : ITypeConverter protected TypeConverterBase(Type type) { - Guard.ArgumentNotNull(() => type); + Guard.NotNull(type, nameof(type)); _type = type; _systemConverter = new Lazy(() => TypeDescriptor.GetConverter(type), true); diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs index 4f25272dd8..ee6c50e474 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs @@ -2,8 +2,11 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; +using System.Dynamic; +using System.Web.Routing; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Email; namespace SmartStore.ComponentModel { @@ -33,6 +36,14 @@ private static void CreateDefaultConverters() _typeConverters.TryAdd(typeof(IList), converter); _typeConverters.TryAdd(typeof(List), converter); _typeConverters.TryAdd(typeof(ProductBundleItemOrderData), new ProductBundleDataConverter(false)); + + converter = new DictionaryTypeConverter>(); + _typeConverters.TryAdd(typeof(IDictionary), converter); + _typeConverters.TryAdd(typeof(Dictionary), converter); + _typeConverters.TryAdd(typeof(RouteValueDictionary), new DictionaryTypeConverter()); + _typeConverters.TryAdd(typeof(ExpandoObject), new DictionaryTypeConverter()); + + _typeConverters.TryAdd(typeof(EmailAddress), new EmailAddressConverter()); } public static void RegisterConverter(ITypeConverter typeConverter) @@ -42,8 +53,8 @@ public static void RegisterConverter(ITypeConverter typeConverter) public static void RegisterConverter(Type type, ITypeConverter typeConverter) { - Guard.ArgumentNotNull(() => type); - Guard.ArgumentNotNull(() => typeConverter); + Guard.NotNull(type, nameof(type)); + Guard.NotNull(typeConverter, nameof(typeConverter)); _typeConverters.TryAdd(type, typeConverter); } @@ -55,10 +66,9 @@ public static ITypeConverter RemoveConverter(ITypeConverter typeConverter) public static ITypeConverter RemoveConverter(Type type) { - Guard.ArgumentNotNull(() => type); + Guard.NotNull(type, nameof(type)); - ITypeConverter converter = null; - _typeConverters.TryRemove(type, out converter); + _typeConverters.TryRemove(type, out var converter); return converter; } @@ -69,17 +79,17 @@ public static ITypeConverter GetConverter() public static ITypeConverter GetConverter(object component) { - Guard.ArgumentNotNull(() => component); + Guard.NotNull(component, nameof(component)); return GetConverter(component.GetType()); } public static ITypeConverter GetConverter(Type type) { - Guard.ArgumentNotNull(() => type); + if (type == null) + throw new ArgumentNullException(nameof(type)); - ITypeConverter converter; - if (_typeConverters.TryGetValue(type, out converter)) + if (_typeConverters.TryGetValue(type, out var converter)) { return converter; } diff --git a/src/Libraries/SmartStore.Core/Configuration/ISettingService.cs b/src/Libraries/SmartStore.Core/Configuration/ISettingService.cs index 3339ebd3c2..2f5f65575c 100644 --- a/src/Libraries/SmartStore.Core/Configuration/ISettingService.cs +++ b/src/Libraries/SmartStore.Core/Configuration/ISettingService.cs @@ -17,7 +17,7 @@ public partial interface ISettingService /// /// Specifies whether the cache should be evicted completely on batch disposal /// A disposable unit of work - IDisposable BeginBatch(bool clearCache = true); + IDisposable BeginScope(bool clearCache = true); /// /// Gets a value indicating whether settings have changed during a request, making cache eviction necessary. @@ -71,15 +71,22 @@ bool SettingExists(T settings, /// Store identifier for which settigns should be loaded T LoadSetting(int storeId = 0) where T : ISettings, new(); - /// - /// Set setting value - /// - /// Type - /// Key - /// Value + /// + /// Load settings + /// + /// Setting class type + /// Store identifier for which settigns should be loaded + ISettings LoadSetting(Type settingType, int storeId = 0); + + /// + /// Set setting value + /// + /// Type + /// Key + /// Value /// Store identifier - /// A value indicating whether to clear cache after setting update - void SetSetting(string key, T value, int storeId = 0, bool clearCache = true); + /// A value indicating whether to clear cache after setting update + void SetSetting(string key, T value, int storeId = 0, bool clearCache = true); /// /// Save settings object @@ -89,6 +96,13 @@ bool SettingExists(T settings, /// Store identifier void SaveSetting(T settings, int storeId = 0) where T : ISettings, new(); + /// + /// Save settings object + /// + /// Setting instance + /// Store identifier + void SaveSetting(ISettings settings, int storeId = 0); + /// /// Save settings object /// 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/DataProviderFactory.cs b/src/Libraries/SmartStore.Core/Data/DataProviderFactory.cs index e807fe9447..34d423474f 100644 --- a/src/Libraries/SmartStore.Core/Data/DataProviderFactory.cs +++ b/src/Libraries/SmartStore.Core/Data/DataProviderFactory.cs @@ -6,7 +6,7 @@ public abstract class DataProviderFactory { protected DataProviderFactory(DataSettings settings) { - Guard.ArgumentNotNull(() => settings); + Guard.NotNull(settings, nameof(settings)); this.Settings = settings; } diff --git a/src/Libraries/SmartStore.Core/Data/DataSettings.cs b/src/Libraries/SmartStore.Core/Data/DataSettings.cs index 8743fce97f..146cc96bcb 100644 --- a/src/Libraries/SmartStore.Core/Data/DataSettings.cs +++ b/src/Libraries/SmartStore.Core/Data/DataSettings.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Data.SqlClient; +using System.Data.SqlServerCe; using System.IO; using System.Linq; using System.Threading; @@ -9,8 +11,7 @@ using SmartStore.Utilities.Threading; namespace SmartStore.Core.Data -{ - +{ public partial class DataSettings { private static readonly ReaderWriterLockSlim s_rwLock = new ReaderWriterLockSlim(); @@ -31,7 +32,7 @@ private DataSettings() public static void SetDefaultFactory(Func factory) { - Guard.ArgumentNotNull(() => factory); + Guard.NotNull(factory, nameof(factory)); lock (s_rwLock.GetWriteLock()) { @@ -91,12 +92,21 @@ public static void Reload() public static void Delete() { - using (s_rwLock.GetWriteLock()) + if (s_current?.TenantPath != null) { - string filePath = Path.Combine(CommonHelper.MapPath("~/App_Data/"), FILENAME); - File.Delete(filePath); - s_current = null; - s_installed = null; + using (s_rwLock.GetWriteLock()) + { + string filePath = Path.Combine(CommonHelper.MapPath(s_current.TenantPath), FILENAME); + try + { + File.Delete(filePath); + } + finally + { + s_current = null; + s_installed = null; + } + } } } @@ -104,6 +114,18 @@ public static void Delete() #region Instance members + public string TenantName + { + get; + private set; + } + + public string TenantPath + { + get; + private set; + } + public Version AppVersion { get; @@ -154,7 +176,13 @@ public string DataConnectionString set; } - public IDictionary RawDataSettings + public string DataConnectionType + { + get; + set; + } + + public IDictionary RawDataSettings { get; private set; @@ -169,11 +197,16 @@ public virtual bool Load() { using (s_rwLock.GetWriteLock()) { - string filePath = Path.Combine(CommonHelper.MapPath("~/App_Data/"), FILENAME); - this.Reset(); - if (File.Exists(filePath)) + var curTenant = ResolveTenant(); + var tenantPath = "~/App_Data/Tenants/" + curTenant; + string filePath = Path.Combine(CommonHelper.MapPath(tenantPath), FILENAME); + + this.TenantName = curTenant; + this.TenantPath = tenantPath; + + if (File.Exists(filePath) && !s_TestMode) { string text = File.ReadAllText(filePath); var settings = ParseSettings(text); @@ -187,6 +220,9 @@ public virtual bool Load() if (settings.ContainsKey("DataProvider")) { this.DataProvider = settings["DataProvider"]; + this.DataConnectionType = this.IsSqlServer + ? typeof(SqlConnection).AssemblyQualifiedName + : typeof(SqlCeConnection).AssemblyQualifiedName; } if (settings.ContainsKey("DataConnectionString")) { @@ -206,9 +242,13 @@ public void Reset() using (s_rwLock.GetWriteLock()) { this.RawDataSettings.Clear(); + this.TenantName = null; + this.TenantPath = null; this.AppVersion = null; this.DataProvider = null; this.DataConnectionString = null; + this.DataConnectionType = null; + s_installed = null; } } @@ -220,7 +260,7 @@ public virtual bool Save() using (s_rwLock.GetWriteLock()) { - string filePath = Path.Combine(CommonHelper.MapPath("~/App_Data/"), FILENAME); + string filePath = Path.Combine(CommonHelper.MapPath(this.TenantPath), FILENAME); if (!File.Exists(filePath)) { using (File.Create(filePath)) @@ -240,6 +280,42 @@ public virtual bool Save() #region Instance helpers + protected virtual string ResolveTenant() + { + var tenantsBaseDir = CommonHelper.MapPath("~/App_Data/Tenants"); + var curTenantFile = Path.Combine(tenantsBaseDir, "current.txt"); + + string curTenant = null; + + if (File.Exists(curTenantFile)) + { + curTenant = File.ReadAllText(curTenantFile).EmptyNull().Trim().NullEmpty(); + if (curTenant != curTenant.EmptyNull().ToValidPath()) + { + // File contains invalid path string + curTenant = null; + } + + if (curTenant != null && !Directory.Exists(Path.Combine(tenantsBaseDir, curTenant))) + { + // Specified Tenant directory does not exist + curTenant = null; + } + } + + curTenant = curTenant ?? "Default"; + + var tenantPath = Path.Combine(tenantsBaseDir, curTenant); + + if (curTenant.IsCaseInsensitiveEqual("Default") && !Directory.Exists(tenantPath)) + { + // The Default tenant dir should be created if it doesn't exist + Directory.CreateDirectory(tenantPath); + } + + return curTenant.TrimEnd('/'); + } + protected virtual IDictionary ParseSettings(string text) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); 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/DbSaveHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/DbSaveHook.cs new file mode 100644 index 0000000000..09b74716ac --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/DbSaveHook.cs @@ -0,0 +1,79 @@ +using System; + +namespace SmartStore.Core.Data.Hooks +{ + public abstract class DbSaveHook : IDbSaveHook where TEntity : class + { + public virtual void OnBeforeSave(IHookedEntity entry) + { + var entity = entry.Entity as TEntity; + switch (entry.InitialState) + { + case EntityState.Added: + OnInserting(entity, entry); + break; + case EntityState.Modified: + OnUpdating(entity, entry); + break; + case EntityState.Deleted: + OnDeleting(entity, entry); + break; + } + } + + protected virtual void OnInserting(TEntity entity, IHookedEntity entry) + { + throw new NotImplementedException(); + } + + protected virtual void OnUpdating(TEntity entity, IHookedEntity entry) + { + throw new NotImplementedException(); + } + + protected virtual void OnDeleting(TEntity entity, IHookedEntity entry) + { + throw new NotImplementedException(); + } + + public virtual void OnBeforeSaveCompleted() + { + } + + public virtual void OnAfterSave(IHookedEntity entry) + { + var entity = entry.Entity as TEntity; + switch (entry.InitialState) + { + case EntityState.Added: + OnInserted(entity, entry); + break; + case EntityState.Modified: + OnUpdated(entity, entry); + break; + case EntityState.Deleted: + OnDeleted(entity, entry); + break; + } + } + + protected virtual void OnInserted(TEntity entity, IHookedEntity entry) + { + throw new NotImplementedException(); + } + + protected virtual void OnUpdated(TEntity entity, IHookedEntity entry) + { + throw new NotImplementedException(); + } + + protected virtual void OnDeleted(TEntity entity, IHookedEntity entry) + { + throw new NotImplementedException(); + } + + public virtual void OnAfterSaveCompleted() + { + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs b/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs new file mode 100644 index 0000000000..39126f48f8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Logging; + +namespace SmartStore.Core.Data.Hooks +{ + public class DefaultDbHookHandler : IDbHookHandler + { + private readonly IList> _saveHooks; + + private readonly Multimap _hooksRequestCache = new Multimap(); + + // Prevents repetitive hooking of the same entity/state/[pre|post] combination within a single request + private readonly HashSet _hookedEntities = new HashSet(); + + private static HashSet _importantSaveHookTypes; + private readonly static object _lock = new object(); + + // Contains all IDbHook/EntityType/State/Stage combinations in which + // the implementor threw either NotImplementedException or NotSupportedException. + // This boosts performance because these VOID combinations are not processed again + // and frees us mostly from the obligation always to detect changes. + private readonly static HashSet _voidHooks = new HashSet(); + + public DefaultDbHookHandler(IEnumerable> hooks) + { + _saveHooks = hooks.Where(x => x.Metadata.IsLoadHook == false).ToList(); + } + + public ILogger Logger + { + get; + set; + } = NullLogger.Instance; + + public bool HasImportantSaveHooks() + { + if (_importantSaveHookTypes == null) + { + lock (_lock) + { + if (_importantSaveHookTypes == null) + { + _importantSaveHookTypes = new HashSet(); + _importantSaveHookTypes.AddRange(_saveHooks.Where(x => x.Metadata.Important).Select(x => x.Metadata.ImplType)); + } + } + } + + return _importantSaveHookTypes.Any(); + } + + public IEnumerable TriggerPreSaveHooks(IEnumerable entries, bool importantHooksOnly, out bool anyStateChanged) + { + Guard.NotNull(entries, nameof(entries)); + + anyStateChanged = false; + + var processedHooks = new HashSet(); + + if (!entries.Any() || !_saveHooks.Any() || (importantHooksOnly && !this.HasImportantSaveHooks())) + return processedHooks; + + foreach (var entry in entries) + { + var e = entry; // Prevents access to modified closure + + if (HandledAlready(e, HookStage.PreSave)) + { + // Prevent repetitive hooking of the same entity/state/pre combination within a single request + continue; + } + + var hooks = GetSaveHookInstancesFor(e, HookStage.PreSave, importantHooksOnly); + + foreach (var hook in hooks) + { + // call hook + try + { + //Logger.DebugFormat("PRE save hook: {0}, State: {1}, Entity: {2}", hook.GetType().Name, e.InitialState, e.Entity.GetUnproxiedType().Name); + hook.OnBeforeSave(e); + processedHooks.Add(hook); + } + catch (Exception ex) when (ex is NotImplementedException || ex is NotSupportedException) + { + RegisterVoidHook(hook, e.EntityType, e.InitialState, HookStage.PreSave); + } + catch (Exception ex) + { + Logger.ErrorFormat(ex, "PreSaveHook exception ({0})", hook.GetType().FullName); + } + + // change state if applicable + if (e.HasStateChanged) + { + e.InitialState = e.State; + anyStateChanged = true; + } + } + } + + processedHooks.Each(x => x.OnBeforeSaveCompleted()); + + return processedHooks; + } + + public IEnumerable TriggerPostSaveHooks(IEnumerable entries, bool importantHooksOnly) + { + Guard.NotNull(entries, nameof(entries)); + + var processedHooks = new HashSet(); + + if (!entries.Any() || !_saveHooks.Any() || (importantHooksOnly && !this.HasImportantSaveHooks())) + return processedHooks; + + foreach (var entry in entries) + { + var e = entry; // Prevents access to modified closure + + if (HandledAlready(e, HookStage.PostSave)) + { + // Prevent repetitive hooking of the same entity/state/post combination within a single request + continue; + } + + var hooks = GetSaveHookInstancesFor(e, HookStage.PostSave, importantHooksOnly); + + foreach (var hook in hooks) + { + // call hook + try + { + //Logger.DebugFormat("POST save hook: {0}, State: {1}, Entity: {2}", hook.GetType().Name, e.InitialState, e.Entity.GetUnproxiedType().Name); + hook.OnAfterSave(e); + processedHooks.Add(hook); + } + catch (Exception ex) when (ex is NotImplementedException || ex is NotSupportedException) + { + RegisterVoidHook(hook, e.EntityType, e.InitialState, HookStage.PostSave); + } + catch (Exception ex) + { + Logger.ErrorFormat(ex, "PostSaveHook exception ({0})", hook.GetType().FullName); + } + } + } + + processedHooks.Each(x => x.OnAfterSaveCompleted()); + + return processedHooks; + } + + private IEnumerable GetSaveHookInstancesFor(IHookedEntity entry, HookStage stage, bool importantOnly) + { + return GetHookInstancesFor( + entry.EntityType, + entry.InitialState, + stage, + importantOnly, + _saveHooks, + _importantSaveHookTypes); + } + + private IEnumerable GetHookInstancesFor( + Type entityType, + EntityState entityState, + HookStage stage, + bool importantOnly, + IList> hookList, + HashSet importantHookTypes) where THook : IDbHook + { + IEnumerable hooks; + + if (entityType == null) + { + return Enumerable.Empty(); + } + + // For request cache lookup + var requestKey = new RequestHookKey(entityType, entityState, stage, importantOnly); + + if (_hooksRequestCache.ContainsKey(requestKey)) + { + hooks = _hooksRequestCache[requestKey]; + } + else + { + hooks = hookList + // Reduce by entity types which can be processed by this hook + .Where(x => x.Metadata.HookedType.IsAssignableFrom(entityType)) + // When importantOnly, only include hook types with [ImportantAttribute] + .Where(x => !importantOnly || importantHookTypes.Contains(x.Metadata.ImplType)) + // Exclude void hooks (hooks known to be useless for the current EntityType/State/Stage combination) + .Where(x => !_voidHooks.Contains(new HookKey(x.Metadata.ImplType, entityType, entityState, stage))) + .Select(x => x.Value) + .ToArray(); + + _hooksRequestCache.AddRange(requestKey, hooks); + } + + return hooks.Cast(); + } + + private bool HandledAlready(IHookedEntity entry, HookStage stage) + { + var entity = entry.Entity; + + if (entity == null || entity.IsTransientRecord()) + return false; + + var key = new HookedEntityKey(entry.EntityType, entity.Id, entry.InitialState, stage); + if (_hookedEntities.Contains(key)) + { + return true; + } + + _hookedEntities.Add(key); + return false; + } + + private void RegisterVoidHook(IDbHook hook, Type entityType, EntityState entityState, HookStage stage) + { + var hookType = hook.GetType(); + + // Unregister from request cache (if cached) + _hooksRequestCache.Remove(new RequestHookKey(entityType, entityState, stage, false), hook); + _hooksRequestCache.Remove(new RequestHookKey(entityType, entityState, stage, true), hook); + + lock (_lock) + { + // Add to static void hooks set + _voidHooks.Add(new HookKey(hookType, entityType, entityState, stage)); + } + } + + enum HookStage + { + Load, + PreSave, + PostSave + } + + class HookedEntityKey : Tuple + { + public HookedEntityKey(Type entityType, int entityId, EntityState initialState, HookStage stage) + : base(entityType, entityId, initialState, stage) + { + } + } + + class RequestHookKey : Tuple + { + public RequestHookKey(Type entityType, EntityState entityState, HookStage stage, bool importantOnly) + : base(entityType, entityState, stage, importantOnly) + { + } + } + + class HookKey : Tuple + { + public HookKey(Type hookType, Type entityType, EntityState entityState, HookStage stage) + : base(hookType, entityType, entityState, stage) + { + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HookEntityMetadata.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HookEntityMetadata.cs deleted file mode 100644 index 469e4df485..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/HookEntityMetadata.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace SmartStore.Core.Data.Hooks -{ - public class HookEntityMetadata - { - public HookEntityMetadata(EntityState state) - { - _state = state; - } - - private EntityState _state; - public EntityState State - { - get { return this._state; } - set - { - if (_state != value) - { - this._state = value; - HasStateChanged = true; - } - } - } - - public bool HasStateChanged { get; private set; } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs new file mode 100644 index 0000000000..f1f81574a2 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs @@ -0,0 +1,36 @@ +using System; + +namespace SmartStore.Core.Data.Hooks +{ + public class HookMetadata + { + /// + /// The type of entity + /// + public Type HookedType { get; set; } + + /// + /// The type of the hook class itself + /// + public Type ImplType { get; set; } + + public bool IsLoadHook { get; set; } + + /// + /// Whether the hook should run in any case, even if hooking has been turned off. + /// + public bool Important { get; set; } + + public static HookMetadata Create(Type hookedType, bool important = false) where THook : IDbHook + { + Guard.NotNull(hookedType, nameof(hookedType)); + + return new HookMetadata + { + ImplType = typeof(THook), + HookedType = hookedType, + Important = important + }; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HookableAttribute.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HookableAttribute.cs new file mode 100644 index 0000000000..ed20b256eb --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/HookableAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace SmartStore.Core.Data.Hooks +{ + /// + /// Turns hooking for a specific entity type explicitly on or off. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class HookableAttribute : Attribute + { + public HookableAttribute(bool isHookable) + { + IsHookable = IsHookable; + } + + public bool IsHookable + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HookedEntity.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HookedEntity.cs new file mode 100644 index 0000000000..2175d189d9 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/HookedEntity.cs @@ -0,0 +1,138 @@ +using System; +using System.Data.Entity.Infrastructure; +using EfState = System.Data.Entity.EntityState; + +namespace SmartStore.Core.Data.Hooks +{ + public interface IHookedEntity + { + /// + /// Gets the hooked entity entry + /// + DbEntityEntry Entry { get; } + + BaseEntity Entity { get; } + + Type EntityType { get; } + + /// + /// Gets or sets the initial (presave) state of the hooked entity. + /// The setter is for internal use only, don't invoke! + /// + EntityState InitialState { get; set; } + + /// + /// Gets or sets the current state of the hooked entity + /// + EntityState State { get; set; } + + /// + /// Gets a value indicating whether the entity state has changed during hooking. + /// + bool HasStateChanged { get; } + + /// + /// Gets a value indicating whether a property has been modified. + /// + /// Name of the property + bool IsPropertyModified(string propertyName); + + /// + /// Gets a value indicating whether the entity is in soft deleted state. + /// This is the case when the entity is an instance of + /// and the value of its Deleted property is true AND has changed since tracking. + /// But when the entity is not in modified state the snapshot comparison is omitted. + /// + bool IsSoftDeleted { get; } + } + + public class HookedEntity : IHookedEntity + { + private Type _entityType; + + public HookedEntity(DbEntityEntry entry) + { + Entry = entry; + InitialState = (EntityState)entry.State; + } + + public DbEntityEntry Entry + { + get; + private set; + } + + public BaseEntity Entity + { + get { return Entry.Entity as BaseEntity; } + } + + public Type EntityType + { + get + { + return _entityType ?? (_entityType = this.Entity?.GetUnproxiedType()); + } + } + + public EntityState InitialState + { + get; + set; + } + + public EntityState State + { + get + { + return (EntityState)Entry.State; + } + set + { + Entry.State = (EfState)((int)value); + } + } + + public bool HasStateChanged + { + get + { + return InitialState != State; + } + } + + public bool IsPropertyModified(string propertyName) + { + Guard.NotEmpty(propertyName, nameof(propertyName)); + + if (State == EntityState.Modified) + { + var prop = Entry.Property(propertyName); + if (prop == null) + { + throw new SmartException($"An entity property '{propertyName}' does not exist."); + } + + return prop.CurrentValue != null && !prop.CurrentValue.Equals(prop.OriginalValue); + } + + return false; + } + + public bool IsSoftDeleted + { + get + { + var entity = Entry.Entity as ISoftDeletable; + if (entity != null) + { + return Entry.State == EfState.Modified + ? entity.Deleted && IsPropertyModified("Deleted") + : entity.Deleted; + } + + return false; + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HookedEntityEntry.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HookedEntityEntry.cs deleted file mode 100644 index 679b20f397..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/HookedEntityEntry.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Core.Data.Hooks -{ - public class HookedEntityEntry - { - public object Entity { get; set; } - public EntityState PreSaveState { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs deleted file mode 100644 index 2372dbba00..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/HooksEventConsumer.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Linq.Expressions; -using Autofac.Features.Metadata; -using SmartStore.Collections; -using SmartStore.Core.Events; - -namespace SmartStore.Core.Data.Hooks -{ - - public class PreActionHookEvent - { - public IEnumerable ModifiedEntries { get; set; } - - /// - /// If set to true, executes hooks that require validation, otherwise executes hooks that do NOT require validation - /// - public bool RequiresValidation { get; set; } - } - - public class PostActionHookEvent - { - public IEnumerable ModifiedEntries { get; set; } - } - - public class HookMetadata - { - public Type HookedType { get; set; } - } - - public class HooksEventConsumer : - IConsumer, - IConsumer - { - private readonly IEnumerable> _preHooks; - private readonly IEnumerable> _postHooks; - - private readonly Multimap _preHooksCache = new Multimap(); - private readonly Multimap _postHooksCache = new Multimap(); - - public HooksEventConsumer( - IEnumerable> preHooks, - IEnumerable> postHooks) - { - this._preHooks = preHooks; - this._postHooks = postHooks; - } - - public void HandleEvent(PreActionHookEvent eventMessage) - { - var entries = eventMessage.ModifiedEntries.ToArray(); - - if (entries.Length == 0 || !_preHooks.Any()) - return; - - foreach (var entry in entries) - { - var e = entry; // Prevents access to modified closure - var preHooks = GetPreHookInstancesFor(e.Entity.GetType()); - foreach (var hook in preHooks) - { - if (hook.HookStates == e.PreSaveState && hook.RequiresValidation == eventMessage.RequiresValidation) - { - var metadata = new HookEntityMetadata(e.PreSaveState); - using (var scope = new DbContextScope(hooksEnabled: false)) - { - // dead end: don't let hooks call hooks again - hook.HookObject(e.Entity, metadata); - } - - if (metadata.HasStateChanged) - { - e.PreSaveState = metadata.State; - } - } - } - } - } - - [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] - private IEnumerable GetPreHookInstancesFor(Type hookedType) - { - if (_preHooksCache.ContainsKey(hookedType)) - { - return _preHooksCache[hookedType]; - } - - var hooks = _preHooks.Where(x => x.Metadata.HookedType.IsAssignableFrom(hookedType)).Select(x => x.Value); - _preHooksCache.AddRange(hookedType, hooks); - return hooks; - } - - public void HandleEvent(PostActionHookEvent eventMessage) - { - var entries = eventMessage.ModifiedEntries.ToArray(); - - if (entries.Length == 0 || !_postHooks.Any()) - return; - - foreach (var entry in entries) - { - var e = entry; // Prevents access to modified closure - var postHooks = GetPostHookInstancesFor(e.Entity.GetType()); - foreach (var hook in postHooks) - { - if (hook.HookStates == e.PreSaveState) - { - var metadata = new HookEntityMetadata(e.PreSaveState); - using (var scope = new DbContextScope(hooksEnabled: false)) - { - // dead end: don't let hooks call hooks again - hook.HookObject(e.Entity, metadata); - } - } - } - } - } - - private IEnumerable GetPostHookInstancesFor(Type hookedType) - { - if (_postHooksCache.ContainsKey(hookedType)) - { - return _postHooksCache[hookedType]; - } - - var hooks = _postHooks.Where(x => x.Metadata.HookedType.IsAssignableFrom(hookedType)).Select(x => x.Value); - _postHooksCache.AddRange(hookedType, hooks); - return hooks; - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IDbHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IDbHook.cs new file mode 100644 index 0000000000..14194258c8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/IDbHook.cs @@ -0,0 +1,9 @@ +namespace SmartStore.Core.Data.Hooks +{ + /// + /// Marker interface for the hooking framework + /// + public interface IDbHook + { + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs new file mode 100644 index 0000000000..9e46634c86 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Core.Data.Hooks +{ + public interface IDbHookHandler + { + bool HasImportantSaveHooks(); + + /// + /// Triggers all pre action hooks + /// + /// Entries + /// Whether to trigger only hooks marked with the attribute + /// true if the state of any entry changed + /// The list of actually processed hook instances + IEnumerable TriggerPreSaveHooks(IEnumerable entries, bool importantHooksOnly, out bool anyStateChanged); + + /// + /// Triggers all post action hooks + /// + /// Entries + /// Whether to trigger only hooks marked with the attribute + /// The list of actually processed hook instances + IEnumerable TriggerPostSaveHooks(IEnumerable entries, bool importantHooksOnly); + } + + public sealed class NullDbHookHandler : IDbHookHandler + { + private readonly static IDbHookHandler s_instance = new NullDbHookHandler(); + + public static IDbHookHandler Instance + { + get { return s_instance; } + } + + public bool HasImportantSaveHooks() + { + return false; + } + + public IEnumerable TriggerPreSaveHooks(IEnumerable entries, bool importantHooksOnly, out bool anyStateChanged) + { + anyStateChanged = false; + return Enumerable.Empty(); + } + + public IEnumerable TriggerPostSaveHooks(IEnumerable entries, bool importantHooksOnly) + { + return Enumerable.Empty(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs new file mode 100644 index 0000000000..669ffc9dec --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs @@ -0,0 +1,13 @@ +using System; + +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/Hooks/IDbSaveHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IDbSaveHook.cs new file mode 100644 index 0000000000..3a1161aa39 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/IDbSaveHook.cs @@ -0,0 +1,28 @@ +using System; + +namespace SmartStore.Core.Data.Hooks +{ + /// + /// A hook that is executed before and after a database save operation. + /// An implementor should raise or + /// to signal the hook handler + /// that it never should process the hook again for the current + /// EntityType/State/Stage combination. + /// + public interface IDbSaveHook : IDbHook + { + void OnBeforeSave(IHookedEntity entry); + + void OnAfterSave(IHookedEntity entry); + + /// + /// Called after all entities in the current unit of work has been handled right before saving changes to the database + /// + void OnBeforeSaveCompleted(); + + /// + /// Called after all entities in the current unit of work has been handled after saving changes to the database + /// + void OnAfterSaveCompleted(); + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IHook.cs deleted file mode 100644 index a490f4975c..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/IHook.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace SmartStore.Core.Data.Hooks -{ - public interface IHook - { - void HookObject(object entity, HookEntityMetadata metadata); - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IPostActionHook .cs b/src/Libraries/SmartStore.Core/Data/Hooks/IPostActionHook .cs deleted file mode 100644 index 0886644971..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/IPostActionHook .cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// A hook that is executed after an action. - /// - public interface IPostActionHook : IHook - { - /// - /// Gets the entity state to listen for. - /// - EntityState HookStates { get; } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IPreActionHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IPreActionHook.cs deleted file mode 100644 index 9106db6a29..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/IPreActionHook.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// A hook that is executed before an action. - /// - public interface IPreActionHook : IHook - { - /// - /// Gets a value indicating whether the hook is only used after successful validation. - /// - /// - /// true if requires validation; otherwise, false. - /// - bool RequiresValidation { get; } - - /// - /// Gets the entity state to listen for. - /// - /// - /// The hook states. - /// - EntityState HookStates { get; } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/ImportantAttribute.cs b/src/Libraries/SmartStore.Core/Data/Hooks/ImportantAttribute.cs new file mode 100644 index 0000000000..585988ee7c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Data/Hooks/ImportantAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace SmartStore.Core.Data.Hooks +{ + /// + /// Indicates that a hook instance should run in any case, even when hooking has been turned off. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class ImportantAttribute : Attribute + { + } +} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PostActionHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PostActionHook.cs deleted file mode 100644 index 483ea1b36f..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PostActionHook.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a strongly-typed hook to be run after an action is performed in the database. - /// - /// The type of entity this hook must watch for. - public abstract class PostActionHook : IPostActionHook - { - /// - /// Implements the interface. This causes the hook to only run for objects that are assignable to TEntity. - /// - public void HookObject(object entity, HookEntityMetadata metadata) - { - if (typeof(TEntity).IsAssignableFrom(entity.GetType())) - { - Hook((TEntity)entity, metadata); - } - } - - /// - /// The logic to perform per entity after the registered action gets performed. - /// This gets run once per entity that has been changed. - /// - public abstract void Hook(TEntity entity, HookEntityMetadata metadata); - - /// - /// Entity States that this hook must be registered to listen for. - /// - public abstract EntityState HookStates { get; } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PostDeleteHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PostDeleteHook.cs deleted file mode 100644 index 5ba1bb5c4f..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PostDeleteHook.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a hook that will run after an entity gets deleted from the database. - /// - public abstract class PostDeleteHook : PostActionHook - { - public override EntityState HookStates - { - get { return EntityState.Deleted; } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PostInsertHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PostInsertHook.cs deleted file mode 100644 index 53134d4e7f..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PostInsertHook.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a hook that will run after an entity gets inserted into the database. - /// - public abstract class PostInsertHook : PostActionHook - { - /// - /// Returns as the hookstate to listen for. - /// - public override EntityState HookStates - { - get { return EntityState.Added; } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PostUpdateHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PostUpdateHook.cs deleted file mode 100644 index ba6b9c34da..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PostUpdateHook.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a hook that will run after an entity gets updated in the database. - /// - public abstract class PostUpdateHook : PostActionHook - { - /// - /// Returns as the hookstate to listen for. - /// - public override EntityState HookStates - { - get { return EntityState.Modified; } - } - - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PreActionHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PreActionHook.cs deleted file mode 100644 index 2a49e174fb..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PreActionHook.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// A strongly typed PreActionHook. - /// - /// The type of entity this hook must watch for. - public abstract class PreActionHook : IPreActionHook - { - /// - /// Gets a value indicating whether the hook is only used after successful validation. - /// - /// - /// true if requires validation; otherwise, false. - /// - public abstract bool RequiresValidation { get; } - - /// - /// Entity States that this hook must be registered to listen for. - /// - public abstract EntityState HookStates { get; } - - /// - /// The logic to perform per entity before the registered action gets performed. - /// This gets run once per entity that has been changed. - /// - /// The entity that is processed by Entity Framework. - /// Metadata about the entity in the context of this hook - such as state. - public abstract void Hook(TEntity entity, HookEntityMetadata metadata); - - /// - /// Implements the interface. This causes the hook to only run for objects that are assignable to TEntity. - /// - public void HookObject(object entity, HookEntityMetadata metadata) - { - if (typeof(TEntity).IsAssignableFrom(entity.GetType())) - { - this.Hook((TEntity)entity, metadata); - } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PreDeleteHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PreDeleteHook.cs deleted file mode 100644 index 6d6dd5cfb5..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PreDeleteHook.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a hook that will run before an entity gets deleted from the database. - /// - public abstract class PreDeleteHook : PreActionHook - { - /// - /// Returns as the hookstate to listen for. - /// - public override EntityState HookStates - { - get { return EntityState.Deleted; } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PreInsertHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PreInsertHook.cs deleted file mode 100644 index bb5df75e31..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PreInsertHook.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a hook that will run before an entity gets inserted into the database. - /// - public abstract class PreInsertHook : PreActionHook - { - /// - /// Returns as the hookstate to listen for. - /// - public override EntityState HookStates - { - get { return EntityState.Added; } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/PreUpdateHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/PreUpdateHook.cs deleted file mode 100644 index 483a4b977e..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/PreUpdateHook.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Core.Data.Hooks -{ - /// - /// Implements a hook that will run before an entity gets updated in the database. - /// - public abstract class PreUpdateHook : PreActionHook - { - /// - /// Returns as the hookstate to listen for. - /// - public override EntityState HookStates - { - get { return EntityState.Modified; } - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/IDbContext.cs b/src/Libraries/SmartStore.Core/Data/IDbContext.cs index cb276a03ac..f043ccf175 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContext.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContext.cs @@ -14,7 +14,6 @@ public interface IDbContext DbSet Set() where TEntity : BaseEntity; int SaveChanges(); - Task SaveChangesAsync(); IList ExecuteStoredProcedureList(string commandText, params object[] parameters) @@ -38,9 +37,6 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// The result returned by the database after executing the command. int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters); - /// Executes sql by using SQL-Server Management Objects which supports GO statements. - int ExecuteSqlThroughSmo(string sql); - string Alias { get; } // increasing performance on bulk operations @@ -67,6 +63,37 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// bool AutoCommitEnabled { get; set; } + /// + /// Detects changes made to the properties and relationships of POCO entities. + /// Please note that normally DetectChanges is called automatically by many of the methods of + /// DbContext and its related classes such that it is rare that this method will need to be called explicitly. + /// However, it may be desirable, usually for performance reasons, to turn off this automatic + /// calling of DetectChanges using the AutoDetectChangesEnabled flag. + /// + void DetectChanges(); + + /// + /// Checks whether the underlying ORM mapper is currently in the process of detecting changes. + /// + /// + bool IsDetectingChanges(); + + /// + /// Gets a value indicating whether the given entity was modified since it has been attached to the context + /// + /// The entity to check + /// true if the entity was modified, false otherwise + bool IsModified(BaseEntity entity); + + /// + /// Determines whether an entity property has changed since it was attached. + /// + /// Entity + /// The property name to check + /// The previous/original property value if change was detected + /// true if property has changed, false otherwise + bool TryGetModifiedProperty(BaseEntity entity, string propertyName, out object originalValue); + /// /// Gets a list of modified properties for the specified entity /// @@ -109,13 +136,20 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// The count of detached entities int DetachEntities(bool unchangedEntitiesOnly = true) where TEntity : class; + /// + /// Detaches all entities matching the passed from the current object context + /// + /// When true, only entities in unchanged state get detached. + /// The count of detached entities + int DetachEntities(Func predicate, bool unchangedEntitiesOnly = true); + /// /// Change the state of an entity object /// /// Type of entity /// The entity instance - /// The new state - void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity; + /// The requested new state + void ChangeState(TEntity entity, System.Data.Entity.EntityState requestedState) where TEntity : BaseEntity; /// /// Reloads the entity from the database overwriting any property values with values from the database. diff --git a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs index f3afa01d07..0d56838cce 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs @@ -10,7 +10,6 @@ namespace SmartStore { public static class IDbContextExtensions { - /// /// Detaches all entities from the current object context /// @@ -23,7 +22,7 @@ public static int DetachAll(this IDbContext ctx, bool unchangedEntitiesOnly = tr public static void DetachEntities(this IDbContext ctx, IEnumerable entities) where TEntity : BaseEntity { - Guard.ArgumentNotNull(() => ctx); + Guard.NotNull(ctx, nameof(ctx)); entities.Each(x => ctx.DetachEntity(x)); } @@ -56,8 +55,8 @@ public static IQueryable QueryForCollection( where TEntity : BaseEntity where TCollection : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => navigationProperty); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(navigationProperty, nameof(navigationProperty)); var dbContext = ctx as DbContext; if (dbContext == null) @@ -75,8 +74,8 @@ public static IQueryable QueryForReference( where TEntity : BaseEntity where TProperty : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => navigationProperty); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(navigationProperty, nameof(navigationProperty)); var dbContext = ctx as DbContext; if (dbContext == null) @@ -96,8 +95,8 @@ public static void LoadCollection( where TEntity : BaseEntity where TCollection : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => navigationProperty); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(navigationProperty, nameof(navigationProperty)); var dbContext = ctx as DbContext; if (dbContext == null) @@ -108,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; @@ -140,12 +145,13 @@ public static void LoadReference( this IDbContext ctx, TEntity entity, Expression> navigationProperty, - bool force = false) + bool force = false, + Func, IQueryable> queryAction = null) where TEntity : BaseEntity where TProperty : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => navigationProperty); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(navigationProperty, nameof(navigationProperty)); var dbContext = ctx as DbContext; if (dbContext == null) @@ -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; @@ -163,7 +175,23 @@ public static void LoadReference( if (!reference.IsLoaded) { - reference.Load(); + if (queryAction != null || ctx.ForceNoTracking) + { + var query = !ctx.ForceNoTracking + ? reference.Query() + : reference.Query().AsNoTracking(); + + var myQuery = queryAction != null + ? queryAction(query) + : query; + + reference.CurrentValue = myQuery.FirstOrDefault(); + } + else + { + reference.Load(); + } + reference.IsLoaded = true; } } diff --git a/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs index 2289488b95..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. /// @@ -21,8 +20,8 @@ public static class IQueryableExtensions /// A new query object to which the expansion was applied. public static IQueryable Expand(this IQueryable query, string path) where T : BaseEntity { - Guard.ArgumentNotNull(query, "query"); - Guard.ArgumentNotEmpty(path, "path"); + Guard.NotNull(query, "query"); + Guard.NotEmpty(path, "path"); return query.Include(path); } @@ -35,11 +34,10 @@ public static IQueryable Expand(this IQueryable query, string path) whe /// A new query object to which the expansion was applied. public static IQueryable Expand(this IQueryable query, Expression> path) where T : BaseEntity { - Guard.ArgumentNotNull(query, "query"); - Guard.ArgumentNotNull(path, "path"); + Guard.NotNull(query, "query"); + Guard.NotNull(path, "path"); return query.Include(path); } - } } diff --git a/src/Libraries/SmartStore.Core/Data/IRepository.cs b/src/Libraries/SmartStore.Core/Data/IRepository.cs index cae856f654..8337168c58 100644 --- a/src/Libraries/SmartStore.Core/Data/IRepository.cs +++ b/src/Libraries/SmartStore.Core/Data/IRepository.cs @@ -94,25 +94,6 @@ public partial interface IRepository where T : BaseEntity [Obsolete("Use the extension method from 'SmartStore.Core, SmartStore.Core.Data' instead")] IQueryable Expand(IQueryable query, Expression> path); - /// - /// Gets a value indicating whether the given entity was modified since it has been attached to the context - /// - /// The entity to check - /// true if the entity was modified, false otherwise - bool IsModified(T entity); - - /// - /// Gets a list of modified properties for the specified entity - /// - /// The entity instance for which to get modified properties for - /// - /// A dictionary, where the key is the name of the modified property - /// and the value is its ORIGINAL value (which was tracked when the entity - /// was attached to the context the first time) - /// Returns an empty dictionary if no modification could be detected. - /// - IDictionary GetModifiedProperties(T entity); - /// /// Returns the data context associated with the repository. /// diff --git a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs index c2495f1733..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 { @@ -16,10 +17,10 @@ public static T GetFirst(this IRepository rs, Func predicate) whe public static IEnumerable GetMany(this IRepository rs, IEnumerable ids) where T : BaseEntity { - foreach (var chunk in ids.Chunk()) + foreach (var chunk in ids.Slice(128)) { - var query = rs.Table.Where(a => chunk.Contains(a.Id)); - foreach (var item in query) + var items = rs.Table.Where(a => chunk.Contains(a.Id)).ToList(); + foreach (var item in items) { yield return item; } @@ -28,21 +29,19 @@ public static IEnumerable GetMany(this IRepository rs, IEnumerable public static void Delete(this IRepository rs, int id) where T : BaseEntity { - Guard.ArgumentNotZero(id, "id"); + Guard.NotZero(id, nameof(id)); // Perf: work with stub entity var entity = rs.Create(); entity.Id = id; - rs.Attach(entity); - // must downcast 'cause of Rhino mocks stub rs.Context.ChangeState((BaseEntity)entity, System.Data.Entity.EntityState.Deleted); } public static void DeleteRange(this IRepository rs, IEnumerable ids) where T : BaseEntity { - Guard.ArgumentNotNull(() => ids); + Guard.NotNull(ids, nameof(ids)); ids.Each(id => rs.Delete(id)); } @@ -77,7 +76,7 @@ public static int DeleteAll(this IRepository rs, Expression> if (cascade) { var records = query.ToList(); - foreach (var chunk in records.Chunk(500)) + foreach (var chunk in records.Slice(500)) { rs.DeleteRange(chunk.ToList()); count += rs.Context.SaveChanges(); @@ -86,7 +85,7 @@ public static int DeleteAll(this IRepository rs, Expression> else { var ids = query.Select(x => new { Id = x.Id }).ToList(); - foreach (var chunk in ids.Chunk(500)) + foreach (var chunk in ids.Slice(500)) { rs.DeleteRange(chunk.Select(x => x.Id)); count += rs.Context.SaveChanges(); @@ -97,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/Data/SqlFileTokenizer.cs b/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs index e85c9bac4c..2575c2f79f 100644 --- a/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs +++ b/src/Libraries/SmartStore.Core/Data/SqlFileTokenizer.cs @@ -15,7 +15,7 @@ public class SqlFileTokenizer public SqlFileTokenizer(string fileName, Assembly assembly = null, string location = null) { - Guard.ArgumentNotEmpty(() => fileName); + Guard.NotEmpty(fileName, nameof(fileName)); this.FileName = fileName; this.Assembly = assembly; diff --git a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogComment.cs b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogComment.cs index e80f4db959..fce406b0b9 100644 --- a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogComment.cs +++ b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogComment.cs @@ -1,25 +1,30 @@ +using System.Runtime.Serialization; using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Blogs { - /// - /// Represents a blog comment - /// - public partial class BlogComment : CustomerContent + /// + /// Represents a blog comment + /// + [DataContract] + public partial class BlogComment : CustomerContent { - /// - /// Gets or sets the comment text - /// - public string CommentText { get; set; } + /// + /// Gets or sets the comment text + /// + [DataMember] + public string CommentText { get; set; } - /// - /// Gets or sets the blog post identifier - /// - public int BlogPostId { get; set; } + /// + /// Gets or sets the blog post identifier + /// + [DataMember] + public int BlogPostId { get; set; } - /// - /// Gets or sets the blog post - /// - public virtual BlogPost BlogPost { get; set; } + /// + /// Gets or sets the blog post + /// + [DataMember] + public virtual BlogPost BlogPost { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogPost.cs b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogPost.cs index da8ad66682..0a58154a36 100644 --- a/src/Libraries/SmartStore.Core/Domain/Blogs/BlogPost.cs +++ b/src/Libraries/SmartStore.Core/Domain/Blogs/BlogPost.cs @@ -1,105 +1,123 @@ using System; using System.Collections.Generic; +using System.Runtime.Serialization; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Blogs { - /// - /// Represents a blog post - /// + /// + /// Represents a blog post + /// + [DataContract] public partial class BlogPost : BaseEntity, ISlugSupported, IStoreMappingSupported { private ICollection _blogComments; - /// - /// Gets or sets the language identifier - /// - public int LanguageId { get; set; } - - /// - /// Gets or sets the blog post title - /// - public string Title { get; set; } - - /// - /// Gets or sets the blog post title - /// - public string Body { get; set; } - - /// - /// Gets or sets a value indicating whether the blog post comments are allowed - /// - public bool AllowComments { get; set; } - - /// - /// Gets or sets the total number of approved comments - /// The same as if we run newsItem.NewsComments.Where(n => n.IsApproved).Count() - /// We use this property for performance optimization (no SQL command executed) - /// - /// - public int ApprovedCommentCount { get; set; } - /// - /// Gets or sets the total number of not approved comments - /// The same as if we run newsItem.NewsComments.Where(n => !n.IsApproved).Count() - /// We use this property for performance optimization (no SQL command executed) - /// - /// - public int NotApprovedCommentCount { get; set; } - - /// - /// Gets or sets the blog tags - /// - public string Tags { get; set; } - - /// - /// Gets or sets the blog post start date and time - /// - public DateTime? StartDateUtc { get; set; } - - /// - /// Gets or sets the blog post end date and time - /// - public DateTime? EndDateUtc { get; set; } - - /// - /// Gets or sets the meta keywords - /// - public string MetaKeywords { get; set; } - - /// - /// Gets or sets the meta description - /// - public string MetaDescription { get; set; } - - /// - /// Gets or sets the meta title - /// - public string MetaTitle { get; set; } + /// + /// Gets or sets the language identifier + /// + [DataMember] + public int LanguageId { get; set; } + + /// + /// Gets or sets the blog post title + /// + [DataMember] + public string Title { get; set; } + + /// + /// Gets or sets the blog post title + /// + [DataMember] + public string Body { get; set; } + + /// + /// Gets or sets a value indicating whether the blog post comments are allowed + /// + [DataMember] + public bool AllowComments { get; set; } + + /// + /// Gets or sets the total number of approved comments + /// The same as if we run newsItem.NewsComments.Where(n => n.IsApproved).Count() + /// We use this property for performance optimization (no SQL command executed) + /// + /// + [DataMember] + public int ApprovedCommentCount { get; set; } + + /// + /// Gets or sets the total number of not approved comments + /// The same as if we run newsItem.NewsComments.Where(n => !n.IsApproved).Count() + /// We use this property for performance optimization (no SQL command executed) + /// + [DataMember] + public int NotApprovedCommentCount { get; set; } + + /// + /// Gets or sets the blog tags + /// + [DataMember] + public string Tags { get; set; } + + /// + /// Gets or sets the blog post start date and time + /// + [DataMember] + public DateTime? StartDateUtc { get; set; } + + /// + /// Gets or sets the blog post end date and time + /// + [DataMember] + public DateTime? EndDateUtc { 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 the date and time of entity creation - /// - public virtual DateTime CreatedOnUtc { get; set; } - - /// - /// Gets or sets the blog comments - /// - public virtual ICollection BlogComments + + /// + /// Gets or sets the date and time of entity creation + /// + [DataMember] + public virtual DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the blog comments + /// + [DataMember] + public virtual ICollection BlogComments { get { return _blogComments ?? (_blogComments = new HashSet()); } protected set { _blogComments = value; } } - - /// - /// Gets or sets the language - /// - public virtual Language Language { get; set; } + + /// + /// Gets or sets the language + /// + [DataMember] + public virtual Language Language { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/AttributeControlType.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/AttributeControlType.cs index 5e9f4ef487..56361ef2d2 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/AttributeControlType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/AttributeControlType.cs @@ -34,8 +34,8 @@ public enum AttributeControlType /// FileUpload = 30, /// - /// Color squares + /// Boxes /// - ColorSquares = 40, + Boxes = 40, } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index 12f9fa8587..394e9e4e9e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -10,62 +10,78 @@ public enum SubCategoryDisplayType Bottom = 10 } + public enum GridColumnSpan + { + Max2Cols = 2, + Max3Cols = 3, + Max4Cols = 4, + Max5Cols = 5, + Max6Cols = 6 + } + public class CatalogSettings : ISettings { public CatalogSettings() { FileUploadAllowedExtensions = new List(); AllowProductSorting = true; - DefaultSortOrder = ProductSortingEnum.Position; + DefaultSortOrder = ProductSortingEnum.Relevance; AllowProductViewModeChanging = true; DefaultViewMode = "grid"; CategoryBreadcrumbEnabled = true; ShowShareButton = true; - PageShareCode = "
"; + PageShareCode = "
"; DefaultProductRatingValue = 5; NotifyStoreOwnerAboutNewProductReviews = true; EmailAFriendEnabled = true; AskQuestionEnabled = true; - RecentlyViewedProductsNumber = 6; + RecentlyViewedProductsNumber = 8; RecentlyViewedProductsEnabled = true; - RecentlyAddedProductsNumber = 10; + RecentlyAddedProductsNumber = 100; RecentlyAddedProductsEnabled = true; CompareProductsEnabled = true; - FilterEnabled = true; - MaxFilterItemsToDisplay = 4; - SortFilterResultsByMatches = true; SubCategoryDisplayType = SubCategoryDisplayType.AboveProductList; - ProductSearchAutoCompleteEnabled = true; - ShowProductImagesInSearchAutoComplete = true; - ProductSearchAutoCompleteNumberOfProducts = 10; - ProductSearchTermMinimumLength = 3; - NumberOfBestsellersOnHomepage = 6; + NumberOfBestsellersOnHomepage = 12; + ShowPopularProductTagsOnHomepage = false; ShowManufacturersOnHomepage = true; - ShowManufacturerPictures = false; - ShowManufacturerPicturesInProductDetail = true; - SearchPageProductsPerPage = 6; + ShowManufacturerPictures = true; + ManufacturerItemsToDisplayOnHomepage = 18; + ShowManufacturersInOffCanvas = true; + ManufacturerItemsToDisplayInOffcanvasMenu = 20; + ShowManufacturerInProductDetail = true; + ShowManufacturerPicturesInProductDetail = true; ProductsAlsoPurchasedEnabled = true; - ProductsAlsoPurchasedNumber = 6; + ProductsAlsoPurchasedNumber = 12; NumberOfProductTags = 15; - ProductsByTagPageSize = 12; + DefaultProductListPageSize = 24; UseSmallProductBoxOnHomePage = true; DisplayTierPricesWithDiscounts = true; - DefaultPageSizeOptions = "12, 18, 36, 72, 150"; - ProductsByTagAllowCustomersToSelectPageSize = true; - ProductsByTagPageSizeOptions = "12, 18, 36, 72, 150"; + DefaultPageSizeOptions = "12,24,36,48,72,120"; + AllowCustomersToSelectPageSize = true; MaximumBackInStockSubscriptions = 200; FileUploadMaximumSizeBytes = 1024 * 200; //200KB - ManufacturersBlockItemsToDisplay = 5; DisplayAllImagesNumber = 6; + ShowShortDescriptionInGridStyleLists = true; + ShowManufacturerInGridStyleLists = true; + ShowManufacturerLogoInLists = false; + ShowProductOptionsInLists = true; ShowColorSquaresInLists = true; ShowDiscountSign = true; ShowVariantCombinationPriceAdjustment = true; - ShowLinkedAttributeValueImage = true; + ShowLinkedAttributeValueImage = true; EnableDynamicPriceUpdate = true; ShowProductReviewsInProductDetail = true; + EnableHtmlTextCollapser = true; HtmlTextCollapsedHeight = 260; MostRecentlyUsedCategoriesMaxSize = 6; MostRecentlyUsedManufacturersMaxSize = 4; + IncludeShortDescriptionInCompareProducts = true; + GridStyleListColumnSpan = GridColumnSpan.Max4Cols; + ShowProductsFromSubcategories = true; + ApplyTierPricePercentageToAttributePriceAdjustments = false; + AllowDifferingEmailAddressForEmailAFriend = false; + AllowAnonymousUsersToEmailAFriend = false; + AllowAnonymousUsersToReviewProduct = false; } /// @@ -113,16 +129,26 @@ 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 /// public bool ShowLinkedAttributeValueImage { get; set; } + /// + /// Gets or sets a value indicating how many menu items will be displayed + /// + public int? MaxItemsToDisplayInCatalogMenu { get; set; } + /// /// Gets or sets a value indicating whether product sorting is enabled /// @@ -162,36 +188,26 @@ public CatalogSettings() /// Gets or sets a value indicating whether category breadcrumb is enabled ///
public bool CategoryBreadcrumbEnabled { get; set; } - - /// - /// Gets or sets a value indicating whether filter is enabled - /// - public bool FilterEnabled { get; set; } - /// - /// Gets or sets a value which determines the maximum number of displayed filter items - /// - public int MaxFilterItemsToDisplay { get; set; } + /// + /// 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 all filter criterias should be expanded - /// - public bool ExpandAllFilterCriteria { get; set; } + /// + /// An option indicating whether sub pages should display the subcategories + /// + public bool ShowSubCategoriesInSubPages { get; set; } /// - /// Gets or sets a value indicating whether filter results should be sorted by matches + /// An option indicating whether sub pages should display the category/manufacturer description /// - public bool SortFilterResultsByMatches { get; set; } + public bool ShowDescriptionInSubPages { get; set; } /// - /// Gets or sets a value indicating whether and where to display a list of subcategories + /// Gets or sets a value indicating whether a 'Share button' is enabled /// - public SubCategoryDisplayType SubCategoryDisplayType { get; set; } - - /// - /// Gets or sets a value indicating whether a 'Share button' is enabled - /// - public bool ShowShareButton { get; set; } + public bool ShowShareButton { get; set; } /// /// Gets or sets a share code (e.g. AddThis button code) @@ -237,6 +253,11 @@ public CatalogSettings() /// Gets or sets a value indicating whether 'ask product question' feature is enabled /// public bool AskQuestionEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether customers are allowed to enter a differing email address + /// + public bool AllowDifferingEmailAddressForEmailAFriend { get; set; } /// /// Gets or sets a value indicating whether to allow anonymous users to email a friend. @@ -269,70 +290,70 @@ public CatalogSettings() public bool CompareProductsEnabled { get; set; } /// - /// Gets or sets a value indicating whether autocomplete is enabled - /// - public bool ProductSearchAutoCompleteEnabled { get; set; } - - /// - /// Gets or sets a value indicating whether to show product images in the auto complete search + /// Gets or sets a value indicating whether to show bestsellers on home page /// - public bool ShowProductImagesInSearchAutoComplete { get; set; } + public bool ShowBestsellersOnHomepage { get; set; } /// - /// Gets or sets a number of products to return when using "autocomplete" feature + /// Gets or sets a number of bestsellers on home page /// - public int ProductSearchAutoCompleteNumberOfProducts { get; set; } + public int NumberOfBestsellersOnHomepage { get; set; } /// - /// Gets or sets a minimum search term length + /// Gets or sets a value indicating whether to show manufacturers on home page /// - public int ProductSearchTermMinimumLength { get; set; } + public bool ShowManufacturersOnHomepage { get; set; } /// - /// Gets or sets a value indicating whether to show bestsellers on home page + /// Gets or sets a value indicating whether to show manufacturers in offcanvas menu /// - public bool ShowBestsellersOnHomepage { get; set; } - + public bool ShowManufacturersInOffCanvas { get; set; } + /// - /// Gets or sets a number of bestsellers on home page + /// Gets or sets the value indicating how many manufacturers on home page /// - public int NumberOfBestsellersOnHomepage { get; set; } + public int ManufacturerItemsToDisplayOnHomepage { get; set; } /// - /// Gets or sets a value indicating whether to show manufacturers on home page + /// Gets or sets the value indicating how many manufacturers in offcanvas menu /// - public bool ShowManufacturersOnHomepage { get; set; } - + public int ManufacturerItemsToDisplayInOffcanvasMenu { get; set; } + /// /// Gets or sets a value indicating whether to show manufacturer pictures or names on home page /// public bool ShowManufacturerPictures { get; set; } - /// - /// Gets or sets a value indicating whether to hide manufacturer pictures in product detail + /// + /// Gets or sets a value indicating whether to display manufacturer detail links in product detail pages /// - public bool ShowManufacturerPicturesInProductDetail { get; set; } + public bool ShowManufacturerInProductDetail { get; set; } + + /// + /// Gets or sets a value indicating whether to display pictures or textual links to manufacturer pages in product detail pages + /// + public bool ShowManufacturerPicturesInProductDetail { get; set; } /// - /// Gets or sets a value indicating whether to hide maufacturer default pictures + /// Gets or sets a value indicating whether to hide manufacturer default pictures /// public bool HideManufacturerDefaultPictures { get; set; } - /// - /// Gets or sets a value indicating whether to hide category default pictures + /// + /// Gets or sets a value indicating whether to hide manufacturer default pictures /// - public bool HideCategoryDefaultPictures { get; set; } + public bool SortManufacturersAlphabetically { get; set; } + + /// + /// Gets or sets a value indicating whether to hide category default pictures + /// + public bool HideCategoryDefaultPictures { get; set; } /// /// Gets or sets a value indicating whether to hide product default pictures /// public bool HideProductDefaultPictures { get; set; } - /// - /// Gets or sets a number of products per page on search products page - /// - public int SearchPageProductsPerPage { get; set; } - /// /// Gets or sets "List of products purchased by other customers who purchased the above" option is enable /// @@ -359,29 +380,29 @@ public CatalogSettings() public int NumberOfProductTags { get; set; } /// - /// Gets or sets a number of products per page on 'products by tag' page + /// Gets or sets a number of products per page on a product list page /// - public int ProductsByTagPageSize { get; set; } + public int DefaultProductListPageSize { get; set; } /// - /// Gets or sets a value indicating whether customers can select the page size for 'products by tag' + /// Gets or sets a value indicating whether customers can select page size in product listings /// - public bool ProductsByTagAllowCustomersToSelectPageSize { get; set; } + public bool AllowCustomersToSelectPageSize { get; set; } + + public int DisplayAllImagesNumber { get; set; } - /// - /// Gets or sets the available customer selectable page size options for 'products by tag' - /// - public string ProductsByTagPageSizeOptions { get; set; } + public bool ShowManufacturerInGridStyleLists { get; set; } - public int ProductSearchPageSize { get; set; } + /// + /// Whether to show brand logo instead of textual name in product lists + /// + public bool ShowManufacturerLogoInLists { get; set; } - public bool ProductSearchAllowCustomersToSelectPageSize { get; set; } + public bool ShowShortDescriptionInGridStyleLists { get; set; } - public string ProductSearchPageSizeOptions { get; set; } - - public int DisplayAllImagesNumber { get; set; } + public bool ShowProductOptionsInLists { get; set; } - public bool ShowColorSquaresInLists { get; set; } + public bool ShowColorSquaresInLists { get; set; } public bool HideBuyButtonInLists { get; set; } @@ -389,24 +410,31 @@ public CatalogSettings() public bool ShowDefaultQuantityUnit { get; set; } + public bool ShowDefaultDeliveryTime { get; set; } + + public bool ShowPopularProductTagsOnHomepage { get; set; } + public bool ShowDiscountSign { get; set; } - public bool SuppressSkuSearch { get; set; } + /// + /// Gets or sets the price display style for prices + /// + public PriceDisplayStyle PriceDisplayStyle { get; set; } - /// - /// Gets or sets a value indicating whether to search long description - /// - public bool SearchDescriptions { 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 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 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 @@ -427,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) @@ -443,18 +476,6 @@ public CatalogSettings() /// public bool IgnoreFeaturedProducts { get; set; } - /// - /// Gets or set the default value to use for Category page size options (for new Categories) - /// - /// Obsolete - public string DefaultCategoryPageSizeOptions { get; set; } - - /// - /// Gets or set the default value to use for Manufacturer page size opitons (for new Manufacturers) - /// - /// Obsolete - public string DefaultManufacturerPageSizeOptions { get; set; } - /// /// Gets or sets a value indicating maximum number of 'back in stock' subscription /// @@ -469,12 +490,7 @@ public CatalogSettings() /// Gets or sets a list of allowed file extensions for customer uploaded files /// public List FileUploadAllowedExtensions { get; set; } - - /// - /// Gets or sets the value indicating how many manufacturers to display in manufacturers block - /// - public int ManufacturersBlockItemsToDisplay { get; set; } - + /// /// Gets or sets a value indicating if html long text should be collapsed /// @@ -499,5 +515,15 @@ public CatalogSettings() /// Gets or sets how many items to display maximally in the most recently used manufacturer list /// public int MostRecentlyUsedManufacturersMaxSize { get; set; } + + /// + /// Gets or sets how many columns per row should be displayed at most in grid style lists on largest screen resolution. + /// + public GridColumnSpan GridStyleListColumnSpan { get; set; } + + /// + /// Gets or sets a value indicating whether percental values of tierprices should be applied to price adjustments of attributes + /// + public bool ApplyTierPricePercentageToAttributePriceAdjustments { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs index 83c5ef53c6..d74e7c0c73 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs @@ -1,14 +1,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using System.Runtime.Serialization; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Catalog { @@ -17,7 +14,7 @@ namespace SmartStore.Core.Domain.Catalog /// [DataContract] [DebuggerDisplay("{Id}: {Name} (Parent: {ParentCategoryId})")] - public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported + public partial class Category : BaseEntity, ICategoryNode, IAuditable, ISoftDeletable, IPagingOptions { private ICollection _appliedDiscounts; @@ -45,11 +42,29 @@ public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, IS [DataMember] public string BottomDescription { get; set; } - /// - /// Gets or sets the category alias - /// (an optional key for advanced customization) + /// + /// 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 /// - [DataMember] + [DataMember] + public string BadgeText { get; set; } + + /// + /// Gets or sets the type of the badge within menus + /// + [DataMember] + public int BadgeStyle { get; set; } + + /// + /// Gets or sets the category alias + /// (an optional key for advanced customization) + /// + [DataMember] public string Alias { get; set; } /// @@ -98,13 +113,13 @@ public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, IS /// Gets or sets the page size /// [DataMember] - public int PageSize { get; set; } + public int? PageSize { get; set; } /// /// Gets or sets a value indicating whether customers can select the page size /// [DataMember] - public bool AllowCustomersToSelectPageSize { get; set; } + public bool? AllowCustomersToSelectPageSize { get; set; } /// /// Gets or sets the available customer selectable page size options @@ -112,15 +127,16 @@ public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, IS [DataMember] public string PageSizeOptions { get; set; } - /// - /// Gets or sets the available price ranges - /// - [DataMember] + /// + /// Gets or sets the available price ranges + /// + [Obsolete("Price ranges are calculated automatically since version 3")] + [StringLength(400)] public string PriceRanges { get; set; } - /// - /// Gets or sets a value indicating whether to show the category on home page - /// + /// + /// Gets or sets a value indicating whether to show the category on home page + /// [DataMember] public bool ShowOnHomePage { get; set; } @@ -128,7 +144,7 @@ public partial class Category : BaseEntity, ISoftDeletable, ILocalizedEntity, IS /// Gets or sets a value indicating whether this category has discounts applied /// The same as if we run category.AppliedDiscounts.Count > 0 /// We use this property for performance optimization: - /// if this property is set to false, then we do not need to load Applied Discounts navifation property + /// if this property is set to false, then we do not need to load Applied Discounts navigation property /// /// [DataMember] diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CategoryNode.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CategoryNode.cs new file mode 100644 index 0000000000..c07c9fe69e --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CategoryNode.cs @@ -0,0 +1,40 @@ +using System; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; + +namespace SmartStore.Core.Domain.Catalog +{ + public interface ICategoryNode : ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported + { + int ParentCategoryId { get; } + string Name { get; } + string ExternalLink { get; } + string Alias { get; } + int? PictureId { get; } + bool Published { get; } + int DisplayOrder { get; } + DateTime UpdatedOnUtc { get; } + string BadgeText { get; } + int BadgeStyle { get; } + } + + [Serializable] + public class CategoryNode : ICategoryNode + { + public int Id { get; set; } + public int ParentCategoryId { get; set; } + public string Name { 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; } + public DateTime UpdatedOnUtc { get; set; } + public string BadgeText { get; set; } + public int BadgeStyle { get; set; } + public bool SubjectToAcl { get; set; } + public bool LimitedToStores { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/IPagingOptions.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/IPagingOptions.cs new file mode 100644 index 0000000000..3a4ce6b720 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/IPagingOptions.cs @@ -0,0 +1,11 @@ +using System; + +namespace SmartStore.Core.Domain.Catalog +{ + public interface IPagingOptions + { + int? PageSize { get; } + bool? AllowCustomersToSelectPageSize { get; } + string PageSizeOptions { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs index 88b366c773..823f5e57fb 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs @@ -1,22 +1,27 @@ using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Runtime.Serialization; +using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; -using System.Runtime.Serialization; -using SmartStore.Core.Domain.Media; -using System.ComponentModel.DataAnnotations.Schema; namespace SmartStore.Core.Domain.Catalog { - /// - /// Represents a manufacturer - /// + /// + /// Represents a manufacturer + /// [DataContract] - public partial class Manufacturer : BaseEntity, ISoftDeletable, ILocalizedEntity, ISlugSupported, IStoreMappingSupported - { - /// - /// Gets or sets the name - /// + public partial class Manufacturer : BaseEntity, IAuditable, ISoftDeletable, ILocalizedEntity, ISlugSupported, IStoreMappingSupported, IPagingOptions + { + private ICollection _appliedDiscounts; + + /// + /// Gets or sets the name + /// [DataMember] public string Name { get; set; } @@ -66,13 +71,13 @@ public partial class Manufacturer : BaseEntity, ISoftDeletable, ILocalizedEntity /// Gets or sets the page size /// [DataMember] - public int PageSize { get; set; } + public int? PageSize { get; set; } /// /// Gets or sets a value indicating whether customers can select the page size /// [DataMember] - public bool AllowCustomersToSelectPageSize { get; set; } + public bool? AllowCustomersToSelectPageSize { get; set; } /// /// Gets or sets the available customer selectable page size options @@ -83,7 +88,8 @@ public partial class Manufacturer : BaseEntity, ISoftDeletable, ILocalizedEntity /// /// Gets or sets the available price ranges /// - [DataMember] + [Obsolete("Price ranges are calculated automatically since version 3")] + [StringLength(400)] public string PriceRanges { get; set; } /// @@ -121,5 +127,25 @@ public partial class Manufacturer : BaseEntity, ISoftDeletable, ILocalizedEntity /// [DataMember] public DateTime UpdatedOnUtc { get; set; } - } + + /// + /// Gets or sets a value indicating whether this manufacturer has discounts applied + /// The same as if we run Manufacturer.AppliedDiscounts.Count > 0 + /// We use this property for performance optimization: + /// if this property is set to false, then we do not need to load Applied Discounts navigation property + /// + /// + [DataMember] + public bool HasDiscountsApplied { get; set; } + + /// + /// Gets or sets the collection of applied discounts + /// + [DataMember] + public virtual ICollection AppliedDiscounts + { + get { return _appliedDiscounts ?? (_appliedDiscounts = new HashSet()); } + protected set { _appliedDiscounts = value; } + } + } } 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 999f66bad3..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; @@ -17,9 +18,32 @@ namespace SmartStore.Core.Domain.Catalog /// Represents a product /// [DataContract] - public partial class Product : BaseEntity, ISoftDeletable, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported, IMergedData + 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, ISoftDeletable, ILocalizedEntity, ISl private ICollection _tierPrices; private ICollection _appliedDiscounts; private ICollection _productBundleItems; - - private int _stockQuantity; + + private int _stockQuantity; private int _backorderModeId; private string _sku; private string _gtin; @@ -179,7 +203,7 @@ public string Sku [DebuggerStepThrough] get { - return this.GetMergedDataValue("Sku", _sku); + return this.GetMergedDataValue(nameof(Sku), _sku); } set { @@ -197,7 +221,7 @@ public string ManufacturerPartNumber [DebuggerStepThrough] get { - return this.GetMergedDataValue("ManufacturerPartNumber", _manufacturerPartNumber); + return this.GetMergedDataValue(nameof(ManufacturerPartNumber), _manufacturerPartNumber); } set { @@ -215,7 +239,7 @@ public string Gtin [DebuggerStepThrough] get { - return this.GetMergedDataValue("Gtin", _gtin); + return this.GetMergedDataValue(nameof(Gtin), _gtin); } set { @@ -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 @@ -395,7 +419,7 @@ public int StockQuantity [DebuggerStepThrough] get { - return this.GetMergedDataValue("StockQuantity", _stockQuantity); + return this.GetMergedDataValue(nameof(StockQuantity), _stockQuantity); } set { @@ -442,7 +466,7 @@ public int BackorderModeId [DebuggerStepThrough] get { - return this.GetMergedDataValue("BackorderModeId", _backorderModeId); + return this.GetMergedDataValue(nameof(BackorderModeId), _backorderModeId); } set { @@ -468,10 +492,28 @@ public int BackorderModeId [DataMember] public int OrderMaximumQuantity { get; set; } - /// - /// Gets or sets the comma seperated list of allowed quantities. null or empty if any quantity is allowed - /// - [DataMember] + /// + /// Gets or sets the quantity step + /// + [DataMember] + public int QuantityStep { get; set; } + + /// + /// Gets or sets the quantity control type + /// + [DataMember] + public QuantityControlType QuantiyControlType { get; set; } + + /// + /// Gets or sets a value to specify whether or not to hide the quantity input control + /// + [DataMember] + public bool HideQuantityControl { get; set; } + + /// + /// Gets or sets the comma seperated list of allowed quantities. null or empty if any quantity is allowed + /// + [DataMember] public string AllowedQuantities { get; set; } /// @@ -507,7 +549,7 @@ public decimal Price [DebuggerStepThrough] get { - return this.GetMergedDataValue("Price", _price); + return this.GetMergedDataValue(nameof(Price), _price); } set { @@ -604,7 +646,7 @@ public decimal Length [DebuggerStepThrough] get { - return this.GetMergedDataValue("Length", _length); + return this.GetMergedDataValue(nameof(Length), _length); } set { @@ -621,7 +663,7 @@ public decimal Width [DebuggerStepThrough] get { - return this.GetMergedDataValue("Width", _width); + return this.GetMergedDataValue(nameof(Width), _width); } set { @@ -638,7 +680,7 @@ public decimal Height [DebuggerStepThrough] get { - return this.GetMergedDataValue("Height", _height); + return this.GetMergedDataValue(nameof(Height), _height); } set { @@ -668,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; } @@ -697,7 +756,7 @@ public int? DeliveryTimeId [DebuggerStepThrough] get { - return this.GetMergedDataValue("DeliveryTimeId", _deliveryTimeId); + return this.GetMergedDataValue(nameof(DeliveryTimeId), _deliveryTimeId); } set { @@ -717,7 +776,7 @@ public int? QuantityUnitId [DebuggerStepThrough] get { - return this.GetMergedDataValue("QuantityUnitId", _quantityUnitId); + return this.GetMergedDataValue(nameof(QuantityUnitId), _quantityUnitId); } set { @@ -728,6 +787,23 @@ public int? QuantityUnitId [DataMember] public virtual QuantityUnit QuantityUnit { get; set; } + /// + /// Gets or sets the customs tariff number + /// + [DataMember] + public string CustomsTariffNumber { get; set; } + + /// + /// Gets or sets the country of origin identifier + /// + [DataMember] + public int? CountryOfOriginId { get; set; } + + /// + /// Gets or sets the country of origin + /// + [DataMember] + public virtual Country CountryOfOrigin { get; set; } /// /// Gets or sets if base price quotation (PAnGV) is enabled @@ -751,7 +827,7 @@ public decimal? BasePriceAmount [DebuggerStepThrough] get { - return this.GetMergedDataValue("BasePriceAmount", _basePriceAmount); + return this.GetMergedDataValue(nameof(BasePriceAmount), _basePriceAmount); } set { @@ -769,7 +845,7 @@ public int? BasePriceBaseAmount [DebuggerStepThrough] get { - return this.GetMergedDataValue("BasePriceBaseAmount", _basePriceBaseAmount); + return this.GetMergedDataValue(nameof(BasePriceBaseAmount), _basePriceBaseAmount); } set { @@ -811,9 +887,21 @@ public bool BasePriceHasValue public bool BundlePerItemShoppingCart { get; set; } /// - /// Gets or sets the product type + /// Gets or sets the main picture id + /// + [DataMember] + public int? MainPictureId { get; set; } + + /// + /// 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 @@ -833,7 +921,7 @@ public string ProductTypeLabelHint switch (ProductType) { case ProductType.SimpleProduct: - return "smnet-hide"; + return "secondary d-none"; case ProductType.GroupedProduct: return "success"; case ProductType.BundledProduct: @@ -1048,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 e169ae8fd8..72742f23b3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttribute.cs @@ -1,20 +1,25 @@ -using SmartStore.Core.Domain.Localization; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; namespace SmartStore.Core.Domain.Catalog { - /// - /// Represents a product attribute - /// + /// + /// Represents a product attribute + /// [DataContract] - public partial class ProductAttribute : BaseEntity, ILocalizedEntity - { + public partial class ProductAttribute : BaseEntity, ILocalizedEntity, ISearchAlias + { + private ICollection _productAttributeOptionsSets; + /// /// Gets or sets the product attribute alias - /// (an optional key for advanced customization) /// [DataMember] - public string Alias { get; set; } + public string Alias { get; set; } /// /// Gets or sets the name @@ -27,5 +32,46 @@ public partial class ProductAttribute : BaseEntity, ILocalizedEntity /// [DataMember] public string Description { get; set; } - } + + /// + /// Gets or sets whether the attribute can be filtered + /// + [DataMember] + [Index] + public bool AllowFiltering { get; set; } + + /// + /// Gets or sets the display order + /// + [DataMember] + public int DisplayOrder { get; set; } + + /// + /// Gets or sets the facet template hint. Only effective in accordance with MegaSearchPlus plugin. + /// + [DataMember] + public FacetTemplateHint FacetTemplateHint { get; set; } + + /// + /// Specifies whether option names should be included in the search index. Only effective in accordance with MegaSearchPlus plugin. + /// + [DataMember] + public bool IndexOptionNames { get; set; } + + /// + /// 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()); } + protected set { _productAttributeOptionsSets = value; } + } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttributeOption.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttributeOption.cs new file mode 100644 index 0000000000..7897e23016 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttributeOption.cs @@ -0,0 +1,131 @@ +using System; +using System.Runtime.Serialization; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search; + +namespace SmartStore.Core.Domain.Catalog +{ + /// + /// Represents a product attribute option + /// + [DataContract] + public partial class ProductAttributeOption : BaseEntity, ILocalizedEntity, ISearchAlias, ICloneable + { + /// + /// Gets or sets the product attribute options set identifier + /// + [DataMember] + public int ProductAttributeOptionsSetId { get; set; } + + /// + /// Gets or sets the option alias + /// + [DataMember] + public string Alias { get; set; } + + /// + /// Gets or sets the option name + /// + [DataMember] + public string Name { get; set; } + + /// + /// Gets or sets the Picture Id + /// + [DataMember] + public int PictureId { get; set; } + + /// + /// Gets or sets the color RGB value (used with "Boxes" attribute type) + /// + [DataMember] + public string Color { get; set; } + + /// + /// Gets or sets the price adjustment + /// + [DataMember] + public decimal PriceAdjustment { get; set; } + + /// + /// Gets or sets the weight adjustment + /// + [DataMember] + public decimal WeightAdjustment { get; set; } + + /// + /// Gets or sets a value indicating whether the option is pre-selected + /// + [DataMember] + public bool IsPreSelected { get; set; } + + /// + /// Gets or sets the display order + /// + [DataMember] + public int DisplayOrder { get; set; } + + /// + /// Gets or sets the type Id + /// + [DataMember] + public int ValueTypeId { get; set; } + + /// + /// Gets or sets the linked product Id + /// + [DataMember] + public int LinkedProductId { get; set; } + + /// + /// Gets or sets the quantity for the linked product + /// + [DataMember] + public int Quantity { get; set; } + + /// + /// Gets the product attribute options set + /// + [DataMember] + public virtual ProductAttributeOptionsSet ProductAttributeOptionsSet { get; set; } + + /// + /// Gets or sets the product attribute value type + /// + [DataMember] + public ProductVariantAttributeValueType ValueType + { + get + { + return (ProductVariantAttributeValueType)this.ValueTypeId; + } + set + { + this.ValueTypeId = (int)value; + } + } + + public ProductVariantAttributeValue Clone() + { + var value = new ProductVariantAttributeValue(); + value.Alias = Alias; + value.Name = Name; + value.PictureId = PictureId; + value.Color = Color; + value.PriceAdjustment = PriceAdjustment; + value.WeightAdjustment = WeightAdjustment; + value.IsPreSelected = IsPreSelected; + value.DisplayOrder = DisplayOrder; + value.ValueTypeId = ValueTypeId; + value.LinkedProductId = LinkedProductId; + value.Quantity = Quantity; + + return value; + } + + object ICloneable.Clone() + { + return this.Clone(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttributeOptionsSet.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttributeOptionsSet.cs new file mode 100644 index 0000000000..c6819334d2 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttributeOptionsSet.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SmartStore.Core.Domain.Catalog +{ + [DataContract] + public partial class ProductAttributeOptionsSet : BaseEntity + { + private ICollection _productAttributeOptions; + + /// + /// Gets or sets the name + /// + [DataMember] + public string Name { get; set; } + + /// + /// Gets or sets the product attribute identifier + /// + [DataMember] + public int ProductAttributeId { get; set; } + + /// + /// Gets the product attribute + /// + [DataMember] + public virtual ProductAttribute ProductAttribute { get; set; } + + /// + /// Gets or sets the prooduct attribute options + /// + [DataMember] + public virtual ICollection ProductAttributeOptions + { + get { return _productAttributeOptions ?? (_productAttributeOptions = new HashSet()); } + protected set { _productAttributeOptions = value; } + } + } +} 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/ProductBundleItem.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleItem.cs index ea28857ec8..38865d9b70 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleItem.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleItem.cs @@ -6,7 +6,7 @@ namespace SmartStore.Core.Domain.Catalog { [DataContract] - public partial class ProductBundleItem : BaseEntity, ILocalizedEntity, ICloneable + public partial class ProductBundleItem : BaseEntity, IAuditable, ILocalizedEntity, ICloneable { private ICollection _attributeFilters; @@ -117,7 +117,7 @@ public virtual ICollection AttributeFilters public ProductBundleItem Clone() { - var bundleItem = new ProductBundleItem() + var bundleItem = new ProductBundleItem { ProductId = this.ProductId, BundleProductId = this.BundleProductId, diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs index 28a12a7948..ee5adfe594 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Catalog @@ -24,6 +25,7 @@ public partial class ProductCategory : BaseEntity /// Gets or sets a value indicating whether the product is featured /// [DataMember] + [Index] public bool IsFeaturedProduct { get; set; } /// @@ -31,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 43a445001b..73edaec790 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductManufacturer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductManufacturer.cs @@ -1,9 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; + namespace SmartStore.Core.Domain.Catalog { - /// - /// Represents a product manufacturer mapping - /// + /// + /// Represents a product manufacturer mapping + /// [DataContract] public partial class ProductManufacturer : BaseEntity { @@ -23,6 +25,7 @@ public partial class ProductManufacturer : BaseEntity /// Gets or sets a value indicating whether the product is featured /// [DataMember] + [Index] public bool IsFeaturedProduct { get; set; } /// @@ -34,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 8db56d81b4..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, - /// - /// Position (display order) - /// - Position = 1, + + /// + /// 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 8facecd692..eb8595b3af 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSpecificationAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSpecificationAttribute.cs @@ -20,17 +20,17 @@ public partial class ProductSpecificationAttribute : BaseEntity [DataMember] public int SpecificationAttributeOptionId { get; set; } - /// - /// Gets or sets whether the attribute can be filtered by - /// + /// + /// Gets or sets whether the attribute can be filtered. Only effective in accordance with MegaSearchPlus plugin. + /// [DataMember] - public bool AllowFiltering { get; set; } + public bool? AllowFiltering { get; set; } /// - /// Gets or sets whether the attrbiute will be shown on the product page + /// Gets or sets whether the attribute will be shown on the product page. /// [DataMember] - public bool ShowOnProductPage { get; set; } + public bool? ShowOnProductPage { get; set; } /// /// Gets or sets the display order @@ -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/ProductVariantAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttribute.cs index c6ca76e9d2..96a9e5367b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttribute.cs @@ -10,7 +10,7 @@ namespace SmartStore.Core.Domain.Catalog /// [DataContract] public partial class ProductVariantAttribute : BaseEntity, ILocalizedEntity - { + { private ICollection _productVariantAttributeValues; /// @@ -42,6 +42,7 @@ public partial class ProductVariantAttribute : BaseEntity, ILocalizedEntity /// Gets or sets the attribute control type identifier /// [DataMember] + [Index] public int AttributeControlTypeId { get; set; } /// @@ -67,6 +68,20 @@ public AttributeControlType AttributeControlType } } + public bool IsListTypeAttribute() + { + switch (AttributeControlType) + { + case AttributeControlType.Checkboxes: + case AttributeControlType.Boxes: + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + return true; + default: + return false; + } + } + /// /// Gets the product attribute /// diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttributeValue.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttributeValue.cs index dabf57aadf..f6a9c32444 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttributeValue.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductVariantAttributeValue.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search; namespace SmartStore.Core.Domain.Catalog { @@ -8,8 +9,8 @@ namespace SmartStore.Core.Domain.Catalog /// Represents a product variant attribute value /// [DataContract] - public partial class ProductVariantAttributeValue : BaseEntity, ILocalizedEntity - { + public partial class ProductVariantAttributeValue : BaseEntity, ILocalizedEntity, ISearchAlias + { /// /// Gets or sets the product variant attribute mapping identifier /// @@ -19,7 +20,6 @@ public partial class ProductVariantAttributeValue : BaseEntity, ILocalizedEntity /// /// Gets or sets the product variant attribute alias - /// (an optional key for advanced customization) /// [DataMember] public string Alias { get; set; } @@ -28,13 +28,20 @@ public partial class ProductVariantAttributeValue : BaseEntity, ILocalizedEntity /// Gets or sets the product variant attribute name ///
[DataMember] + [Index] public string Name { get; set; } /// - /// Gets or sets the color RGB value (used with "Color squares" attribute type) + /// Gets or sets the Picture Id /// [DataMember] - public virtual string ColorSquaresRgb { get; set; } + public int PictureId { get; set; } + + /// + /// Gets or sets the color RGB value (used with "Boxes" attribute type) + /// + [DataMember] + public string Color { get; set; } /// /// Gets or sets the price adjustment @@ -65,6 +72,7 @@ public partial class ProductVariantAttributeValue : BaseEntity, ILocalizedEntity /// Gets or sets the type Id /// [DataMember] + [Index] public int ValueTypeId { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/QuantiyControlType.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/QuantiyControlType.cs new file mode 100644 index 0000000000..636bb0e66f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/QuantiyControlType.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Core.Domain.Catalog +{ + /// + /// Represents a quantity control type + /// + public enum QuantityControlType + { + /// + /// Spinner + /// + Spinner = 0, + + /// + /// Dropdown + /// + Dropdown = 1 + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttribute.cs index d8a42e6ccf..c5dd44386d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttribute.cs @@ -1,15 +1,18 @@ using System.Collections.Generic; -using SmartStore.Core.Domain.Localization; +using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; namespace SmartStore.Core.Domain.Catalog { - /// - /// Represents a specification attribute - /// + /// + /// Represents a specification attribute + /// [DataContract] - public partial class SpecificationAttribute : BaseEntity, ILocalizedEntity - { + public partial class SpecificationAttribute : BaseEntity, ILocalizedEntity, ISearchAlias + { private ICollection _specificationAttributeOptions; /// @@ -18,16 +21,53 @@ public partial class SpecificationAttribute : BaseEntity, ILocalizedEntity [DataMember] public string Name { get; set; } - /// - /// Gets or sets the display order - /// + /// + /// Gets or sets the specification attribute alias + /// + [DataMember] + public string Alias { get; set; } + + /// + /// Gets or sets the display order + /// [DataMember] public int DisplayOrder { get; set; } + /// + /// Gets or sets whether the specification attribute will be shown on the product page. + /// + [DataMember] + public bool ShowOnProductPage { get; set; } + + /// + /// Gets or sets whether the specification attribute can be filtered. Only effective in accordance with MegaSearchPlus plugin. + /// + [DataMember] + [Index] + public bool AllowFiltering { get; set; } + + /// + /// Gets or sets the sorting of facets. Only effective in accordance with MegaSearchPlus plugin. + /// + [DataMember] + public FacetSorting FacetSorting { get; set; } + + /// + /// Gets or sets the facet template hint. Only effective in accordance with MegaSearchPlus plugin. + /// + [DataMember] + public FacetTemplateHint FacetTemplateHint { get; set; } + + /// + /// Specifies whether option names should be included in the search index. Only effective in accordance with MegaSearchPlus plugin. + /// + [DataMember] + public bool IndexOptionNames { get; set; } + /// /// Gets or sets the specification attribute options /// - [DataMember] + [DataMember] public virtual ICollection SpecificationAttributeOptions { get { return _specificationAttributeOptions ?? (_specificationAttributeOptions = new HashSet()); } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs index e3019778e6..47b5a55cef 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs @@ -1,15 +1,16 @@ using System.Collections.Generic; -using SmartStore.Core.Domain.Localization; using System.Runtime.Serialization; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search; namespace SmartStore.Core.Domain.Catalog { - /// - /// Represents a specification attribute option - /// + /// + /// Represents a specification attribute option + /// [DataContract] - public partial class SpecificationAttributeOption : BaseEntity, ILocalizedEntity - { + public partial class SpecificationAttributeOption : BaseEntity, ILocalizedEntity, ISearchAlias + { private ICollection _productSpecificationAttributes; /// @@ -24,20 +25,32 @@ public partial class SpecificationAttributeOption : BaseEntity, ILocalizedEntity [DataMember] public string Name { get; set; } - /// - /// Gets or sets the display order - /// + /// + /// Gets or sets the specification attribute option alias + /// + [DataMember] + public string Alias { get; set; } + + /// + /// Gets or sets the display order + /// [DataMember] public int DisplayOrder { get; set; } - - /// - /// Gets or sets the specification attribute - /// + + /// + /// Gets or sets the number value for range filtering + /// + [DataMember] + public decimal NumberValue { get; set; } + + /// + /// Gets or sets the specification attribute + /// [DataMember] 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/Catalog/TierPrice.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/TierPrice.cs index e8baac9503..7186b46490 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/TierPrice.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/TierPrice.cs @@ -42,11 +42,24 @@ public partial class TierPrice : BaseEntity /// /// Gets or sets the product /// - public virtual Product Product { get; set; } + [DataMember] + public TierPriceCalculationMethod CalculationMethod { get; set; } + + /// + /// Gets or sets the product + /// + public virtual Product Product { get; set; } /// /// Gets or sets the customer role /// public virtual CustomerRole CustomerRole { get; set; } } + + public enum TierPriceCalculationMethod + { + Fixed = 0, + Percental = 5, + Adjustment = 10 + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Cms/ContentSliderSettings.cs b/src/Libraries/SmartStore.Core/Domain/Cms/ContentSliderSettings.cs deleted file mode 100644 index 0c41b452a7..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/Cms/ContentSliderSettings.cs +++ /dev/null @@ -1,74 +0,0 @@ -using SmartStore.Core.Configuration; -using System.Collections.Generic; -using System.Web.Mvc; -using Newtonsoft.Json; - -namespace SmartStore.Core.Domain.Cms -{ - [JsonPersist] - public class ContentSliderSettings : ISettings - { - public ContentSliderSettings() - { - Slides = new List(); - IsActive = true; - ContentSliderHeight = "300px"; - } - - public bool IsActive { get; set; } - - public string ContentSliderHeight { get; set; } - - public int BackgroundPictureId { get; set; } - - [JsonIgnore] - public string BackgroundPictureUrl { get; set; } - - public bool AutoPlay { get; set; } - - public int AutoPlayDelay { get; set; } - - public IList Slides { get; set; } - } - - public class ContentSliderSlideSettings : ISettings - { - public int DisplayOrder { get; set; } - - public string Title { get; set; } - - public string Text { get; set; } - - public int PictureId { get; set; } - - [JsonIgnore] - public string PictureUrl { get; set; } - - [JsonIgnore] - public string LanguageName { get; set; } - - public string LanguageCulture { get; set; } - - public bool Published { get; set; } - - public bool LimitedToStores { get; set; } - public int[] SelectedStoreIds { get; set; } - - public ContentSliderButtonSettings Button1 { get; set; } - - public ContentSliderButtonSettings Button2 { get; set; } - - public ContentSliderButtonSettings Button3 { get; set; } - } - - public class ContentSliderButtonSettings : ISettings - { - public string Text { get; set; } - - public string Type { get; set; } - - public string Url { get; set; } - - public bool Published { get; set; } - } -} \ No newline at end of file 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/Address.cs b/src/Libraries/SmartStore.Core/Domain/Common/Address.cs index ec942ed6de..c4723d0e6e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/Address.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/Address.cs @@ -109,7 +109,6 @@ public class Address : BaseEntity, ICloneable [DataMember] public virtual StateProvince StateProvince { get; set; } - public object Clone() { var addr = new Address() @@ -134,5 +133,18 @@ public object Clone() }; return addr; } + + public static string DefaultAddressFormat + { + get + { + return @"{{ Salutation }} {{ Title }} {{ FirstName }} {{ LastName }} +{{ Company }} +{{ Street1 }} +{{ Street2 }} +{{ ZipCode }} {{ City }} +{{ Country | Upcase }}"; + } + } } } 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 84ceca5428..523a5dbe52 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs @@ -7,14 +7,12 @@ public class CommonSettings : ISettings public CommonSettings() { UseStoredProceduresIfSupported = true; - SitemapEnabled = true; - SitemapIncludeCategories = true; - SitemapIncludeManufacturers = true; - SitemapIncludeTopics = true; - FullTextMode = FulltextSearchMode.ExactMatch; AutoUpdateEnabled = true; EntityPickerPageSize = 48; - } + MaxScheduleHistoryAgeInDays = 30; + MaxNumberOfScheduleHistoryEntries = 100; + MaxQueuedMessagesAgeInDays = 14; + } public bool UseSystemEmailForContactUsForm { get; set; } @@ -22,32 +20,31 @@ public CommonSettings() public bool HideAdvertisementsOnAdminArea { get; set; } - public bool SitemapEnabled { get; set; } - public bool SitemapIncludeCategories { get; set; } - public bool SitemapIncludeManufacturers { get; set; } - public bool SitemapIncludeProducts { get; set; } - public bool SitemapIncludeTopics { get; set; } - /// /// Gets or sets a value indicating whether to display a warning if java-script is disabled /// public bool DisplayJavaScriptDisabledWarning { get; set; } - /// - /// Gets or sets a value indicating whether to full-text search is supported - /// - public bool UseFullTextSearch { get; set; } - - /// - /// Gets or sets a Full-Text search mode - /// - public FulltextSearchMode FullTextMode { get; set; } - public bool AutoUpdateEnabled { get; set; } /// /// 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/CompanyInformationSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/CompanyInformationSettings.cs index 43c6a211cb..9c6430e25a 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CompanyInformationSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CompanyInformationSettings.cs @@ -94,6 +94,5 @@ public class CompanyInformationSettings : ISettings /// Gets or sets the tax number that will be used /// public string TaxNumber { get; set; } - } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Common/FulltextSearchMode.cs b/src/Libraries/SmartStore.Core/Domain/Common/FulltextSearchMode.cs deleted file mode 100644 index ab43c1c3ee..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/Common/FulltextSearchMode.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace SmartStore.Core.Domain.Common -{ - /// - /// Represents a Full-Text search mode - /// - public enum FulltextSearchMode - { - /// - /// Exact match (using CONTAINS with prefix_term) - /// - ExactMatch = 0, - /// - /// Using CONTAINS and OR with prefix_term - /// - Or = 5, - /// - /// Using CONTAINS and AND with prefix_term - /// - And = 10 - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Common/GenericAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Common/GenericAttribute.cs index ebfc2a6044..931d8fbb51 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/GenericAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/GenericAttribute.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Common { @@ -22,7 +23,7 @@ public partial class GenericAttribute : BaseEntity /// /// Gets or sets the key /// - [DataMember] + [DataMember, Index("IX_GenericAttribute_Key")] public string Key { get; set; } /// 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 7fd9c789ac..1a45382602 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/SocialSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/SocialSettings.cs @@ -4,43 +4,34 @@ namespace SmartStore.Core.Domain.Common { public class SocialSettings : ISettings { - public SocialSettings() - { - ShowSocialLinksInFooter = true; - FacebookLink = "#"; - GooglePlusLink = "#"; - TwitterLink = "#"; - PinterestLink = "#"; - } - - /// + /// /// 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; } = "#"; } } \ 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 e55f57bd4f..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; @@ -123,13 +124,13 @@ public PasswordFormat PasswordFormat /// /// Gets or sets the last IP address /// - [DataMember] + [DataMember, Index("IX_Customer_LastIpAddress")] public string LastIpAddress { get; set; } /// /// Gets or sets the date and time of entity creation /// - [DataMember] + [DataMember, Index("IX_Customer_CreatedOn")] public DateTime CreatedOnUtc { get; set; } /// @@ -141,15 +142,41 @@ public PasswordFormat PasswordFormat /// /// Gets or sets the date and time of last activity /// - [DataMember] + [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; } @@ -164,10 +191,11 @@ public virtual ICollection CustomerContent protected set { _customerContent = value; } } - /// - /// Gets or sets the customer roles - /// - public virtual ICollection CustomerRoles + /// + /// Gets or sets the customer roles + /// + [DataMember] + public virtual ICollection CustomerRoles { get { return _customerRoles ?? (_customerRoles = new HashSet()); } protected set { _customerRoles = value; } @@ -201,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 /// @@ -250,56 +293,66 @@ public virtual ICollection ForumPosts get { return _forumPosts ?? (_forumPosts = new HashSet()); } protected set { _forumPosts = value; } } - - #endregion - - #region Addresses - - public virtual void RemoveAddress(Address address) - { - if (this.Addresses.Contains(address)) - { - if (this.BillingAddress == address) this.BillingAddress = null; - if (this.ShippingAddress == address) this.ShippingAddress = null; - - this.Addresses.Remove(address); - } - } - #endregion + #endregion - #region Reward points + #region Utils - public void AddRewardPointsHistoryEntry(int points, string message = "", - Order usedWithOrder = null, decimal usedAmount = 0M) - { - int newPointsBalance = this.GetRewardPointsBalance() + points; - - var rewardPointsHistory = new RewardPointsHistory() - { - Customer = this, - UsedWithOrder = usedWithOrder, - Points = points, - PointsBalance = newPointsBalance, - UsedAmount = usedAmount, - Message = message, - CreatedOnUtc = DateTime.UtcNow - }; - - this.RewardPointsHistory.Add(rewardPointsHistory); - } - /// - /// Gets reward points balance - /// - public int GetRewardPointsBalance() - { - int result = 0; - if (this.RewardPointsHistory.Count > 0) - result = this.RewardPointsHistory.OrderByDescending(rph => rph.CreatedOnUtc).ThenByDescending(rph => rph.Id).FirstOrDefault().PointsBalance; - return result; - } + /// + /// Gets a string identifier for the customer's roles by joining all role ids + /// + /// true ignores all inactive roles + /// The identifier + public string GetRolesIdent(bool onlyActiveCustomerRoles = true) + { + return string.Join(",", this.CustomerRoles.Where(x => !onlyActiveCustomerRoles || x.Active).Select(x => x.Id)); + } + + public virtual void RemoveAddress(Address address) + { + if (this.Addresses.Contains(address)) + { + if (this.BillingAddress == address) this.BillingAddress = null; + if (this.ShippingAddress == address) this.ShippingAddress = null; + + this.Addresses.Remove(address); + } + } + + public void AddRewardPointsHistoryEntry( + int points, + string message = "", + Order usedWithOrder = null, + decimal usedAmount = 0M) + { + int newPointsBalance = this.GetRewardPointsBalance() + points; + + var rewardPointsHistory = new RewardPointsHistory() + { + Customer = this, + UsedWithOrder = usedWithOrder, + Points = points, + PointsBalance = newPointsBalance, + UsedAmount = usedAmount, + Message = message, + CreatedOnUtc = DateTime.UtcNow + }; + + this.RewardPointsHistory.Add(rewardPointsHistory); + } - #endregion - } + /// + /// Gets reward points balance + /// + public int GetRewardPointsBalance() + { + int result = 0; + if (this.RewardPointsHistory.Count > 0) + result = this.RewardPointsHistory.OrderByDescending(rph => rph.CreatedOnUtc).ThenByDescending(rph => rph.Id).FirstOrDefault().PointsBalance; + return result; + } + + #endregion + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerContent.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerContent.cs index 370cd655d0..1041f57d17 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerContent.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerContent.cs @@ -1,41 +1,48 @@ - using System; +using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Customers { - /// - /// Represents a customer generated content - /// - public partial class CustomerContent : BaseEntity - { - /// - /// Gets or sets the customer identifier - /// - public int CustomerId { get; set; } + /// + /// Represents a customer generated content + /// + [DataContract] + public partial class CustomerContent : BaseEntity, IAuditable + { + /// + /// Gets or sets the customer identifier + /// + [DataMember] + public int CustomerId { get; set; } - /// - /// Gets or sets the IP address - /// - public string IpAddress { get; set; } + /// + /// Gets or sets the IP address + /// + [DataMember] + public string IpAddress { get; set; } - /// - /// Gets or sets a value indicating whether the content is approved - /// - public bool IsApproved { get; set; } + /// + /// Gets or sets a value indicating whether the content is approved + /// + [DataMember] + public bool IsApproved { get; set; } - /// - /// Gets or sets the date and time of instance creation - /// - public DateTime CreatedOnUtc { get; set; } + /// + /// Gets or sets the date and time of instance creation + /// + [DataMember] + public DateTime CreatedOnUtc { get; set; } - /// - /// Gets or sets the date and time of instance update - /// - public DateTime UpdatedOnUtc { get; set; } + /// + /// Gets or sets the date and time of instance update + /// + [DataMember] + public DateTime UpdatedOnUtc { get; set; } - /// - /// Gets or sets the customer - /// - public virtual Customer Customer { get; set; } + /// + /// Gets or sets the customer + /// + [DataMember] + public virtual Customer Customer { get; set; } } } 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/CustomerRole.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs index 6d3b2003e8..beb22d1ae3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerRole.cs @@ -1,50 +1,58 @@ using System.Collections.Generic; +using System.Runtime.Serialization; using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Tax; namespace SmartStore.Core.Domain.Customers { - /// - /// Represents a customer role - /// - public partial class CustomerRole : BaseEntity + /// + /// Represents a customer role + /// + [DataContract] + public partial class CustomerRole : BaseEntity { private ICollection _permissionRecords; - /// - /// Gets or sets the customer role name - /// - public string Name { get; set; } + /// + /// Gets or sets the customer role name + /// + [DataMember] + public string Name { get; set; } - /// - /// Gets or sets a value indicating whether the customer role is marked as free shiping - /// - public bool FreeShipping { get; set; } + /// + /// Gets or sets a value indicating whether the customer role is marked as free shiping + /// + [DataMember] + public bool FreeShipping { get; set; } - /// - /// Gets or sets a value indicating whether the customer role is marked as tax exempt - /// - public bool TaxExempt { get; set; } + /// + /// Gets or sets a value indicating whether the customer role is marked as tax exempt + /// + [DataMember] + public bool TaxExempt { get; set; } - /// - /// Gets or sets the tax display type - /// - public int? TaxDisplayType { get; set; } + /// + /// Gets or sets the tax display type + /// + [DataMember] + public int? TaxDisplayType { get; set; } - /// - /// Gets or sets a value indicating whether the customer role is active - /// - public bool Active { get; set; } + /// + /// Gets or sets a value indicating whether the customer role is active + /// + [DataMember] + public bool Active { get; set; } - /// - /// Gets or sets a value indicating whether the customer role is system - /// - public bool IsSystemRole { get; set; } + /// + /// Gets or sets a value indicating whether the customer role is system + /// + [DataMember] + public bool IsSystemRole { get; set; } - /// - /// Gets or sets the customer role system name - /// - public string SystemName { get; set; } + /// + /// Gets or sets the customer role system name + /// + [DataMember] + public string SystemName { get; set; } /// @@ -56,5 +64,4 @@ public virtual ICollection PermissionRecords protected set { _permissionRecords = value; } } } - } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index ce4671c6e2..20033f0c21 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -1,21 +1,19 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Customers { - public class CustomerSettings : ISettings + 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 = 20000; - DefaultAvatarEnabled = true; + AvatarMaximumSizeBytes = 512000; CustomerNameFormat = CustomerNameFormat.ShowFirstName; CustomerNameFormatMaxLength = 64; GenderEnabled = true; @@ -24,13 +22,14 @@ public CustomerSettings() NewsletterEnabled = true; OnlineCustomerMinutes = 20; StoreLastVisitedPage = 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,11 +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 display a checkbox to the customer where he can agree to privacy terms - /// - public bool DisplayPrivacyAgreementOnContactUs { get; set; } #region Form fields @@ -169,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 @@ -256,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 1b8943727b..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"; } } @@ -47,9 +45,9 @@ public static partial class SystemCustomerAttributeNames public static string OfferedShippingOptions { get { return "OfferedShippingOptions"; } } public static string LastContinueShoppingPage { get { return "LastContinueShoppingPage"; } } public static string NotifiedAboutNewPrivateMessages { get { return "NotifiedAboutNewPrivateMessages"; } } - public static string WorkingDesktopThemeName { get { return "WorkingDesktopThemeName"; } } - public static string DontUseMobileVersion { get { return "DontUseMobileVersion"; } } + 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 6ea68ec73c..4bd1648beb 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -12,13 +12,24 @@ public enum ExportEntityType Manufacturer, Customer, Order, - NewsLetterSubscription - } - - /// - /// Supported deployment types - /// - public enum ExportDeploymentType + NewsLetterSubscription, + ShoppingCartItem + } + + /// + /// 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, @@ -131,7 +142,17 @@ public enum ExportFeatures /// /// Whether to provide additional data of attribute combinations /// - UsesAttributeCombination = 1 << 13 + UsesAttributeCombination = 1 << 13, + + /// + /// Whether to export attribute combinations as products including parent product. Only effective with CanProjectAttributeCombinations. + /// + 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 23d08fd03c..476c1a5b8c 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -170,6 +170,20 @@ public class ExportFilter /// public bool? IsActiveSubscriber { get; set; } + /// + /// Filter by language + /// + public int? WorkingLanguageId { get; set; } + + #endregion + + #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/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index 493acda473..fd41da5fb4 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -13,6 +13,11 @@ namespace SmartStore.Core.Domain.DataExchange [Serializable] public class ExportProjection { + public ExportProjection() + { + OnlyIndividuallyVisibleAssociated = true; + } + #region All entity types /// @@ -151,6 +156,11 @@ public ExportAttributeValueMerging AttributeCombinationValueMerging /// public bool NoGroupedProducts { get; set; } + /// + /// Whether to export associated products that marked as "visible individually". false to load all records, true to load "visible individually" only + /// + public bool OnlyIndividuallyVisibleAssociated { get; set; } + #endregion #region Order @@ -177,5 +187,14 @@ public ExportOrderStatusChange OrderStatusChange } #endregion + + #region Shopping Cart Item + + /// + /// Whether to export bundle products + /// + public bool NoBundleProducts { get; set; } + + #endregion } } 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/DataExchange/SyncMapping.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/SyncMapping.cs index a297bc8f85..d8067400cb 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/SyncMapping.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/SyncMapping.cs @@ -5,6 +5,7 @@ using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; +using SmartStore.Core.Data.Hooks; namespace SmartStore.Core.Domain.DataExchange { @@ -12,6 +13,7 @@ namespace SmartStore.Core.Domain.DataExchange /// Holds info about a synchronization operation with an external system /// [DataContract] + [Hookable(false)] public partial class SyncMapping : BaseEntity { public SyncMapping() diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs b/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs index 117a487a07..45c1dacca7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/Country.cs @@ -1,20 +1,18 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Directory { - /// - /// Represents a country - /// + /// + /// Represents a country + /// [DataContract] public partial class Country : BaseEntity, ILocalizedEntity, IStoreMappingSupported { private ICollection _stateProvinces; - private ICollection _restrictedShippingMethods; - /// /// Gets or sets the name @@ -75,24 +73,20 @@ public partial class Country : BaseEntity, ILocalizedEntity, IStoreMappingSuppor /// [DataMember] public bool LimitedToStores { get; set; } - - /// - /// Gets or sets the state/provinces - /// - public virtual ICollection StateProvinces + + /// + /// Gets or sets the international mailing address format + /// + [DataMember, MaxLength] + public string AddressFormat { get; set; } + + /// + /// Gets or sets the state/provinces + /// + public virtual ICollection StateProvinces { get { return _stateProvinces ?? (_stateProvinces = new HashSet()); } protected set { _stateProvinces = value; } } - - /// - /// Gets or sets the restricted shipping methods - /// - public virtual ICollection RestrictedShippingMethods - { - get { return _restrictedShippingMethods ?? (_restrictedShippingMethods = new HashSet()); } - protected set { _restrictedShippingMethods = value; } - } } - } diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs b/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs index b985cd4854..048486e36b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs @@ -1,7 +1,10 @@ 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; -using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Directory { @@ -9,8 +12,13 @@ namespace SmartStore.Core.Domain.Directory /// Represents a currency /// [DataContract] - public partial class Currency : BaseEntity, ILocalizedEntity, IStoreMappingSupported + public partial class Currency : BaseEntity, IAuditable, ILocalizedEntity, IStoreMappingSupported { + public Currency() + { + RoundNumDecimals = 2; + } + /// /// Gets or sets the name /// @@ -18,7 +26,7 @@ public partial class Currency : BaseEntity, ILocalizedEntity, IStoreMappingSuppo public string Name { get; set; } /// - /// Gets or sets the currency code + /// Gets or sets the ISO currency code /// [DataMember] public string CurrencyCode { get; set; } @@ -76,5 +84,68 @@ public partial class Currency : BaseEntity, ILocalizedEntity, IStoreMappingSuppo ///
[DataMember] public string DomainEndings { get; set; } - } + + #region Rounding + + /// + /// Gets or sets a value indicating whether rounding of order items is enabled + /// + [DataMember] + public bool RoundOrderItemsEnabled { get; set; } + + /// + /// Gets or sets the number of decimal places to round to + /// + [DataMember] + public int RoundNumDecimals { get; set; } + + /// + /// Gets or sets a value indicating whether to round the order total + /// + [DataMember] + public bool RoundOrderTotalEnabled { get; set; } + + /// + /// Gets or sets the smallest denomination. The order total is rounded to the nearest multiple of it. + /// + [DataMember] + public decimal RoundOrderTotalDenominator { get; set; } + + /// + /// Gets or sets the order total rounding rule. + /// + [DataMember] + public CurrencyRoundingRule RoundOrderTotalRule { get; set; } + + #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/CurrencyRoundingRule.cs b/src/Libraries/SmartStore.Core/Domain/Directory/CurrencyRoundingRule.cs new file mode 100644 index 0000000000..06b80a4f24 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Directory/CurrencyRoundingRule.cs @@ -0,0 +1,25 @@ +namespace SmartStore.Core.Domain.Directory +{ + public enum CurrencyRoundingRule + { + /// + /// E.g. denomination 0.05: 9.225 will round to 9.20 + /// + RoundMidpointDown = 0, + + /// + /// E.g. denomination 0.05: 9.225 will round to 9.25 + /// + RoundMidpointUp, + + /// + /// E.g. denomination 0.05: 9.24 will round to 9.20 + /// + AlwaysRoundDown, + + /// + /// E.g. denomination 0.05: 9.26 will round to 9.30 + /// + AlwaysRoundUp + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/DeliveryTime.cs b/src/Libraries/SmartStore.Core/Domain/Directory/DeliveryTime.cs index 8918b39b4f..33f66da021 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/DeliveryTime.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/DeliveryTime.cs @@ -34,6 +34,10 @@ public partial class DeliveryTime : BaseEntity, ILocalizedEntity [DataMember] public int DisplayOrder { get; set; } + /// + /// Gets or sets the display order + /// + [DataMember] + public bool? IsDefault { get; set; } } - } diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/ExchangeRate.cs b/src/Libraries/SmartStore.Core/Domain/Directory/ExchangeRate.cs index b8d1c9f989..271ac9b2fc 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/ExchangeRate.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/ExchangeRate.cs @@ -3,9 +3,9 @@ namespace SmartStore.Core.Domain.Directory { - /// - /// Represents an exchange rate - /// + /// + /// Represents an exchange rate + /// [DebuggerDisplay("{CurrencyCode} {Rate}")] public partial class ExchangeRate { @@ -14,25 +14,34 @@ public partial class ExchangeRate ///
public ExchangeRate() { + Name = string.Empty; CurrencyCode = string.Empty; Rate = 1.0m; } - - /// - /// The three letter ISO code for the Exchange Rate, e.g. USD - /// - public string CurrencyCode { get; set; } - /// - /// The conversion rate of this currency from the base currency - /// - public decimal Rate { get; set; } + /// + /// Name of the currrency + /// + public string Name { get; set; } + + /// + /// The three letter ISO code for the Exchange Rate, e.g. USD + /// + public string CurrencyCode { get; set; } + + /// + /// Indicates whether the currency is available in the store + /// + public bool IsStoreCurrency { get; set; } + + /// + /// The conversion rate of this currency from the base currency + /// + public decimal Rate { get; set; } /// /// When was this exchange rate updated from the data source (the internet data xml feed) /// public DateTime UpdatedOn { get; set; } - - } - + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/MeasureDimension.cs b/src/Libraries/SmartStore.Core/Domain/Directory/MeasureDimension.cs index 87a37d30d1..dfd825307d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/MeasureDimension.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/MeasureDimension.cs @@ -1,28 +1,35 @@ +using System.Runtime.Serialization; + namespace SmartStore.Core.Domain.Directory { - /// - /// Represents a measure dimension - /// - public partial class MeasureDimension : BaseEntity + /// + /// Represents a measure dimension + /// + [DataContract] + public partial class MeasureDimension : BaseEntity { - /// - /// Gets or sets the name - /// - public string Name { get; set; } + /// + /// Gets or sets the name + /// + [DataMember] + public string Name { get; set; } - /// - /// Gets or sets the system keyword - /// - public string SystemKeyword { get; set; } + /// + /// Gets or sets the system keyword + /// + [DataMember] + public string SystemKeyword { get; set; } - /// - /// Gets or sets the ratio - /// - public decimal Ratio { get; set; } + /// + /// Gets or sets the ratio + /// + [DataMember] + public decimal Ratio { get; set; } - /// - /// Gets or sets the display order - /// - public int DisplayOrder { get; set; } + /// + /// Gets or sets the display order + /// + [DataMember] + public int DisplayOrder { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/MeasureWeight.cs b/src/Libraries/SmartStore.Core/Domain/Directory/MeasureWeight.cs index 973e62a417..ec9b31da59 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/MeasureWeight.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/MeasureWeight.cs @@ -1,28 +1,35 @@ +using System.Runtime.Serialization; + namespace SmartStore.Core.Domain.Directory { - /// - /// Represents a measure weight - /// - public partial class MeasureWeight : BaseEntity + /// + /// Represents a measure weight + /// + [DataContract] + public partial class MeasureWeight : BaseEntity { - /// - /// Gets or sets the name - /// - public string Name { get; set; } + /// + /// Gets or sets the name + /// + [DataMember] + public string Name { get; set; } - /// - /// Gets or sets the system keyword - /// - public string SystemKeyword { get; set; } + /// + /// Gets or sets the system keyword + /// + [DataMember] + public string SystemKeyword { get; set; } - /// - /// Gets or sets the ratio - /// - public decimal Ratio { get; set; } + /// + /// Gets or sets the ratio + /// + [DataMember] + public decimal Ratio { get; set; } - /// - /// Gets or sets the display order - /// - public int DisplayOrder { get; set; } + /// + /// Gets or sets the display order + /// + [DataMember] + public int DisplayOrder { get; set; } } } 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 a803bc898f..f7b116da84 100644 --- a/src/Libraries/SmartStore.Core/Domain/Discounts/Discount.cs +++ b/src/Libraries/SmartStore.Core/Domain/Discounts/Discount.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; +using System.Diagnostics; using System.Runtime.Serialization; +using SmartStore.Core.Domain.Catalog; namespace SmartStore.Core.Domain.Discounts { - /// - /// Represents a discount - /// + /// + /// Represents a discount + /// [DataContract] + [DebuggerDisplay("{Name} - {DiscountType}")] public partial class Discount : BaseEntity { private ICollection _discountRequirements; private ICollection _appliedToCategories; + private ICollection _appliedToManufacturers; private ICollection _appliedToProducts; /// @@ -132,6 +135,16 @@ public virtual ICollection AppliedToCategories protected set { _appliedToCategories = value; } } + /// + /// Gets or sets the manufacturers + /// + [DataMember] + public virtual ICollection AppliedToManufacturers + { + get { return _appliedToManufacturers ?? (_appliedToManufacturers = new HashSet()); } + protected set { _appliedToManufacturers = value; } + } + /// /// Gets or sets the products /// diff --git a/src/Libraries/SmartStore.Core/Domain/Discounts/DiscountType.cs b/src/Libraries/SmartStore.Core/Domain/Discounts/DiscountType.cs index 5487372e5e..c70468a740 100644 --- a/src/Libraries/SmartStore.Core/Domain/Discounts/DiscountType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Discounts/DiscountType.cs @@ -17,10 +17,14 @@ public enum DiscountType /// Assigned to categories (all products in a category) /// AssignedToCategories = 5, - /// - /// Assigned to shipping - /// - AssignedToShipping = 10, + /// + /// Assigned to manufacturers (all products of a manufacturer) + /// + AssignedToManufacturers = 6, + /// + /// Assigned to shipping + /// + AssignedToShipping = 10, /// /// Assigned to order subtotal /// diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs index 1af950f759..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; @@ -7,11 +8,12 @@ namespace SmartStore.Core.Domain.Forums /// /// Represents a forum /// - public partial class Forum : BaseEntity, ILocalizedEntity, ISlugSupported + public partial class Forum : BaseEntity, IAuditable, ILocalizedEntity, ISlugSupported { /// /// 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, ILocalizedEntity, ISlugSupported /// /// 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 fc84ae983e..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, 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, IStoreMappingSupported, ILocalized /// /// Gets or sets the display order /// + [Index] public int DisplayOrder { get; set; } /// @@ -38,10 +41,17 @@ public partial class ForumGroup : BaseEntity, IStoreMappingSupported, ILocalized /// 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 16a66a3253..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 @@ -6,8 +8,10 @@ namespace SmartStore.Core.Domain.Forums /// /// Represents a forum post /// - public partial class ForumPost : BaseEntity - { + 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 /// /// 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 /// 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 /// 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 5646785b54..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 @@ -6,11 +7,12 @@ namespace SmartStore.Core.Domain.Forums /// /// Represents a forum topic /// - public partial class ForumTopic : BaseEntity - { + 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 /// /// 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 /// 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 /// /// 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 /// 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 8d5d0fd1ca..5f022ea319 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/ILocalizedEntity.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/ILocalizedEntity.cs @@ -6,6 +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 ae4864b0c8..1d4b30386e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/Language.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/Language.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; -using SmartStore.Core.Domain.Stores; using System.Runtime.Serialization; using System.Diagnostics; +using System.Globalization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Localization { @@ -21,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; } @@ -71,5 +72,21 @@ public virtual ICollection LocaleStringResources protected set { _localeStringResources = value; } } + public string GetTwoLetterISOLanguageName() + { + if (UniqueSeoCode.HasValue()) + { + return UniqueSeoCode; + } + + try + { + var ci = new CultureInfo(LanguageCulture); + return ci.TwoLetterISOLanguageName; + } + catch { } + + return null; + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs index b02a4ad2a2..e523cb11ea 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizationSettings.cs @@ -9,7 +9,6 @@ public LocalizationSettings() UseImagesForLanguageSelection = true; DefaultLanguageRedirectBehaviour = DefaultLanguageRedirectBehaviour.StripSeoCode; InvalidLanguageRedirectBehaviour = InvalidLanguageRedirectBehaviour.ReturnHttp404; - LoadAllLocalizedPropertiesOnStartup = true; } /// @@ -22,16 +21,6 @@ public LocalizationSettings() /// public bool UseImagesForLanguageSelection { get; set; } - /// - /// A value indicating whether to load all records on application startup - /// - public bool LoadAllLocaleRecordsOnStartup { get; set; } - - /// - /// A value indicating whether to load all localized entity properties on application startup - /// - public bool LoadAllLocalizedPropertiesOnStartup { get; set; } - /// /// A value indicating whether the browser user lannguage should be detected /// 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/Logging/Log.cs b/src/Libraries/SmartStore.Core/Domain/Logging/Log.cs index b32f2ade12..f1ab2cf330 100644 --- a/src/Libraries/SmartStore.Core/Domain/Logging/Log.cs +++ b/src/Libraries/SmartStore.Core/Domain/Logging/Log.cs @@ -1,5 +1,7 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Logging; namespace SmartStore.Core.Domain.Logging { @@ -8,10 +10,11 @@ namespace SmartStore.Core.Domain.Logging /// public partial class Log : BaseEntity { - /// - /// Gets or sets the log level identifier - /// - public int LogLevelId { get; set; } + /// + /// Gets or sets the log level identifier + /// + [Index("IX_Log_Level", IsUnique = false)] + public int LogLevelId { get; set; } /// /// Gets or sets the short message @@ -48,16 +51,26 @@ public partial class Log : BaseEntity /// public DateTime CreatedOnUtc { get; set; } - public DateTime? UpdatedOnUtc { get; set; } + /// + /// Gets or sets the logger name + /// + [Index("IX_Log_Logger", IsUnique = false)] + public string Logger { get; set; } - public int Frequency { get; set; } + /// + /// Gets or sets the HTTP method + /// + public string HttpMethod { get; set; } - public string ContentHash { get; set; } + /// + /// Gets or sets the user name + /// + public string UserName { get; set; } - /// - /// Gets or sets the log level - /// - public LogLevel LogLevel + /// + /// Gets or sets the log level + /// + public LogLevel LogLevel { get { diff --git a/src/Libraries/SmartStore.Core/Domain/Media/Download.cs b/src/Libraries/SmartStore.Core/Domain/Media/Download.cs index 0082cbfc67..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; @@ -8,13 +11,8 @@ namespace SmartStore.Core.Domain.Media /// Represents a download /// [DataContract] - public partial class Download : BaseEntity, ITransient - { - public Download() - { - this.UpdatedOnUtc = DateTime.UtcNow; - } - + public partial class Download : BaseEntity, ITransient, IHasMedia + { /// /// Gets or sets a GUID /// @@ -34,10 +32,11 @@ public Download() [DataMember] public string DownloadUrl { get; set; } - /// - /// Gets or sets the download binary - /// - public byte[] DownloadBinary { get; set; } + /// + /// Gets or sets the download binary + /// + [Obsolete("Use property MediaStorage instead")] + public byte[] DownloadBinary { get; set; } /// /// The mime-type of the download @@ -76,5 +75,44 @@ public Download() [DataMember] [Index("IX_UpdatedOn_IsTransient", 0)] public DateTime UpdatedOnUtc { get; set; } + + /// + /// Gets or sets the media storage identifier + /// + [DataMember] + public int? MediaStorageId { get; set; } + + /// + /// 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/IHasMedia.cs b/src/Libraries/SmartStore.Core/Domain/Media/IHasMedia.cs new file mode 100644 index 0000000000..7cee5227c9 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Media/IHasMedia.cs @@ -0,0 +1,15 @@ +namespace SmartStore.Core.Domain.Media +{ + public interface IHasMedia + { + /// + /// Gets or sets the media storage identifier + /// + int? MediaStorageId { get; set; } + + /// + /// Gets or sets the media storage + /// + MediaStorage MediaStorage { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs index b3072c0912..2bf9d0f3ee 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs @@ -6,26 +6,28 @@ public class MediaSettings : ISettings { public MediaSettings() { - AvatarPictureSize = 85; - ProductThumbPictureSize = 100; - ProductDetailsPictureSize = 300; + AvatarPictureSize = 250; + ProductThumbPictureSize = 250; + CategoryThumbPictureSize = 250; + ManufacturerThumbPictureSize = 250; + ProductDetailsPictureSize = 600; ProductThumbPictureSizeOnProductDetailsPage = 70; MessageProductThumbPictureSize = 70; - AssociatedProductPictureSize = 125; + AssociatedProductPictureSize = 600; BundledProductPictureSize = 70; - CategoryThumbPictureSize = 125; - ManufacturerThumbPictureSize = 125; - CartThumbPictureSize = 80; + CartThumbPictureSize = ProductThumbPictureSize; CartThumbBundleItemPictureSize = 32; - MiniCartThumbPictureSize = 32; - VariantValueThumbPictureSize = 20; - MaximumImageSize = 1280; + MiniCartThumbPictureSize = ProductThumbPictureSize; + VariantValueThumbPictureSize = 70; + MaximumImageSize = 2048; DefaultPictureZoomEnabled = true; PictureZoomType = "window"; DefaultImageQuality = 90; MultipleThumbDirectories = true; + DefaultThumbnailAspectRatio = 1; + AutoGenerateAbsoluteUrls = true; } - + public int AvatarPictureSize { get; set; } public int ProductThumbPictureSize { get; set; } public int ProductDetailsPictureSize { get; set; } @@ -50,9 +52,23 @@ public MediaSettings() /// public int DefaultImageQuality { get; set; } + /// + /// Gets or sets the height to width ratio for thumbnails in grid style lists (0.2 - 2) + /// + /// + /// A value greater than 1 indicates, that your product pictures are generally + /// in portrait format, less than 1 indicates landscape format. + /// + public int DefaultThumbnailAspectRatio { get; set; } + /// /// Geta or sets a vaue indicating whether single (/media/thumbs/) or multiple (/media/thumbs/0001/ and /media/thumbs/0002/) directories will used for picture thumbs /// public bool MultipleThumbDirectories { get; set; } - } + + /// + /// Generates absolute media urls based upon current request uri instead of relative urls. + /// + public bool AutoGenerateAbsoluteUrls { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Media/MediaStorage.cs b/src/Libraries/SmartStore.Core/Domain/Media/MediaStorage.cs new file mode 100644 index 0000000000..f58d750751 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Media/MediaStorage.cs @@ -0,0 +1,10 @@ +namespace SmartStore.Core.Domain.Media +{ + public partial class MediaStorage : BaseEntity + { + /// + /// Binary data + /// + public byte[] Data { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs b/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs index f527b7d693..1250ef1d07 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/Picture.cs @@ -1,27 +1,21 @@ -using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; -using System.Runtime.Serialization; using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Runtime.Serialization; +using SmartStore.Core.Domain.Catalog; namespace SmartStore.Core.Domain.Media { - /// - /// Represents a picture - /// [DataContract] - public partial class Picture : BaseEntity, ITransient - { - public Picture() - { - this.UpdatedOnUtc = DateTime.UtcNow; - } - + public partial class Picture : BaseEntity, ITransient, IHasMedia + { private ICollection _productPictures; - /// - /// Gets or sets the picture binary - /// - public byte[] PictureBinary { get; set; } + + /// + /// Gets or sets the picture binary + /// + [Obsolete("Use property MediaStorage instead")] + public byte[] PictureBinary { get; set; } /// /// Gets or sets the picture mime type @@ -29,9 +23,21 @@ public Picture() [DataMember] public string MimeType { get; set; } - /// - /// Gets or sets the SEO friednly filename of the picture - /// + /// + /// Gets or sets the picture width + /// + [DataMember] + public int? Width { get; set; } + + /// + /// Gets or sets the picture height + /// + [DataMember] + public int? Height { get; set; } + + /// + /// Gets or sets the SEO friednly filename of the picture + /// [DataMember] public string SeoFilename { get; set; } @@ -55,9 +61,20 @@ public Picture() [Index("IX_UpdatedOn_IsTransient", 0)] public DateTime UpdatedOnUtc { get; set; } - /// - /// Gets or sets the product pictures - /// + /// + /// Gets or sets the media storage identifier + /// + [DataMember] + public int? MediaStorageId { get; set; } + + /// + /// Gets or sets the media storage + /// + public virtual MediaStorage MediaStorage { get; set; } + + /// + /// Gets or sets the product pictures + /// [DataMember] public virtual ICollection ProductPictures { diff --git a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs index 3da89ff17a..94eb05184c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs @@ -3,16 +3,14 @@ namespace SmartStore.Core.Domain.Media /// /// Represents a picture item type /// - public enum PictureType + public enum FallbackPictureType { - /// - /// Entities (products, categories, manufacturers) - /// - Entity = 1, - /// - /// Avatar - /// - Avatar = 10, + NoFallback = 0, + + /// + /// Entities (products, categories, manufacturers) + /// + Entity = 1, } public enum ThumbnailScaleMode diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs index 859a7bd74f..d39d745ef8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs @@ -1,4 +1,5 @@ using System; +using SmartStore.Core.Email; namespace SmartStore.Core.Domain.Messages { @@ -70,5 +71,10 @@ object ICloneable.Clone() { return this.MemberwiseClone(); } + + public EmailAddress ToEmailAddress() + { + return new EmailAddress(this.Email, this.DisplayName); + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccountSettings.cs b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccountSettings.cs index e53573cb1d..8a4d73f784 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccountSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccountSettings.cs @@ -9,6 +9,10 @@ public class EmailAccountSettings : ISettings /// public int DefaultEmailAccountId { get; set; } - } - + /// + /// Gets or sets a folder where mail messages should be saved (instead of sending them). + /// For debug and test purposes only. + /// + public string PickupDirectoryLocation { get; set; } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs b/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs index 8d1cbea6af..a10f7193c1 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs @@ -1,122 +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); - } - } - - /// - /// A container for tokens that are added. - /// - /// - public class EntityTokensAddedEvent where T : BaseEntity - { - private readonly T _entity; - private readonly IList _tokens; - - public EntityTokensAddedEvent(T entity, IList tokens) - { - _entity = entity; - _tokens = tokens; + return (Email != null ? Email.GetHashCode() : 0); } - - public T Entity { get { return _entity; } } - public IList Tokens { get { return _tokens; } } - } - - /// - /// A container for tokens that are added. - /// - /// - public class MessageTokensAddedEvent - { - private readonly MessageTemplate _message; - private readonly IList _tokens; - - public MessageTokensAddedEvent(MessageTemplate message, IList tokens) - { - _message = message; - _tokens = tokens; - } - - public MessageTemplate Message { get { return _message; } } - public IList Tokens { get { return _tokens; } } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs index 75f66564ae..d939cc684b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplate.cs @@ -1,4 +1,6 @@ -using SmartStore.Core.Domain.Localization; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Stores; @@ -9,24 +11,40 @@ namespace SmartStore.Core.Domain.Messages /// public partial class MessageTemplate : BaseEntity, ILocalizedEntity, IStoreMappingSupported { - /// + /// /// Gets or sets the name /// public string Name { get; set; } - /// - /// Gets or sets the BCC Email addresses - /// - public string BccEmailAddresses { get; set; } + [StringLength(500), Required] + public string To { get; set; } - /// - /// Gets or sets the subject - /// - public string Subject { get; set; } + [StringLength(500)] + public string ReplyTo { get; set; } + + /// + /// A comma separated list of required model types (e.g.: Product, Order, Customer, GiftCard) + /// + [StringLength(500)] + public string ModelTypes { get; set; } + + [MaxLength] + public string LastModelTree { get; set; } + + /// + /// Gets or sets the BCC Email addresses + /// + public string BccEmailAddresses { get; set; } + + /// + /// Gets or sets the subject + /// + public string Subject { get; set; } /// /// Gets or sets the body /// + [AllowHtml] public string Body { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplateNames.cs b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplateNames.cs new file mode 100644 index 0000000000..d768f0c2dc --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplateNames.cs @@ -0,0 +1,39 @@ +using System; + +namespace SmartStore.Core.Domain.Messages +{ + public class MessageTemplateNames + { + public const string CustomerRegistered = "NewCustomer.Notification"; + public const string CustomerWelcome = "Customer.WelcomeMessage"; + public const string CustomerEmailValidation = "Customer.EmailValidationMessage"; + public const string CustomerPasswordRecovery = "Customer.PasswordRecovery"; + public const string OrderPlacedStoreOwner = "OrderPlaced.StoreOwnerNotification"; + public const string OrderPlacedCustomer = "OrderPlaced.CustomerNotification"; + public const string ShipmentSentCustomer = "ShipmentSent.CustomerNotification"; + public const string ShipmentDeliveredCustomer = "ShipmentDelivered.CustomerNotification"; + public const string OrderCompletedCustomer = "OrderCompleted.CustomerNotification"; + public const string OrderCancelledCustomer = "OrderCancelled.CustomerNotification"; + public const string OrderNoteAddedCustomer = "Customer.NewOrderNote"; + public const string RecurringPaymentCancelledStoreOwner = "RecurringPaymentCancelled.StoreOwnerNotification"; + public const string NewsLetterSubscriptionActivation = "NewsLetterSubscription.ActivationMessage"; + public const string NewsLetterSubscriptionDeactivation = "NewsLetterSubscription.DeactivationMessage"; + public const string ShareProduct = "Service.EmailAFriend"; + public const string ShareWishlist = "Wishlist.EmailAFriend"; + public const string ProductQuestion = "Product.AskQuestion"; + public const string NewReturnRequestStoreOwner = "NewReturnRequest.StoreOwnerNotification"; + public const string ReturnRequestStatusChangedCustomer = "ReturnRequestStatusChanged.CustomerNotification"; + public const string NewForumTopic = "Forums.NewForumTopic"; + public const string NewForumPost = "Forums.NewForumPost"; + public const string NewPrivateMessage = "Customer.NewPM"; + public const string GiftCardCustomer = "GiftCard.Notification"; + public const string ProductReviewStoreOwner = "Product.ProductReview"; + public const string QuantityBelowStoreOwner = "QuantityBelow.StoreOwnerNotification"; + public const string NewVatSubmittedStoreOwner = "NewVATSubmitted.StoreOwnerNotification"; + public const string BlogCommentStoreOwner = "Blog.BlogComment"; + public const string NewsCommentStoreOwner = "News.NewsComment"; + public const string BackInStockCustomer = "Customer.BackInStock"; + public const string SystemCampaign = "System.Campaign"; + public const string SystemContactUs = "System.ContactUs"; + } +} 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/Messages/QueuedEmail.cs b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs index d3f7e63b93..be65e172b7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmail.cs @@ -20,31 +20,16 @@ public partial class QueuedEmail : BaseEntity /// public string From { get; set; } - /// - /// Gets or sets the FromName property - /// - public string FromName { get; set; } - /// /// Gets or sets the To property /// public string To { get; set; } - /// - /// Gets or sets the ToName property - /// - public string ToName { get; set; } - /// /// Gets or sets the ReplyTo property /// public string ReplyTo { get; set; } - /// - /// Gets or sets the ReplyToName property - /// - public string ReplyToName { get; set; } - /// /// Gets or sets the CC /// diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs index cd6d436139..e12b76c32e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/QueuedEmailAttachment.cs @@ -1,13 +1,13 @@ -using SmartStore.Core.Domain.Media; +using System; +using SmartStore.Core.Domain.Media; namespace SmartStore.Core.Domain.Messages { /// - /// Reperesents an e-mail attachment + /// Represents an e-mail attachment /// - public partial class QueuedEmailAttachment : BaseEntity + public partial class QueuedEmailAttachment : BaseEntity, IHasMedia { - /// /// Gets or sets the queued email identifier /// @@ -44,6 +44,7 @@ public partial class QueuedEmailAttachment : BaseEntity /// /// The attachment's binary data (only applicable if location is Blob) /// + [Obsolete("Use property MediaStorage instead")] public byte[] Data { get; set; } /// @@ -56,6 +57,14 @@ public partial class QueuedEmailAttachment : BaseEntity /// public string MimeType { get; set; } - } + /// + /// Gets or sets the media storage identifier + /// + public int? MediaStorageId { get; set; } + /// + /// Gets or sets the media storage + /// + public virtual MediaStorage MediaStorage { get; set; } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/GiftCard.cs b/src/Libraries/SmartStore.Core/Domain/Orders/GiftCard.cs index 0ec44d2923..3f8de5f300 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/GiftCard.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/GiftCard.cs @@ -122,13 +122,22 @@ public decimal GetGiftCardRemainingAmount() /// /// Is gift card valid /// + /// Store identifier. 0 validates the gift card for all stores /// Result - public bool IsGiftCardValid() + public bool IsGiftCardValid(int storeId) { if (!this.IsGiftCardActivated) return false; - decimal remainingAmount = GetGiftCardRemainingAmount(); + if (storeId != 0 && + PurchasedWithOrderItemId.HasValue && PurchasedWithOrderItem != null && + PurchasedWithOrderItem.Order != null) + { + if (PurchasedWithOrderItem.Order.StoreId != storeId) + return false; + } + + decimal remainingAmount = GetGiftCardRemainingAmount(); if (remainingAmount > decimal.Zero) return true; diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs index 9db5f14733..96d262c76c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs @@ -18,10 +18,10 @@ namespace SmartStore.Core.Domain.Orders /// Represents an order /// [DataContract] - public partial class Order : BaseEntity, ISoftDeletable + 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,6 +230,18 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat [DataMember] public decimal OrderDiscount { get; set; } + /// + /// 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; } + /// /// Gets or sets the order total /// @@ -242,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; } /// @@ -489,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/OrganizedShoppingCartItem.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrganizedShoppingCartItem.cs index 781e855ab1..41d38a8d0f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrganizedShoppingCartItem.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrganizedShoppingCartItem.cs @@ -8,16 +8,21 @@ public partial class OrganizedShoppingCartItem { public OrganizedShoppingCartItem(ShoppingCartItem item) { - if (item == null) - throw new ArgumentNullException("item"); + Guard.NotNull(item, nameof(item)); Item = item; // must not be null ChildItems = new List(); BundleItemData = new ProductBundleItemData(item.BundleItem); + CustomProperties = new Dictionary(); } public ShoppingCartItem Item { get; private set; } public IList ChildItems { get; set; } public ProductBundleItemData BundleItemData { get; set; } + + /// + /// Use this dictionary for any custom data required along cart processing + /// + public Dictionary CustomProperties { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs index ba269bdf4c..888c2ac83e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs @@ -1,15 +1,15 @@ 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 - { + public partial class ReturnRequest : BaseEntity, IAuditable + { /// /// Gets or sets the store identifier /// @@ -75,10 +75,16 @@ public partial class ReturnRequest : BaseEntity /// [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/ShoppingCartItem.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartItem.cs index a02f24b929..a7c3c91273 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartItem.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartItem.cs @@ -8,8 +8,8 @@ namespace SmartStore.Core.Domain.Orders /// /// Represents a shopping cart item /// - public partial class ShoppingCartItem : BaseEntity - { + public partial class ShoppingCartItem : BaseEntity, IAuditable + { /// /// Gets or sets the store identifier /// @@ -65,10 +65,10 @@ public partial class ShoppingCartItem : BaseEntity /// public DateTime UpdatedOnUtc { get; set; } - /// - /// Gets the log type - /// - public ShoppingCartType ShoppingCartType + /// + /// Gets or sets the shopping cart type + /// + public ShoppingCartType ShoppingCartType { get { diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs index e403d7cdb1..e569f371b7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs @@ -23,11 +23,10 @@ public ShoppingCartSettings() EmailWishlistEnabled = true; MiniShoppingCartEnabled = true; ShowProductImagesInMiniShoppingCart = true; - MiniShoppingCartProductNumber = 5; - //RoundPricesDuringCalculation = false; ShowBasePrice = true; ShowDeliveryTimes = true; ShowShortDesc = true; + AllowAnonymousUsersToEmailWishlist = false; } /// @@ -144,16 +143,6 @@ public ShoppingCartSettings() /// public bool ShowProductImagesInMiniShoppingCart { get; set; } - /// Gets or sets a maximum number of products which can be displayed in the mini-shopping cart block - /// - public int MiniShoppingCartProductNumber { get; set; } - - //Round is already an issue. - /// - /// Gets or sets a value indicating whether to round calculated prices and total during calculation - /// - public bool RoundPricesDuringCalculation { get; set; } - /// /// Gets or sets a value indicating whether to show a legal hint in the order summary /// diff --git a/src/Libraries/SmartStore.Core/Domain/Payments/CapturePaymentReason.cs b/src/Libraries/SmartStore.Core/Domain/Payments/CapturePaymentReason.cs new file mode 100644 index 0000000000..39d599f41d --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Payments/CapturePaymentReason.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Core.Domain.Payments +{ + /// + /// The reason for automatic capturing of the payment amount. + /// + public enum CapturePaymentReason + { + /// + /// Capture payment because the order has been marked as shipped. + /// + OrderShipped = 0, + + /// + /// Capture payment because the order has been marked as delivered. + /// + OrderDelivered + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentMethod.cs b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentMethod.cs index e078434cc6..ba4b86162d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentMethod.cs +++ b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentMethod.cs @@ -1,5 +1,6 @@ using System.Runtime.Serialization; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Payments { @@ -7,7 +8,7 @@ namespace SmartStore.Core.Domain.Payments /// Represents a payment method /// [DataContract] - public partial class PaymentMethod : BaseEntity, ILocalizedEntity + public partial class PaymentMethod : BaseEntity, ILocalizedEntity, IStoreMappingSupported { /// /// Gets or sets the payment method system name @@ -20,5 +21,18 @@ public partial class PaymentMethod : BaseEntity, ILocalizedEntity /// [DataMember] public string FullDescription { get; set; } + + /// + /// Gets or sets a value indicating whether to round the order total. Also known as "Cash rounding". + /// + /// + [DataMember] + public bool RoundOrderTotalEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + [DataMember] + public bool LimitedToStores { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs index 8b30907586..7aad6a5667 100644 --- a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs @@ -9,7 +9,7 @@ public PaymentSettings() { ActivePaymentMethodSystemNames = new List(); AllowRePostingPayments = true; - BypassPaymentMethodSelectionIfOnlyOne = true; + BypassPaymentMethodSelectionIfOnlyOne = false; } /// @@ -31,5 +31,15 @@ public PaymentSettings() /// Gets or sets a value indicating whether we should bypass the payment method info page /// public bool BypassPaymentMethodInfo { get; set; } - } + + /// + /// Gets or sets the reason for automatic payment capturing + /// + public CapturePaymentReason? CapturePaymentReason { get; set; } + + /// + /// Gets or sets the identifier of the currency in which the wallet is kept. + /// + public int WalletCurrencyId { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Security/IAclSupported.cs b/src/Libraries/SmartStore.Core/Domain/Security/IAclSupported.cs index 60661ea0df..872f42384d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Security/IAclSupported.cs +++ b/src/Libraries/SmartStore.Core/Domain/Security/IAclSupported.cs @@ -5,9 +5,9 @@ namespace SmartStore.Core.Domain.Security /// public partial interface IAclSupported { - /// - /// Gets or sets a value indicating whether the entity is subject to ACL - /// - bool SubjectToAcl { get; set; } + /// + /// Gets or sets a value indicating whether the entity is subject to ACL + /// + bool SubjectToAcl { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs b/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs index 75d492b68d..71999bd5ba 100644 --- a/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs @@ -11,16 +11,16 @@ public SecuritySettings() EncryptionKey = CommonHelper.GenerateRandomDigitCode(16); AdminAreaAllowedIpAddresses = new List(); } - + /// - /// Gets or sets a value indicating whether all pages will be forced to use SSL (no matter of a specified [HttpsRequirementAttribute] attribute) - /// - public bool ForceSslForAllPages { get; set; } + /// When true, bypasses any SSL redirection on localhost + /// + public bool UseSslOnLocalhost { get; set; } - /// - /// Gets or sets an encryption key - /// - public string EncryptionKey { get; set; } + /// + /// Gets or sets an encryption key + /// + public string EncryptionKey { get; set; } /// /// Gets or sets a list of adminn area allowed IP addresses @@ -31,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/ISlugSupported.cs b/src/Libraries/SmartStore.Core/Domain/Seo/ISlugSupported.cs index bb59b5581e..6b52764cec 100644 --- a/src/Libraries/SmartStore.Core/Domain/Seo/ISlugSupported.cs +++ b/src/Libraries/SmartStore.Core/Domain/Seo/ISlugSupported.cs @@ -6,6 +6,5 @@ namespace SmartStore.Core.Domain.Seo /// public interface ISlugSupported { - - } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs index 1f06b5aba6..9d1d5f5bce 100644 --- a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs @@ -17,6 +17,12 @@ public SeoSettings() CanonicalHostNameRule = CanonicalHostNameRule.NoRule; LoadAllUrlAliasesOnStartup = true; + XmlSitemapEnabled = true; + XmlSitemapIncludesCategories = true; + XmlSitemapIncludesManufacturers = true; + XmlSitemapIncludesProducts = true; + XmlSitemapIncludesTopics = true; + ExtraRobotsDisallows = new List { "/blog/tag/", "/blog/month/", "/producttags/" }; ReservedUrlRecordSlugs = new List @@ -75,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 @@ -88,5 +94,17 @@ public SeoSettings() /// A value indicating whether to load all URL records and active slugs on application startup /// public bool LoadAllUrlAliasesOnStartup { get; set; } - } + + public bool RedirectLegacyTopicUrls { get; set; } + + #region XML Sitemap + + public bool XmlSitemapEnabled { get; set; } + public bool XmlSitemapIncludesCategories { get; set; } + public bool XmlSitemapIncludesManufacturers { get; set; } + public bool XmlSitemapIncludesProducts { get; set; } + public bool XmlSitemapIncludesTopics { get; set; } + + #endregion + } } \ No newline at end of file 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/Shipping/ShippingMethod.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs index 078b12a20c..ca5d7b1207 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingMethod.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using System.Runtime.Serialization; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Shipping { @@ -9,10 +8,8 @@ namespace SmartStore.Core.Domain.Shipping /// Represents a shipping method (used for offline shipping rate computation methods) /// [DataContract] - public partial class ShippingMethod : BaseEntity, ILocalizedEntity - { - private ICollection _restrictedCountries; - + public partial class ShippingMethod : BaseEntity, ILocalizedEntity, IStoreMappingSupported + { /// /// Gets or sets the name /// @@ -31,17 +28,16 @@ public partial class ShippingMethod : BaseEntity, ILocalizedEntity [DataMember] public int DisplayOrder { get; set; } + /// + /// Gets or sets whether to ignore charges + /// [DataMember] public bool IgnoreCharges { get; set; } - /// - /// Gets or sets the restricted countries - /// + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// [DataMember] - public virtual ICollection RestrictedCountries - { - get { return _restrictedCountries ?? (_restrictedCountries = new HashSet()); } - protected set { _restrictedCountries = value; } - } + public bool LimitedToStores { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingSettings.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingSettings.cs index 1157cc0d40..1dc2bfbf54 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShippingSettings.cs @@ -56,6 +56,11 @@ public ShippingSettings() /// /// Gets or sets a value indicating whether to display shipping options during checkout process only if more then one option is available /// - public bool SuppressShippingOptsCheckoutStepIfOnlyOneActiveOpt { get; set; } + public bool SkipShippingIfSingleOption { get; set; } + + /// + /// Gets or sets a value indicating whether to charge only the highest shipping surcharge of products + /// + public bool ChargeOnlyHighestProductShippingSurcharge { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs b/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs index 0d9fe6be96..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) { @@ -104,7 +110,49 @@ public HttpSecurityMode GetSecurityMode(bool? useSsl = null) return HttpSecurityMode.Ssl; } } + return HttpSecurityMode.Unsecured; } + + private string _secureHost; + private string _unsecureHost; + + /// + /// Gets the store host name + /// + /// + /// If false, returns the default unsecured url. + /// If true, returns the secure url, but only if SSL is enabled for the store. + /// + /// The host name + public string GetHost(bool secure) + { + return secure + ? _secureHost ?? (_secureHost = GetHostInternal(true)) + : _unsecureHost ?? (_unsecureHost = GetHostInternal(false)); + } + + private string GetHostInternal(bool secure) + { + var host = string.Empty; + + if (secure && SslEnabled) + { + if (SecureUrl.HasValue()) + { + host = SecureUrl; + } + else + { + host = Url.Replace("http:/", "https:/"); + } + } + else + { + host = Url; + } + + return host.EnsureEndsWith("/"); + } } } 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 5806d83c4f..f516fc53da 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs @@ -1,14 +1,17 @@ - -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; namespace SmartStore.Core.Domain.Tasks { [DebuggerDisplay("{Name} (Type: {Type})")] + [Hookable(false)] public class ScheduleTask : BaseEntity, ICloneable { + private ICollection _scheduleTaskHistory; + /// /// Gets or sets the name /// @@ -44,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/AuxiliaryServicesTaxType.cs b/src/Libraries/SmartStore.Core/Domain/Tax/AuxiliaryServicesTaxType.cs new file mode 100644 index 0000000000..a215f4160c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Tax/AuxiliaryServicesTaxType.cs @@ -0,0 +1,29 @@ +namespace SmartStore.Core.Domain.Tax +{ + /// + /// Specifies how to calculate the tax of auxiliary services like shipping and payment fees + /// + public enum AuxiliaryServicesTaxType + { + /// + /// Calculate tax of auxiliary services with the tax category specified in settings + /// + SpecifiedTaxCategory = 0, + + /// + /// Calculate tax with the tax rate that has the highest amount in the cart + /// + HighestCartAmount = 10, + + /// + /// Calculate tax by the highest tax rate in the cart + /// + HighestTaxRate = 15, + + /// + /// Calculate tax pro rata in accordance with main service (proportion of line subtotal and sum of all line subtotals) + /// + /// commented out cause requires several plugins to be updated and migration of Order.OrderShippingTaxRate and Order.PaymentMethodAdditionalFeeTaxRate + ///ProRata = 20 + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Tax/TaxCategory.cs b/src/Libraries/SmartStore.Core/Domain/Tax/TaxCategory.cs index ebea02eb95..7fd0279410 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tax/TaxCategory.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tax/TaxCategory.cs @@ -1,20 +1,24 @@ +using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Tax { - /// - /// Represents a tax category - /// - public partial class TaxCategory : BaseEntity + /// + /// Represents a tax category + /// + [DataContract] + public partial class TaxCategory : BaseEntity { - /// - /// Gets or sets the name - /// - public string Name { get; set; } + /// + /// Gets or sets the name + /// + [DataMember] + public string Name { get; set; } - /// - /// Gets or sets the display order - /// - public int DisplayOrder { get; set; } + /// + /// Gets or sets the display order + /// + [DataMember] + public int DisplayOrder { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs b/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs index 5a69937cac..28fd36b269 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs @@ -1,5 +1,4 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Tax { @@ -13,9 +12,11 @@ public TaxSettings() DisplayTaxSuffix = true; PricesIncludeTax = true; EuVatAllowVatExemption = true; - ShowLegalHintsInFooter = true; + ShippingPriceIncludesTax = true; + ShowLegalHintsInFooter = true; ShowLegalHintsInProductDetails = true; ShowLegalHintsInProductList = true; + AuxiliaryServicesTaxingType = AuxiliaryServicesTaxType.SpecifiedTaxCategory; } /// @@ -113,10 +114,15 @@ public TaxSettings() /// public int PaymentMethodAdditionalFeeTaxClassId { get; set; } - /// - /// Gets or sets a value indicating whether EU VAT (Eupore Union Value Added Tax) is enabled - /// - public bool EuVatEnabled { get; set; } + /// + /// Taxing type for auxiliary services like shipping and payment fees + /// + public AuxiliaryServicesTaxType AuxiliaryServicesTaxingType { get; set; } + + /// + /// Gets or sets a value indicating whether EU VAT (Eupore Union Value Added Tax) is enabled + /// + public bool EuVatEnabled { get; set; } /// /// Gets or sets a shop country identifier diff --git a/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs b/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs index 43517c8410..aff5ac1f4b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Themes/ThemeSettings.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SmartStore.Core.Configuration; namespace SmartStore.Core.Domain.Themes @@ -11,10 +7,9 @@ public class ThemeSettings : ISettings { public ThemeSettings() { - DefaultDesktopTheme = "Alpha"; - DefaultMobileTheme = "Mobile"; + DefaultTheme = "Flex"; AllowCustomerToSelectTheme = true; - MobileDevicesSupported = true; + AssetCachingEnabled = 2; } /// @@ -22,16 +17,27 @@ public ThemeSettings() /// asset bundling is enabled /// /// - /// 0: Auto (decide based on web.config debug) + /// 0: Auto (decide based on debug mode) > default /// 1: Disabled /// 2: Enabled /// public int BundleOptimizationEnabled { get; set; } - /// - /// Gets or sets a default store theme for desktops - /// - public string DefaultDesktopTheme { get; set; } + /// + /// Gets or sets a value indicating whether + /// asset caching is enabled + /// + /// + /// 0: Auto (decide based on debug mode) + /// 1: Disabled + /// 2: Enabled > default + /// + public int AssetCachingEnabled { get; set; } + + /// + /// Gets or sets a default store theme for desktops + /// + public string DefaultTheme { get; set; } /// /// Gets or sets a value indicating whether customers are allowed to select a theme @@ -47,20 +53,5 @@ public ThemeSettings() /// public bool SaveThemeChoiceInCookie { get; set; } - /// - /// Gets or sets a value indicating whether mobile devices supported - /// - public bool MobileDevicesSupported { get; set; } - - /// - /// Gets or sets a default store theme used by mobile devices (if enabled) - /// - public string DefaultMobileTheme { get; set; } - - /// - /// Gets or sets a value indicating whether all requests will be handled as mobile devices (used for testing) - /// - public bool EmulateMobileDevice { get; set; } - } } diff --git a/src/Libraries/SmartStore.Core/Domain/Themes/ThemeVariable.cs b/src/Libraries/SmartStore.Core/Domain/Themes/ThemeVariable.cs index 12dad624d9..2befa5532e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Themes/ThemeVariable.cs +++ b/src/Libraries/SmartStore.Core/Domain/Themes/ThemeVariable.cs @@ -6,13 +6,11 @@ // codehint: sm-add (whole file) namespace SmartStore.Core.Domain.Themes -{ - +{ public class ThemeVariable : BaseEntity { - /// - /// Gets or sets the theme the attribute belongs to + /// Gets or sets the theme the variable belongs to /// public string Theme { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs b/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs index 8181a60047..d05d74c975 100644 --- a/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs @@ -1,106 +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 { - /// - /// Gets or sets the name - /// - public string SystemName { get; set; } + public Topic() + { + IsPublished = true; + } - /// - /// Gets or sets the value indicating whether this topic should be included in sitemap - /// - public bool IncludeInSitemap { get; set; } + /// + /// Gets or sets the name + /// + [DataMember] + public string SystemName { 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 deleteable by a user + /// + [DataMember] + public bool IsSystemTopic { get; set; } /// - /// Gets or sets the password + /// Gets or sets the html id /// - public string Password { get; set; } + [DataMember] + public string HtmlId { get; set; } /// - /// Gets or sets the title + /// Gets or sets the body css class /// - public string Title { get; set; } + [DataMember] + public string BodyCssClass { get; set; } /// - /// Gets or sets the body + /// Gets or sets the value indicating whether this topic should be included in sitemap /// - public string Body { get; set; } + [DataMember] + public bool IncludeInSitemap { get; set; } - /// - /// Gets or sets the meta keywords - /// - public string MetaKeywords { get; set; } + /// + /// Gets or sets the value indicating whether this topic is password protected + /// + [DataMember] + public bool IsPasswordProtected { get; set; } - /// - /// Gets or sets the meta description - /// - public string MetaDescription { get; set; } + /// + /// Gets or sets the password + /// + [DataMember] + public string Password { get; set; } - /// - /// Gets or sets the meta title - /// - public string MetaTitle { get; set; } + /// + /// Gets or sets the title + /// + [DataMember] + public string Title { get; set; } + + /// + /// Gets or sets the short title (for links) + /// + [DataMember] + public string ShortTitle { get; set; } + + /// + /// Gets or sets the intro + /// + [DataMember] + public string Intro { get; set; } + + /// + /// Gets or sets the body + /// + [DataMember] + public string Body { get; set; } + + /// + /// Gets or sets the meta keywords + /// + [DataMember] + public string MetaKeywords { get; set; } + + /// + /// Gets or sets the meta description + /// + [DataMember] + public string MetaDescription { get; set; } + + /// + /// Gets or sets the meta title + /// + [DataMember] + public string MetaTitle { get; set; } /// /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores /// + [DataMember] public bool LimitedToStores { get; set; } - /// - /// Gets or sets a value indicating whether the topic should also be rendered as a generic html widget - /// - public bool RenderAsWidget { get; set; } + /// + /// Gets or sets a value indicating whether the topic should also be rendered as a generic html widget + /// + [DataMember] + public bool RenderAsWidget { get; set; } - /// - /// Gets or sets the widget zone name - /// - public string WidgetZone { get; set; } + /// + /// Gets or sets the widget zone name + /// + [DataMember] + public string WidgetZone { get; set; } /// /// Gets or sets a value indicating whether the content should be surrounded by a topic block wrapper /// + [DataMember] public bool? WidgetWrapContent { get; set; } - /// - /// Gets or sets a value indicating whether the title should be displayed in the widget block - /// - public bool WidgetShowTitle { get; set; } + /// + /// Gets or sets a value indicating whether the 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 b9862e0c52..5492f4631f 100644 --- a/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs +++ b/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs @@ -4,22 +4,27 @@ using System.Text; using System.Net.Mail; using System.Net.Mime; -using System.Net; using System.IO; -using System.ComponentModel; using System.Threading.Tasks; +using SmartStore.Core.Domain.Messages; namespace SmartStore.Core.Email { public class DefaultEmailSender : IEmailSender { + private readonly EmailAccountSettings _emailAccountSettings; + + public DefaultEmailSender(EmailAccountSettings emailAccountSettings) + { + _emailAccountSettings = emailAccountSettings; + } - /// - /// Builds System.Net.Mail.Message - /// - /// SmartStore.Email.Message - /// System.Net.Mail.Message - protected virtual MailMessage BuildMailMessage(EmailMessage original) + /// + /// Builds System.Net.Mail.Message + /// + /// SmartStore.Email.Message + /// System.Net.Mail.Message + protected virtual MailMessage BuildMailMessage(EmailMessage original) { MailMessage msg = new MailMessage(); @@ -61,38 +66,49 @@ protected virtual MailMessage BuildMailMessage(EmailMessage original) return msg; } - #region IMailSender Members + #region IMailSender Members - public void SendEmail(SmtpContext context, EmailMessage message) + public void SendEmail(SmtpContext context, EmailMessage message) { - Guard.ArgumentNotNull(() => context); - Guard.ArgumentNotNull(() => message); + Guard.NotNull(context, nameof(context)); + Guard.NotNull(message, nameof(message)); using (var msg = this.BuildMailMessage(message)) { using (var client = context.ToSmtpClient()) { + ApplySettings(client); client.Send(msg); } } } - public Task SendEmailAsync(SmtpContext context, EmailMessage message) + public async Task SendEmailAsync(SmtpContext context, EmailMessage message) { - Guard.ArgumentNotNull(() => context); - Guard.ArgumentNotNull(() => message); - - var client = context.ToSmtpClient(); - var msg = this.BuildMailMessage(message); + Guard.NotNull(context, nameof(context)); + Guard.NotNull(message, nameof(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); + } + } } - #endregion + private void ApplySettings(SmtpClient client) + { + var pickupDirLocation = _emailAccountSettings.PickupDirectoryLocation; + if (pickupDirLocation.HasValue() && client.DeliveryMethod != SmtpDeliveryMethod.SpecifiedPickupDirectory && Path.IsPathRooted(pickupDirLocation)) + { + client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory; + client.PickupDirectoryLocation = pickupDirLocation; + client.EnableSsl = false; + } + } - } + #endregion + } } diff --git a/src/Libraries/SmartStore.Core/Email/EmailAddress.cs b/src/Libraries/SmartStore.Core/Email/EmailAddress.cs index 3920dbc087..8699174caf 100644 --- a/src/Libraries/SmartStore.Core/Email/EmailAddress.cs +++ b/src/Libraries/SmartStore.Core/Email/EmailAddress.cs @@ -1,43 +1,56 @@ using System; using System.Net.Mail; -using System.Text; namespace SmartStore.Core.Email { public class EmailAddress { - public string Address { get; set; } - public string DisplayName { get; set; } + private readonly MailAddress _inner; - public EmailAddress(string address) + public EmailAddress(string address) { - this.Address = address; + _inner = new MailAddress(address); } public EmailAddress(string address, string displayName) { - this.Address = address; - this.DisplayName = displayName; - } + _inner = new MailAddress(address, displayName); + } - public override int GetHashCode() + public EmailAddress(MailAddress address) { - return this.ToString().GetHashCode(); + _inner = address; } - public override string ToString() + public string Address { - if (this.DisplayName.IsEmpty()) - { - return this.Address; - } + get { return _inner.Address; } + } - return "{0} [{1}]".FormatCurrent(this.DisplayName, this.Address); + public string DisplayName + { + get { return _inner.DisplayName; } + } + + public string User + { + get { return _inner.User; } } - public MailAddress ToMailAddress() + public string Host + { + get { return _inner.Host; } + } + + public override int GetHashCode() => _inner.GetHashCode(); + + public override string ToString() => _inner.ToString(); + + public MailAddress ToMailAddress() => _inner; + + public static implicit operator string(EmailAddress obj) { - return new MailAddress(this.Address, this.DisplayName); + return obj.ToString(); } - } + } } diff --git a/src/Libraries/SmartStore.Core/Email/EmailMessage.cs b/src/Libraries/SmartStore.Core/Email/EmailMessage.cs index 683071e498..cdc83e6efb 100644 --- a/src/Libraries/SmartStore.Core/Email/EmailMessage.cs +++ b/src/Libraries/SmartStore.Core/Email/EmailMessage.cs @@ -8,10 +8,8 @@ namespace SmartStore.Core.Email { - public class EmailMessage : ICloneable { - public EmailMessage() { this.BodyFormat = MailBodyFormat.Html; @@ -30,10 +28,10 @@ public EmailMessage() public EmailMessage(string to, string subject, string body, string from) : this() { - Guard.ArgumentNotEmpty(() => to); - Guard.ArgumentNotEmpty(() => from); - Guard.ArgumentNotEmpty(() => subject); - Guard.ArgumentNotEmpty(() => body); + Guard.NotEmpty(to, nameof(to)); + Guard.NotEmpty(from, nameof(from)); + Guard.NotEmpty(subject, nameof(subject)); + Guard.NotEmpty(body, nameof(body)); this.To.Add(new EmailAddress(to)); this.Subject = subject; @@ -44,10 +42,10 @@ public EmailMessage(string to, string subject, string body, string from) public EmailMessage(EmailAddress to, string subject, string body, EmailAddress from) : this() { - Guard.ArgumentNotNull(() => to); - Guard.ArgumentNotNull(() => from); - Guard.ArgumentNotEmpty(() => subject); - Guard.ArgumentNotEmpty(() => body); + Guard.NotNull(to, nameof(to)); + Guard.NotNull(from, nameof(from)); + Guard.NotEmpty(subject, nameof(subject)); + Guard.NotEmpty(body, nameof(body)); this.To.Add(to); this.Subject = subject; diff --git a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs index 054487610f..e1fde3c68a 100644 --- a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs +++ b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs @@ -12,8 +12,8 @@ public class SmtpContext public SmtpContext(string host, int port = 25) { - Guard.ArgumentNotEmpty(() => host); - Guard.ArgumentIsPositive(port, "port"); + Guard.NotEmpty(host, nameof(host)); + Guard.IsPositive(port, nameof(port)); this.Host = host; this.Port = port; @@ -21,7 +21,7 @@ public SmtpContext(string host, int port = 25) public SmtpContext(EmailAccount account) { - Guard.ArgumentNotNull(() => account); + Guard.NotNull(account, nameof(account)); this.Host = account.Host; this.Port = account.Port; @@ -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/CommonMessages/AppStartedEvent.cs b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppStartedEvent.cs new file mode 100644 index 0000000000..c1f2880780 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppStartedEvent.cs @@ -0,0 +1,10 @@ +using System; +using System.Web; + +namespace SmartStore.Core.Events +{ + public class AppStartedEvent + { + public HttpContextBase HttpContext { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityDeleted.cs b/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityDeleted.cs deleted file mode 100644 index 9199400ada..0000000000 --- a/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityDeleted.cs +++ /dev/null @@ -1,19 +0,0 @@ - -namespace SmartStore.Core.Events -{ - /// - /// A container for passing entities that have been deleted. This is not used for entities that are deleted logicaly via a bit column. - /// - /// - public class EntityDeleted : ComparableObject where T : BaseEntity - { - - public EntityDeleted(T entity) - { - this.Entity = entity; - } - - [ObjectSignature] - public T Entity { get; private set; } - } -} diff --git a/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityInserted.cs b/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityInserted.cs deleted file mode 100644 index 951f723fbf..0000000000 --- a/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityInserted.cs +++ /dev/null @@ -1,19 +0,0 @@ - -namespace SmartStore.Core.Events -{ - /// - /// A container for entities that have been inserted. - /// - /// - public class EntityInserted : ComparableObject where T : BaseEntity - { - - public EntityInserted(T entity) - { - this.Entity = entity; - } - - [ObjectSignature] - public T Entity { get; private set; } - } -} diff --git a/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityUpdated.cs b/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityUpdated.cs deleted file mode 100644 index d12cc0edbd..0000000000 --- a/src/Libraries/SmartStore.Core/Events/CommonMessages/EntityUpdated.cs +++ /dev/null @@ -1,21 +0,0 @@ - -using System.Collections.Generic; -namespace SmartStore.Core.Events -{ - /// - /// A container for entities that are updated. - /// - /// - public class EntityUpdated : ComparableObject where T : BaseEntity - { - - public EntityUpdated(T entity) - { - this.Entity = entity; - } - - [ObjectSignature] - public T Entity { get; private set; } - - } -} 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 ca12ff8653..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 eventMessage); - } + /// + /// 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 225808124f..21f0f6ebe6 100644 --- a/src/Libraries/SmartStore.Core/Events/IEventPublisher.cs +++ b/src/Libraries/SmartStore.Core/Events/IEventPublisher.cs @@ -1,26 +1,9 @@ - +using System; + namespace SmartStore.Core.Events { - public interface IEventPublisher - { - void Publish(T eventMessage); - } - - public static class IEventPublisherExtensions + public interface IEventPublisher { - public static void EntityInserted(this IEventPublisher eventPublisher, T entity) where T : BaseEntity - { - eventPublisher.Publish(new EntityInserted(entity)); - } - - public static void EntityUpdated(this IEventPublisher eventPublisher, T entity) where T : BaseEntity - { - eventPublisher.Publish(new EntityUpdated(entity)); - } - - public static void EntityDeleted(this IEventPublisher eventPublisher, T entity) where T : BaseEntity - { - eventPublisher.Publish(new EntityDeleted(entity)); - } + 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 f63a42b05b..58992963be 100644 --- a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs @@ -8,6 +8,7 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using SmartStore.ComponentModel; using SmartStore.Utilities; @@ -44,7 +45,8 @@ public static object Convert(this object value, Type to) public static object Convert(this object value, Type to, CultureInfo culture) { - Guard.ArgumentNotNull(to, "to"); + if (to == null) + throw new ArgumentNullException(nameof(to)); if (value == null || value == DBNull.Value || to.IsInstanceOfType(value)) { @@ -91,6 +93,7 @@ public static char ToHex(this int value) { return (char)(value + 48); } + return (char)((value - 10) + 97); } @@ -98,23 +101,9 @@ public static char ToHex(this int value) #region String - public static T ToEnum(this string value, T defaultValue) where T : IComparable, IFormattable - { - Guard.ArgumentIsEnumType(typeof(T), "T"); - - T result; - if (CommonHelper.TryConvert(value, out result)) - { - return result; - } - - return defaultValue; - } - 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; } @@ -124,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; } @@ -145,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; } @@ -194,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; } @@ -223,33 +210,69 @@ public static Version ToVersion(this string value, Version defaultVersion = null public static byte[] ToByteArray(this Stream stream) { - Guard.ArgumentNotNull(stream, "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 ms = new MemoryStream()) + using (var streamReader = new MemoryStream()) { - stream.CopyTo(ms); - return ms.ToArray(); + stream.CopyTo(streamReader); + return streamReader.GetBuffer(); } } - } + } + + public static async Task ToByteArrayAsync(this Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + if (stream is MemoryStream mem) + { + if (mem.TryGetBuffer(out var buffer)) + { + return buffer.Array; + } + + return mem.ToArray(); + } + else + { + using (var streamReader = new MemoryStream()) + { + await stream.CopyToAsync(streamReader); + return streamReader.ToArray(); + } + } + } public static string AsString(this Stream stream) { return stream.AsString(Encoding.UTF8); } - public static string AsString(this Stream stream, Encoding encoding) + public static Task AsStringAsync(this Stream stream) + { + return stream.AsStringAsync(Encoding.UTF8); + } + + public static string AsString(this Stream stream, Encoding encoding) { - Guard.ArgumentNotNull(() => encoding); - + if (encoding == null) + throw new ArgumentNullException(nameof(encoding)); + // convert stream to string - string result; + string result; if (stream.CanSeek) { @@ -264,16 +287,37 @@ public static string AsString(this Stream stream, Encoding encoding) return result; } - #endregion + public static Task AsStringAsync(this Stream stream, Encoding encoding) + { + if (encoding == null) + throw new ArgumentNullException(nameof(encoding)); + + // convert stream to string + Task result; + + if (stream.CanSeek) + { + stream.Position = 0; + } - #region ByteArray + using (StreamReader sr = new StreamReader(stream, encoding)) + { + result = sr.ReadToEndAsync(); + } - /// - /// Converts a byte array into an object. - /// - /// Object to deserialize. May be null. - /// Deserialized object, or null if input was null. - public static object ToObject(this byte[] bytes) + return result; + } + + #endregion + + #region ByteArray + + /// + /// Converts a byte array into an object. + /// + /// Object to deserialize. May be null. + /// Deserialized object, or null if input was null. + public static object ToObject(this byte[] bytes) { if (bytes == null) return null; @@ -297,9 +341,10 @@ public static Stream ToStream(this byte[] bytes) [DebuggerStepThrough] public static string Hash(this byte[] value, bool toBase64 = false) { - Guard.ArgumentNotNull(value, "value"); + if (value == null) + throw new ArgumentNullException(nameof(value)); - using (MD5 md5 = MD5.Create()) + using (MD5 md5 = MD5.Create()) { if (toBase64) @@ -329,7 +374,8 @@ public static string Hash(this byte[] value, bool toBase64 = false) /// The compressed result public static byte[] Zip(this byte[] buffer) { - Guard.ArgumentNotNull(() => buffer); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); using (var compressedStream = new MemoryStream()) using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress)) @@ -347,7 +393,8 @@ public static byte[] Zip(this byte[] buffer) /// The decompressed result public static byte[] UnZip(this byte[] buffer) { - Guard.ArgumentNotNull(() => buffer); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); using (var compressedStream = new MemoryStream(buffer)) using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress)) @@ -369,8 +416,8 @@ public static T ToObject(this IDictionary values) where T : c public static object ToObject(this IDictionary values, Type objectType) { - Guard.ArgumentNotEmpty(values, "values"); - Guard.ArgumentNotNull(objectType, "objectType"); + Guard.NotEmpty(values, nameof(values)); + Guard.NotNull(objectType, nameof(objectType)); if (!DictionaryConverter.CanCreateType(objectType)) { 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 aa6dd811e3..0000000000 --- a/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Globalization; - -namespace SmartStore -{ - public static class DecimalExtensions - { - /// - /// Rounds and formats a decimal culture invariant - /// - /// The decimal - /// Rounding decimal number - /// Formated value - public static string FormatInvariant(this decimal value, int decimals = 2) - { - return Math.Round(value, decimals).ToString("0.00", CultureInfo.InvariantCulture); - } - - /// - /// 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 - /// - /// Smallest currency unit - public static int ToSmallestCurrencyUnit(this decimal value, MidpointRounding rounding = MidpointRounding.AwayFromZero) - { - var result = Math.Round(value * 100, 0, MidpointRounding.AwayFromZero); - return Convert.ToInt32(result); - } - } -} diff --git a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs index 6e4c8976e8..3a3edf2f83 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Web; using System.Web.Routing; @@ -47,39 +48,48 @@ 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) { - instance[key] = !instance.ContainsKey(key) ? value.ToString() : (instance[key] + separator + 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) { - instance[key] = !instance.ContainsKey(key) ? value.ToString() : (value + separator + instance[key]); + AddInValue(instance, key, separator, value, true); } - public static TValue GetValue(this IDictionary instance, TKey key) + private static void AddInValue(IDictionary instance, string key, string separator, string value, bool prepend = false) { - try + if (!instance.TryGetValue(key, out var obj)) { - object val; - if (instance != null && instance.TryGetValue(key, out val) && val != null) - { - return (TValue)Convert.ChangeType(val, typeof(TValue), CultureInfo.InvariantCulture); - } + instance[key] = value; } - catch (Exception ex) + else { - ex.Dump(); + 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); + + instance[key] = string.Join(separator, arr); } + } - return default(TValue); + public static TValue Get(this IDictionary instance, TKey key) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + instance.TryGetValue(key, out var val); + return val; } - public static ExpandoObject ToExpandoObject(this IDictionary source, bool castIfPossible = false) + public static ExpandoObject ToExpandoObject(this IDictionary source, bool castIfPossible = false) { - Guard.ArgumentNotNull(source, "source"); + if (source == null) + throw new ArgumentNullException(nameof(source)); - if (castIfPossible && source is ExpandoObject) + if (castIfPossible && source is ExpandoObject) { return source as ExpandoObject; } @@ -90,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 948cfddc6a..cb529dcace 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -8,9 +8,105 @@ using SmartStore.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using SmartStore.Core; namespace SmartStore { + public static class CollectionSlicer + { + /// + /// Slices the iteration over an enumerable by the given slice sizes. + /// + /// + /// The source sequence to slice + /// + /// Slice sizes. At least one size is required. Multiple sizes result in differently sized slices, + /// whereat the last size is used for the "rest" (if any) + /// + /// The sliced enumerable + public static IEnumerable> Slice(this IEnumerable source, params int[] sizes) + { + if (!sizes.Any(step => step != 0)) + { + throw new InvalidOperationException("Can't slice a collection with step length 0."); + } + + return new Slicer(source.GetEnumerator(), sizes).Slice(); + } + } + + internal sealed class Slicer + { + private readonly IEnumerator _iterator; + private readonly int[] _sizes; + private volatile bool _hasNext; + private volatile int _currentSize; + private volatile int _index; + + public Slicer(IEnumerator iterator, int[] sizes) + { + _iterator = iterator; + _sizes = sizes; + _index = 0; + _currentSize = 0; + _hasNext = true; + } + + public int Index + { + get { return _index; } + } + + public IEnumerable> Slice() + { + var length = _sizes.Length; + var index = 1; + var size = 0; + + for (var i = 0; _hasNext; ++i) + { + if (i < length) + { + size = _sizes[i]; + _currentSize = size - 1; + } + + while (_index < index && _hasNext) + { + _hasNext = MoveNext(); + } + + if (_hasNext) + { + yield return new List(SliceInternal()); + index += size; + } + } + } + + private IEnumerable SliceInternal() + { + if (_currentSize == -1) yield break; + yield return _iterator.Current; + + for (var count = 0; count < _currentSize && _hasNext; ++count) + { + _hasNext = MoveNext(); + + if (_hasNext) + { + yield return _iterator.Current; + } + } + } + + private bool MoveNext() + { + ++_index; + return _iterator.MoveNext(); + } + } + [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] public static class EnumerableExtensions { @@ -38,44 +134,6 @@ internal static ReadOnlyCollection Empty #region IEnumerable - private class Status - { - public bool EndOfSequence; - } - - private static IEnumerable TakeOnEnumerator(IEnumerator enumerator, int count, Status status) - { - while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true))) - { - yield return enumerator.Current; - } - } - - - /// - /// Slices the iteration over an enumerable by the given chunk size. - /// - /// - /// - /// SIze of chunk - /// The sliced enumerable - public static IEnumerable> Chunk(this IEnumerable items, int chunkSize = 100) - { - if (chunkSize < 1) - { - throw new ArgumentException("Chunks should not be smaller than 1 element"); - } - var status = new Status { EndOfSequence = false }; - using (var enumerator = items.GetEnumerator()) - { - while (!status.EndOfSequence) - { - yield return TakeOnEnumerator(enumerator, chunkSize, status); - } - } - } - - /// /// Performs an action on each item while iterating through a list. /// This is a handy shortcut for foreach(item in list) { ... } @@ -114,19 +172,16 @@ public static ReadOnlyCollection AsReadOnly(this IEnumerable source) if (source == null || !source.Any()) return DefaultReadOnlyCollection.Empty; - var readOnly = source as ReadOnlyCollection; - if (readOnly != null) + if (source is ReadOnlyCollection readOnly) { return readOnly; } - - var list = source as List; - if (list != null) + else if (source is List list) { return list.AsReadOnly(); } - return new ReadOnlyCollection(source.ToArray()); + return new ReadOnlyCollection(source.ToList()); } /// @@ -186,9 +241,14 @@ public static Dictionary ToDictionarySafe elementSelector, IEqualityComparer comparer) { - Guard.ArgumentNotNull(() => source); - Guard.ArgumentNotNull(() => keySelector); - Guard.ArgumentNotNull(() => 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); @@ -200,38 +260,92 @@ public static Dictionary ToDictionarySafeThe distinct by. + /// The source. + /// The key selector. + /// Source type + /// Key type + /// the unique list + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + where TKey : IEquatable + { + return source.Distinct(GenericEqualityComparer.CompareMember(keySelector)); + } + + /// + /// Orders a collection of entities by a specific ID sequence + /// + /// Entity type + /// The entity collection to sort + /// The IDs to order by + /// The sorted entity collection + public static IEnumerable OrderBySequence(this IEnumerable source, IEnumerable ids) where TEntity : BaseEntity + { + 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 + select entity; + + return sorted; + } + + public static string StrJoin(this IEnumerable source, string separator) + { + return string.Join(separator, source); + } + + #endregion - #region Multimap + #region Multimap - public static Multimap ToMultimap( + public static Multimap ToMultimap( this IEnumerable source, Func keySelector, Func valueSelector) { - Guard.ArgumentNotNull(() => source); - Guard.ArgumentNotNull(() => keySelector); - Guard.ArgumentNotNull(() => 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; + } + + #endregion - #region NameValueCollection + #region NameValueCollection - public static void AddRange(this NameValueCollection initial, NameValueCollection other) + public static void AddRange(this NameValueCollection initial, NameValueCollection other) { - Guard.ArgumentNotNull(initial, "initial"); + if (initial == null) + throw new ArgumentNullException(nameof(initial)); - if (other == null) + if (other == null) return; foreach (var item in other.AllKeys) @@ -280,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/ExceptionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/ExceptionExtensions.cs new file mode 100644 index 0000000000..4f674a61cf --- /dev/null +++ b/src/Libraries/SmartStore.Core/Extensions/ExceptionExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Threading; + +namespace SmartStore +{ + public static class ExceptionExtensions + { + public static bool IsFatal(this Exception ex) + { + return ex is StackOverflowException || + ex is OutOfMemoryException || + ex is AccessViolationException || + ex is AppDomainUnloadedException || + ex is ThreadAbortException || + ex is SecurityException || + ex is SEHException; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs index 1a2ae254fe..59851619db 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs @@ -8,46 +8,188 @@ using System.Web; using System.Web.Caching; using System.Web.Security; +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"; - - /// - /// 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) - { - return (request.IsSecureConnection || (request.ServerVariables[HTTP_CLUSTER_VAR] != null || request.ServerVariables[HTTP_CLUSTER_VAR] == "on")); - } +{ + 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") + }; + + /// + /// Tries to get the instance without throwing exceptions + /// + /// 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; + } + } - /// - /// 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 HttpRequest request) + /// + /// Returns wether the specified url is local to the host or not + /// + /// + /// + /// + public static bool IsAppLocalUrl(this HttpRequestBase request, string url) + { + if (string.IsNullOrWhiteSpace(url)) + { + return false; + } + + if (url.StartsWith("~/")) + { + return true; + } + + if (url.StartsWith("//") || url.StartsWith("/\\")) + { + return false; + } + + // at this point when the url starts with "/" it is local + if (url.StartsWith("/")) + { + return true; + } + + // at this point, check for a fully qualified url + try + { + var uri = new Uri(url); + if (uri.Authority.Equals(request.Headers["Host"], StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // finally, check the base url from the settings + var storeContext = EngineContext.Current.Resolve(); + if (storeContext != null) + { + var baseUrl = storeContext.CurrentStore.Url; + if (baseUrl.HasValue()) + { + if (uri.Authority.Equals(new Uri(baseUrl).Authority, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + catch + { + // mall-formed url e.g, "abcdef" + return false; + } + } + + /// + /// 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 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) { - Guard.ArgumentNotNull(() => webRequest); - Guard.ArgumentNotNull(() => httpRequest); + CopyCookie(webRequest, httpRequest, FormsAuthentication.FormsCookieName); + } + + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public static void SetAnonymousIdentCookie(this HttpWebRequest webRequest, HttpRequestBase httpRequest) + { + CopyCookie(webRequest, httpRequest, "SMARTSTORE.ANONYMOUS"); + } + + private static void CopyCookie(HttpWebRequest webRequest, HttpRequestBase sourceHttpRequest, string cookieName) + { + Guard.NotNull(webRequest, nameof(webRequest)); + Guard.NotNull(sourceHttpRequest, nameof(sourceHttpRequest)); + Guard.NotEmpty(cookieName, nameof(cookieName)); - var authCookie = httpRequest.Cookies[FormsAuthentication.FormsCookieName]; - if (authCookie == null) + var sourceCookie = sourceHttpRequest.Cookies[cookieName]; + if (sourceCookie == null) return; - var sendCookie = new Cookie(authCookie.Name, authCookie.Value, authCookie.Path, httpRequest.Url.Host); + var sendCookie = new Cookie(sourceCookie.Name, sourceCookie.Value, sourceCookie.Path, sourceHttpRequest.Url.Host); if (webRequest.CookieContainer == null) { @@ -59,13 +201,13 @@ public static void SetFormsAuthenticationCookie(this HttpWebRequest webRequest, 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) { - Guard.ArgumentNotEmpty(() => key); - Guard.ArgumentNotNull(() => acquirer); + Guard.NotEmpty(key, nameof(key)); + Guard.NotNull(acquirer, nameof(acquirer)); object obj = cache.Get(key); @@ -87,22 +229,89 @@ public static T GetOrAdd(this Cache cache, string key, Func acquirer, Time return value; } - public static void RemoveByPattern(this Cache cache, string pattern) + public static T GetItem(this HttpContext httpContext, string key, Func factory = null, bool forceCreation = true) { - var regionName = "SmartStoreNET:"; + return GetItem(new HttpContextWrapper(httpContext), key, factory, forceCreation); + } - pattern = pattern == "*" ? "" : pattern; + public static T GetItem(this HttpContextBase httpContext, string key, Func factory = null, bool forceCreation = true) + { + Guard.NotEmpty(key, nameof(key)); - var keys = from entry in HttpRuntime.Cache.AsParallel().Cast() - let key = entry.Key.ToString() - where key.StartsWith(regionName + pattern, StringComparison.OrdinalIgnoreCase) - select key; + var items = httpContext?.Items; + if (items == null) + { + return default(T); + } + + if (items.Contains(key)) + { + return (T)items[key]; + } + else + { + if (forceCreation) + { + var item = items[key] = (factory ?? (() => Activator.CreateInstance())).Invoke(); + return (T)item; + } + else + { + return default(T); + } + } + } + + public static void RemoveByPattern(this Cache cache, string pattern) + { + var keys = cache.AllKeys(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; + + return keys.ToArray(); + } + + public static ControllerContext GetRootControllerContext(this ControllerContext controllerContext) + { + Guard.NotNull(controllerContext, nameof(controllerContext)); + + var ctx = controllerContext; + + while (ctx.ParentActionViewContext != null) + { + ctx = ctx.ParentActionViewContext; + } + + 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 c0731e7538..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.ArgumentNotNull(() => 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 033c5d4230..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,23 +76,13 @@ public static string ToHexString(this byte[] bytes, int length = 0) { sb.Append(b.ToString("x2")); - if (length > 0 && sb.Length >= length) - break; - } - return sb.ToString(); - } - - public static T GetMergedDataValue(this IMergedData mergedData, string key, T defaultValue) - { - if (mergedData.MergedDataValues != null && !mergedData.MergedDataIgnore) - { - object value; - - if (mergedData.MergedDataValues.TryGetValue(key, out value)) - return (T)value; + if (length > 0 && sb.Length >= length) + { + break; + } } - return defaultValue; + return sb.ToString(); } /// @@ -90,20 +93,24 @@ public static T GetMergedDataValue(this IMergedData mergedData, string key, T /// Delimiter to use public static void Grow(this StringBuilder sb, string grow, string delimiter) { - Guard.ArgumentNotNull(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/RegexExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/RegexExtensions.cs new file mode 100644 index 0000000000..02bc3d5ab5 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Extensions/RegexExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace SmartStore +{ + public static class RegexExtensions + { + public static string ReplaceGroup(this Regex regex, string input, string groupName, string replacement) + { + return ReplaceGroupInternal(regex, input, replacement, m => m.Groups[groupName]); + } + + public static string ReplaceGroup(this Regex regex, string input, int groupNum, string replacement) + { + return ReplaceGroupInternal(regex, input, replacement, m => m.Groups[groupNum]); + } + + private static string ReplaceGroupInternal(this Regex regex, string input, string replacement, Func groupGetter) + { + return regex.Replace(input, match => + { + var group = groupGetter(match); + var sb = new StringBuilder(); + var previousCaptureEnd = 0; + + foreach (var capture in group.Captures.Cast()) + { + var currentCaptureEnd = capture.Index + capture.Length - match.Index; + var currentCaptureLength = capture.Index - match.Index - previousCaptureEnd; + + sb.Append(match.Value.Substring(previousCaptureEnd, currentCaptureLength)); + sb.Append(replacement); + + previousCaptureEnd = currentCaptureEnd; + } + + sb.Append(match.Value.Substring(previousCaptureEnd)); + + return sb.ToString(); + }); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs similarity index 75% rename from src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs rename to src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs index f6cfa11a01..845d7a9a79 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Mvc; -using System.Threading.Tasks; using System.Web.Routing; // use base SmartStore Namespace to ensure the extension methods are always available @@ -13,11 +9,11 @@ public static class RouteExtensions { public static string GetAreaName(this RouteData routeData) { - object obj2; - if (routeData.DataTokens.TryGetValue("area", out obj2)) + if (routeData.DataTokens.TryGetValue("area", out object area)) { - return (obj2 as string); + return (area as string); } + return routeData.Route.GetAreaName(); } @@ -28,19 +24,21 @@ 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"); string action = routeData.GetRequiredString("action"); diff --git a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs index f958bdaf73..6b286561a0 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs @@ -26,17 +26,19 @@ public static bool ToFile(this Stream srcStream, string path) return false; const int BuffSize = 32768; - bool result = true; + var result = true; Stream dstStream = null; - byte[] buffer = new Byte[BuffSize]; + var buffer = new byte[BuffSize]; try { - using (dstStream = File.OpenWrite(path)) - { + using (dstStream = File.Open(path, FileMode.Create)) + { int len; - while ((len = srcStream.Read(buffer, 0, BuffSize)) > 0) - dstStream.Write(buffer, 0, len); + while ((len = srcStream.Read(buffer, 0, BuffSize)) > 0) + { + dstStream.Write(buffer, 0, len); + } } } catch @@ -52,27 +54,31 @@ public static bool ToFile(this Stream srcStream, string path) } } - return (result && System.IO.File.Exists(path)); + return (result && File.Exists(path)); } public static bool ContentsEqual(this Stream src, Stream other) { - Guard.ArgumentNotNull(() => src); - Guard.ArgumentNotNull(() => other); + if (src == null) + throw new ArgumentNullException(nameof(src)); + + if (other == null) + throw new ArgumentNullException(nameof(other)); - if (src.Length != other.Length) + 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) @@ -81,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 9131705215..211fb926af 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -9,43 +9,47 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using AngleSharp.Dom; +using AngleSharp.Parser.Html; +using SmartStore.Core.Html; namespace SmartStore { public static class StringExtensions { - public const string CarriageReturnLineFeed = "\r\n"; + public const string CarriageReturnLineFeed = "\r\n"; public const string Empty = ""; public const char CarriageReturn = '\r'; 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(); @@ -54,7 +58,7 @@ public static string ToUnicode(this char c) internal static void WriteCharAsUnicode(char c, TextWriter writer) { - Guard.ArgumentNotNull(writer, "writer"); + Guard.NotNull(writer, "writer"); char h1 = ((c >> 12) & '\x000f').ToHex(); char h2 = ((c >> 8) & '\x000f').ToHex(); @@ -69,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()) @@ -97,7 +112,8 @@ public static string ToSafe(this string value, string defaultValue = null) { return value; } - return (defaultValue ?? String.Empty); + + return (defaultValue ?? string.Empty); } [DebuggerStepThrough] @@ -207,9 +223,10 @@ public static bool IsEmpty(this string value) [DebuggerStepThrough] public static bool IsWhiteSpace(this string value) { - Guard.ArgumentNotNull(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++) @@ -261,16 +278,39 @@ public static string Mask(this string value, int length) { if (value.HasValue()) return value.Substring(0, length) + new String('*', value.Length - length); + return value; } - [DebuggerStepThrough] - public static bool IsWebUrl(this string value) + private static bool IsWebUrlInternal(string value, bool schemeIsOptional) { - return !String.IsNullOrEmpty(value) && RegularExpressions.IsWebUrl.IsMatch(value.Trim()); - } + if (String.IsNullOrEmpty(value)) + return false; - [DebuggerStepThrough] + value = value.Trim().ToLowerInvariant(); + + if (schemeIsOptional && value.StartsWith("//")) + { + value = "http:" + value; + } + + return Uri.IsWellFormedUriString(value, UriKind.Absolute) && + (value.StartsWith("http://") || value.StartsWith("https://") || value.StartsWith("ftp://")); + } + + [DebuggerStepThrough] + public static bool IsWebUrl(this string value) + { + return IsWebUrlInternal(value, false); + } + + [DebuggerStepThrough] + public static bool IsWebUrl(this string value, bool schemeIsOptional) + { + return IsWebUrlInternal(value, schemeIsOptional); + } + + [DebuggerStepThrough] public static bool IsEmail(this string value) { return !String.IsNullOrEmpty(value) && RegularExpressions.IsEmail.IsMatch(value.Trim()); @@ -317,17 +357,19 @@ public static bool IsAlphaNumeric(this string value) [DebuggerStepThrough] public static string Truncate(this string value, int maxLength, string suffix = "") { - Guard.ArgumentNotNull(suffix, "suffix"); - Guard.ArgumentIsPositive(maxLength, "maxLength"); + 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; @@ -340,6 +382,104 @@ public static string Truncate(this string value, int maxLength, string suffix = } } + /// + /// 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, bool removeEmptyLines = false) + { + Guard.NotNull(input, nameof(input)); + + var sb = new StringBuilder(); + var lines = GetLines(input.Trim(), true, removeEmptyLines).ToArray(); + + foreach (var line in lines) + { + var len = line.Length; + var sbLine = new StringBuilder(len); + var isChar = false; + var isLiteral = false; // When we detect the ~! literal + int i = 0; + var eof = false; + + for (i = 0; i < len; i++) + { + var c = line[i]; + + eof = i == len - 1; + + if (Char.IsWhiteSpace(c)) + { + // Space, Tab etc. + if (isChar) + { + // If last char not empty, append the space. + sbLine.Append(' '); + } + + isLiteral = false; + isChar = false; + } + else + { + // Char or Literal (~!) + + isLiteral = c == '~' && !eof && line[i + 1] == '!'; + isChar = true; + + if (isLiteral) + { + sbLine.Append(' '); + i++; // skip next "!" char + } + else + { + sbLine.Append(c); + } + } + } + + // Append the compacted and trimmed line + sb.AppendLine(sbLine.ToString().Trim().Trim(',')); + } + + return sb.ToString().Trim(); + } + + /// + /// Splits the input string by carriage return. + /// + /// The string to split + /// A sequence with string items per line + public static IEnumerable GetLines(this string input, bool trimLines = false, bool removeEmptyLines = false) + { + if (input.IsEmpty()) + { + yield break; + } + + using (var sr = new StringReader(input)) + { + string line; + while ((line = sr.ReadLine()) != null) + { + if (trimLines) + { + line = line.Trim(); + } + + if (removeEmptyLines && IsEmpty(line)) + { + continue; + } + + yield return line; + } + } + } + /// /// Ensure that a string starts with a string. /// @@ -349,8 +489,11 @@ public static string Truncate(this string value, int maxLength, string suffix = [DebuggerStepThrough] public static string EnsureStartsWith(this string value, string startsWith) { - Guard.ArgumentNotNull(value, "value"); - Guard.ArgumentNotNull(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); } @@ -364,10 +507,13 @@ public static string EnsureStartsWith(this string value, string startsWith) [DebuggerStepThrough] public static string EnsureEndsWith(this string value, string endWith) { - Guard.ArgumentNotNull(value, "value"); - Guard.ArgumentNotNull(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; @@ -411,42 +557,16 @@ public static string HtmlDecode(this string value) return HttpUtility.HtmlDecode(value); } - [DebuggerStepThrough] - public static string RemoveHtml(this string value) - { - return RemoveHtmlInternal(value, null); - } - - public static string RemoveHtml(this string value, ICollection removeTags) - { - return RemoveHtmlInternal(value, removeTags); - } - - private static string RemoveHtmlInternal(string s, ICollection removeTags) - { - List removeTagsUpper = null; - if (removeTags != null) - { - removeTagsUpper = new List(removeTags.Count); - - foreach (string tag in removeTags) - { - removeTagsUpper.Add(tag.ToUpperInvariant()); - } - } - - return RegularExpressions.RemoveHTML.Replace(s, delegate(Match match) - { - string tag = match.Groups["tag"].Value.ToUpperInvariant(); + [Obsolete("The 'removeTags' parameter is not supported anymore. Use the parameterless method instead.")] + public static string RemoveHtml(this string source, ICollection removeTags) + { + return RemoveHtml(source); + } - if (removeTagsUpper == null) - return string.Empty; - else if (removeTagsUpper.Contains(tag)) - return string.Empty; - else - return match.Value; - }); - } + public static string RemoveHtml(this string source) + { + return HtmlUtils.StripTags(source).Trim().HtmlDecode(); + } /// /// Replaces pascal casing with spaces. For example "CustomerId" would become "Customer Id". @@ -457,10 +577,10 @@ private static string RemoveHtmlInternal(string s, ICollection removeTag [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]; @@ -470,6 +590,7 @@ public static string SplitPascalCase(this string value) } sb.Append(c); } + if (ca.Length > 1) { sb.Append(ca[ca.Length - 1]); @@ -478,30 +599,68 @@ public static string SplitPascalCase(this string value) return sb.ToString(); } - [DebuggerStepThrough] + /// + /// Splits a string into a string array + /// + /// String value to split + /// If null then value is searched for a common delimiter like pipe, semicolon or comma + /// String array + [DebuggerStepThrough] public static string[] SplitSafe(this string value, string separator) { if (string.IsNullOrEmpty(value)) return new string[0]; - return value.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); + + // Do not use separator.IsEmpty() here because whitespace like " " is a valid separator. + // an empty separator "" returns array with value. + if (separator == null) + { + for (var i = 0; i < value.Length; i++) + { + var c = value[i]; + if (c == ';' || c == ',' || c == '|') + { + return value.Split(new char[] { c }, StringSplitOptions.RemoveEmptyEntries); + } + if (c == '\r' && (i + 1) < value.Length & value[i + 1] == '\n') + { + return value.GetLines(false, true).ToArray(); + } + } + + return new string[] { value }; + } + else + { + return value.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); + } } /// Splits a string into two strings /// true: success, false: failure - [DebuggerStepThrough] + [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; } @@ -515,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(); @@ -633,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) { @@ -643,24 +804,29 @@ 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 == null ? "" : replaceBy) + value.Substring(x2 + 1); + return value.Substring(0, x1) + (replaceBy.EmptyNull()) + value.Substring(x2 + 1); } + return value; } @@ -685,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) { @@ -695,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; @@ -722,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; } @@ -733,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"); } @@ -783,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] @@ -832,16 +1022,24 @@ 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()); - char[] invalidChars = isPath ? Path.GetInvalidPathChars() : Path.GetInvalidFileNameChars(); + replacement = replacement ?? "-"; - foreach (var c in invalidChars) - { - result = result.Replace(c.ToString(), replacement ?? "-"); - } + var sb = new StringBuilder(); + foreach (var c in input.ToSafe()) + { + if (invalidChars.Contains(c)) + { + sb.Append(replacement); + } + else + { + sb.Append(c); + } + } - return result; + return sb.ToString(); } [DebuggerStepThrough] @@ -858,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); } @@ -884,11 +1083,37 @@ public static string ReplaceCsvChars(this string s) return s.Replace("'", ""); } + [DebuggerStepThrough] + public static string HighlightKeywords(this string input, string keywords, string preMatch = "", string postMatch = "") + { + Guard.NotNull(preMatch, nameof(preMatch)); + Guard.NotNull(postMatch, nameof(postMatch)); + + if (string.IsNullOrWhiteSpace(input) || string.IsNullOrWhiteSpace(keywords)) + { + return input; + } + + var pattern = String.Join("|", keywords.Trim().Split(' ', '-') + .Select(x => x.Trim()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => Regex.Escape(x)) + .Distinct()); + + if (!string.IsNullOrWhiteSpace(pattern)) + { + var rg = new Regex(pattern, RegexOptions.IgnoreCase); + input = rg.Replace(input, m => preMatch + m.Value.EmptyNull().HtmlEncode() + postMatch); + } + + return input; + } + #endregion - #region Helper + #region Helper - private static void EncodeJsChar(TextWriter writer, char c, char delimiter) + private static void EncodeJsChar(TextWriter writer, char c, char delimiter) { switch (c) { diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index baf9195b18..b6832c9dee 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Collections; namespace SmartStore { @@ -15,24 +15,51 @@ public static class TypeExtensions public static string AssemblyQualifiedNameWithoutVersion(this Type type) { - Guard.ArgumentNotNull(() => 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], strArray[1]); + return string.Format("{0}, {1}", strArray[0].Trim(), strArray[1].Trim()); } return null; } - public static bool IsSequenceType(this Type seqType) + 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) { - return ( - (((seqType != typeof(string)) - && (seqType != typeof(byte[]))) - && (seqType != typeof(char[]))) - && (FindIEnumerable(seqType) != null)); + if (type == typeof(string)) + return false; + + return type.IsArray || typeof(IEnumerable).IsAssignableFrom(type); } public static bool IsPredefinedSimpleType(this Type type) @@ -41,6 +68,7 @@ public static bool IsPredefinedSimpleType(this Type type) { return true; } + if (type.IsEnum) { return true; @@ -55,6 +83,7 @@ public static bool IsStruct(this Type type) { return !type.IsPredefinedSimpleType(); } + return false; } @@ -78,10 +107,16 @@ public static bool IsPredefinedType(this Type type) { return (string.Compare(type.FullName, "System.Xml.Linq.XElement", StringComparison.Ordinal) == 0); } + return true; } - public static bool IsInteger(this Type type) + public static bool IsPlainObjectType(this Type type) + { + return type.IsClass && !type.IsSequenceType() && !type.IsPredefinedType(); + } + + public static bool IsInteger(this Type type) { switch (Type.GetTypeCode(type)) { @@ -99,16 +134,24 @@ public static bool IsInteger(this Type type) } } - public static bool IsNullable(this Type type) + public static bool IsNullable(this Type type, out Type wrappedType) { - return type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + wrappedType = null; + + if (type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + wrappedType = type.GetGenericArguments()[0]; + } + + return false; } public static bool IsConstructable(this Type type) { - Guard.ArgumentNotNull(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)) @@ -133,15 +176,17 @@ public static bool IsAnonymous(this Type type) } } } + return false; } [DebuggerStepThrough] public static bool HasDefaultConstructor(this Type type) { - Guard.ArgumentNotNull(() => type); + if (type == null) + throw new ArgumentNullException(nameof(type)); - if (type.IsValueType) + if (type.IsValueType) return true; return type.GetConstructors(BindingFlags.Instance | BindingFlags.Public) @@ -150,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.ArgumentNotNull(type, "type"); - Guard.ArgumentNotNull(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) @@ -209,11 +256,12 @@ private static bool IsSubClassInternal(Type initialType, Type currentType, Type /// public static Type GetNonNullableType(this Type type) { - if (!IsNullable(type)) + if (!IsNullable(type, out var wrappedType)) { return type; } - return type.GetGenericArguments()[0]; + + return wrappedType; } /// @@ -233,6 +281,7 @@ public static TAttribute GetAttribute(this ICustomAttributeProvider { throw Error.MoreThanOneElement(); } + return (TAttribute)attributes[0]; } @@ -294,6 +343,7 @@ public static TAttribute[] GetAllAttributes(this MemberInfo member, } } } + attributes.AddRange(GetAttributes(member, inherits)); return attributes.ToArray(); } @@ -318,7 +368,7 @@ internal static IEnumerable SortAttributesIfPossible(IEn /// PropertyInfo for the property, or null if method is not part of a property. public static PropertyInfo GetPropertyFromMethod(this MethodBase method) { - Guard.ArgumentNotNull(method, "method"); + Guard.NotNull(method, "method"); PropertyInfo property = null; if (method.IsSpecialName) @@ -341,8 +391,10 @@ internal static Type FindIEnumerable(this Type seqType) { if (seqType == null || seqType == typeof(string)) return null; + if (seqType.IsArray) return typeof(IEnumerable<>).MakeGenericType(seqType.GetElementType()); + if (seqType.IsGenericType) { var args = seqType.GetGenericArguments(); @@ -353,6 +405,7 @@ internal static Type FindIEnumerable(this Type seqType) return ienum; } } + Type[] ifaces = seqType.GetInterfaces(); if (ifaces.Length > 0) { @@ -363,8 +416,10 @@ internal static Type FindIEnumerable(this Type seqType) return ienum; } } + if (seqType.BaseType != null && seqType.BaseType != typeof(object)) return FindIEnumerable(seqType.BaseType); + return null; } } 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 233c011857..ee5ecc7928 100644 --- a/src/Libraries/SmartStore.Core/Fakes/FakeHttpRequest.cs +++ b/src/Libraries/SmartStore.Core/Fakes/FakeHttpRequest.cs @@ -2,6 +2,7 @@ using System.Collections.Specialized; using System.Web; using System.Web.Mvc; +using System.Web.Routing; namespace SmartStore.Core.Fakes { @@ -15,6 +16,7 @@ public class FakeHttpRequest : HttpRequestBase private readonly Uri _url; private readonly Uri _urlReferrer; private readonly string _httpMethod; + private RequestContext _requestContext; public FakeHttpRequest(string relativeUrl, Uri url, Uri urlReferrer) : this(relativeUrl, HttpVerbs.Get.ToString("g"), url, urlReferrer, null, null, null, null) @@ -113,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; @@ -134,30 +136,24 @@ public override string UserHostAddress get { return null; } } - public override string RawUrl - { - get { return this.ApplicationPath; } - } - - public override bool IsSecureConnection - { - get { return false; } - } - - public override bool IsAuthenticated - { - get - { - return false; - } - } + public override string RawUrl => this.ApplicationPath; + 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 string[] UserLanguages + public override RequestContext RequestContext { - get + get + { + return _requestContext ?? new RequestContext(); + } + + set { - return new string[] { }; + _requestContext = value; } } - } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/GenericComparer.cs b/src/Libraries/SmartStore.Core/GenericComparer.cs deleted file mode 100644 index 679ebf9e04..0000000000 --- a/src/Libraries/SmartStore.Core/GenericComparer.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SmartStore.Core -{ - /// - /// This class is used to compare any - /// type(property) of a class for sorting. - /// This class automatically fetches the - /// type of the property and compares. - /// - public sealed partial class GenericComparer : IComparer - { - #region Enums - /// - /// The sort order direction for sorting the collection - /// - public enum SortOrder - { - /// - /// Ascending - /// - Ascending, - /// - /// Descending - /// - Descending - }; - #endregion - - #region Ctor - /// - /// Creates a new instance of the GenericComparer class - /// - /// The property on which the collection should be sorted - /// The direction of the sort - public GenericComparer(string sortColumn, SortOrder sortingOrder) - { - this._sortColumn = sortColumn; - this._sortingOrder = sortingOrder; - } - #endregion - - #region Fields - - private readonly string _sortColumn; - private readonly SortOrder _sortingOrder; - - #endregion - - #region Properties - - /// - /// Column Name(public property of the class) to be sorted. - /// - public string SortColumn - { - get { return _sortColumn; } - } - - /// - /// Sorting order. - /// - public SortOrder SortingOrder - { - get { return _sortingOrder; } - } - - #endregion - - #region Methods - /// - /// Compare interface implementation - /// - /// custom Object - /// custom Object - /// int - public int Compare(T x, T y) - { - var propertyInfo = typeof(T).GetProperty(_sortColumn); - var obj1 = (IComparable)propertyInfo.GetValue(x, null); - var obj2 = (IComparable)propertyInfo.GetValue(y, null); - if (_sortingOrder == SortOrder.Ascending) - { - return (obj1.CompareTo(obj2)); - } - else - { - return (obj2.CompareTo(obj1)); - } - } - #endregion - } -} diff --git a/src/Libraries/SmartStore.Core/GenericEqualityComparer.cs b/src/Libraries/SmartStore.Core/GenericEqualityComparer.cs new file mode 100644 index 0000000000..dd66b7b1a7 --- /dev/null +++ b/src/Libraries/SmartStore.Core/GenericEqualityComparer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core +{ + public sealed partial class GenericEqualityComparer : IEqualityComparer + { + private readonly Func _equals; + private readonly Func _getHashcode; + + public GenericEqualityComparer(Func equals, Func getHashcode) + { + _getHashcode = getHashcode; + _equals = equals; + } + + public static GenericEqualityComparer CompareMember(Func memberExpression) where TMember : IEquatable + { + return new GenericEqualityComparer( + (x, y) => memberExpression.Invoke(x).Equals((TMember)memberExpression.Invoke(y)), + x => + { + var invoked = memberExpression.Invoke(x); + return !ReferenceEquals(invoked, default(TMember)) ? invoked.GetHashCode() : 0; + }); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// + /// true if the specified objects are equal; otherwise, false. + /// + /// The first object of type to compare.The second object of type to compare. + public bool Equals(T x, T y) + { + return _equals.Invoke(x, y); + } + + /// + /// Returns a hash code for the specified object. + /// + /// + /// A hash code for the specified object. + /// + /// The for which a hash code is to be returned.The type of is a reference type and is null. + public int GetHashCode(T obj) + { + return _getHashcode.Invoke(obj); + } + } +} 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/CodeFormatter/CodeFormatHelper.cs b/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs index a840a6ed53..e58e51d428 100644 --- a/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs +++ b/src/Libraries/SmartStore.Core/Html/CodeFormatter/CodeFormatHelper.cs @@ -38,7 +38,6 @@ private static string CodeEvaluatorSimple(Match match) string result = match.Value; result = Highlight(options, result); return result; - } /// diff --git a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs index f5f5527993..f3239a1b15 100644 --- a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs +++ b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs @@ -3,143 +3,142 @@ using System.Text.RegularExpressions; using System.Web; using System.Linq; +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 { /// - /// Represents a HTML helper + /// Utility class for html manipulation or creation /// public partial class HtmlUtils { - #region Fields - 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); - - #endregion - - #region Utilities - - private static string EnsureOnlyAllowedHtml(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,blockquote,img,span,p,em,strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite"; - - 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(); + 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); + + /// + /// 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) + { + + if (String.IsNullOrEmpty(text)) + return string.Empty; + + 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); - } - #endregion + return text; + } - #region Methods - /// - /// 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); - } - - if (resolveLinks) - { - text = ResolveLinksHelper.FormatText(text); - } + var sanitizer = new HtmlSanitizer(); - 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 @@ -159,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", "
"); @@ -178,8 +177,7 @@ public static string ConvertPlainTextToHtml(string text) /// A value indicating whether to decode text /// A value indicating whether to replace anchor text (remove a tag from the following url Name and output only the string "Name") /// Formatted text - public static string ConvertHtmlToPlainText(string text, - bool decode = false, bool replaceAnchorTags = false) + public static string ConvertHtmlToPlainText(string text, bool decode = false, bool replaceAnchorTags = false) { if (String.IsNullOrEmpty(text)) return string.Empty; @@ -257,8 +255,8 @@ public static string ConvertPlainTextToParagraph(string text) if (String.IsNullOrEmpty(text)) return string.Empty; - text = paragraphStartRegex.Replace(text, string.Empty); - text = paragraphEndRegex.Replace(text, "\n"); + text = _paragraphStartRegex.Replace(text, string.Empty); + text = _paragraphEndRegex.Replace(text, "\n"); text = text.Replace("\r\n", "\n").Replace("\r", "\n"); text = text + "\n\n"; text = text.Replace("\n\n", "\n"); @@ -271,8 +269,39 @@ public static string ConvertPlainTextToParagraph(string text) builder.AppendFormat("

{0}

\n", str); } } + return builder.ToString(); } - #endregion + + /// + /// Converts all occurences of pixel-based inline font-size expression to relative 'em' + /// + /// + /// + /// + public static string RelativizeFontSizes(string html, int baseFontSizePx = 16) + { + Guard.NotEmpty(html, nameof(html)); + Guard.IsPositive(baseFontSizePx, nameof(baseFontSizePx)); + + var parser = new HtmlParser(new AngleSharp.Configuration().WithCss()); + var doc = parser.Parse(html); + + var nodes = doc.QuerySelectorAll("*[style]"); + foreach (var node in nodes) + { + if (node.Style.FontSize is string s && s.EndsWith("px")) + { + var size = s.Substring(0, s.Length - 2).Convert(); + if (size > 0) + { + //node.Style.FontSize = Math.Round(((double)size / (double)baseFontSizePx), 4) + "em"; + node.Style.FontSize = "{0}em".FormatInvariant(Math.Round(((double)size / (double)baseFontSizePx), 4)); + } + } + } + + return doc.Body.InnerHtml; + } } } 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/IAuditable.cs b/src/Libraries/SmartStore.Core/IAuditable.cs new file mode 100644 index 0000000000..60f4342efc --- /dev/null +++ b/src/Libraries/SmartStore.Core/IAuditable.cs @@ -0,0 +1,17 @@ +using System; + +namespace SmartStore.Core +{ + public interface IAuditable + { + /// + /// Gets or sets the date and time of entity creation + /// + DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the date and time of entity update + /// + DateTime UpdatedOnUtc { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/IMergedData.cs b/src/Libraries/SmartStore.Core/IMergedData.cs index fa1b2e61b5..8233a55987 100644 --- a/src/Libraries/SmartStore.Core/IMergedData.cs +++ b/src/Libraries/SmartStore.Core/IMergedData.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using System.Web.Hosting; +using SmartStore.Core.Data; +using SmartStore.Core.Infrastructure; namespace SmartStore.Core { @@ -7,4 +10,38 @@ public interface IMergedData bool MergedDataIgnore { get; set; } Dictionary MergedDataValues { get; } } + + public static class IMergedDataExtensions + { + public static T GetMergedDataValue(this IMergedData mergedData, string key, T defaultValue) + { + if (mergedData.MergedDataValues == null) + return defaultValue; + + if (mergedData.MergedDataIgnore) + return defaultValue; + + if (mergedData is BaseEntity && HostingEnvironment.IsHosted) + { + // This is absolutely bad coding! But I don't see any alternatives. + // When the passed object is a (EF)-trackable entity, + // we cannot return the merged value while EF performs + // change detection, because entity properties could be set to modified, + // where in fact nothing has changed. + var dbContext = EngineContext.Current.Resolve(); + if (dbContext.IsDetectingChanges()) + { + return defaultValue; + } + } + + object value; + if (mergedData.MergedDataValues.TryGetValue(key, out value)) + { + return (T)value; + } + + return defaultValue; + } + } } diff --git a/src/Libraries/SmartStore.Core/IO/FileSystemSettings.cs b/src/Libraries/SmartStore.Core/IO/FileSystemSettings.cs deleted file mode 100644 index ced6250fe4..0000000000 --- a/src/Libraries/SmartStore.Core/IO/FileSystemSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -using SmartStore.Core.Configuration; - -namespace SmartStore.Core.IO -{ - public class FileSystemSettings : ISettings - { - public string DirectoryName { get; set; } - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/IFile.cs b/src/Libraries/SmartStore.Core/IO/IFile.cs new file mode 100644 index 0000000000..6d86299fc8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/IFile.cs @@ -0,0 +1,72 @@ +using System; +using System.Drawing; +using System.IO; +using System.Threading.Tasks; + +namespace SmartStore.Core.IO +{ + public interface IFile + { + /// + /// 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; } + + /// + /// File extension including dot + /// + string Extension { get; } + + /// + /// Dimensions, if the file is an image. + /// + Size Dimensions { get; } + + bool Exists { get; } + + /// + /// Creates a stream for reading from the file. + /// + Stream OpenRead(); + + /// + /// Creates a stream for writing to the file. + /// + Stream OpenWrite(); + + /// + /// Creates a stream for writing to the file, and truncates the existing content. + /// + Stream CreateFile(); + + /// + /// Asynchronously creates a stream for writing to the file, and truncates the existing content. + /// + Task CreateFileAsync(); + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/IFileSystem.cs b/src/Libraries/SmartStore.Core/IO/IFileSystem.cs new file mode 100644 index 0000000000..c90b886ffb --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/IFileSystem.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace SmartStore.Core.IO +{ + public interface IFileSystem + { + /// + /// Checks whether the underlying storage is remote, like 'Azure' for example. + /// + bool IsCloudStorage { get; } + + /// + /// Gets the root path + /// + string Root { get; } + + /// + /// 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, bool forCloud = false); + + /// + /// Retrieves the path within the storage provider for a given public url. + /// + /// The virtual or public url of a file. + /// The storage path or null if the media is not in a correct format. + string GetStoragePath(string url); + + /// + /// Checks if the given file exists within the storage provider. + /// + /// The relative path within the storage provider. + /// True if the file exists; False otherwise. + bool FileExists(string path); + + /// + /// Checks if the given folder exists within the storage provider. + /// + /// The relative path within the storage provider. + /// True if the folder exists; False otherwise. + bool FolderExists(string path); + + /// + /// Retrieves a file within the storage provider. + /// + /// The relative path to the file within the storage provider. + /// The file. + /// If the file is not found. + IFile GetFile(string path); + + /// + /// Retrieves a folder within the storage provider. + /// + /// The relative path to the folder within the storage provider. + /// The folder. + /// If the folder is not found. + IFolder GetFolder(string path); + + /// + /// Retrieves a folder for file path within the storage provider. + /// + /// The relative path to the file within the storage provider. + /// The folder for the file. + /// 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, bool deep = true); + + /// + /// Lists the files within a storage provider's path. + /// + /// The relative path to the folder which files to list. + /// The list of files in the folder. + IEnumerable ListFiles(string path); + + /// + /// Lists the folders within a storage provider's path. + /// + /// The relative path to the folder which folders to list. + /// The list of folders in the folder. + IEnumerable ListFolders(string path); + + /// + /// Creates a folder in the storage provider. + /// + /// The relative path to the folder to be created. + /// If the folder already exists. + void CreateFolder(string path); + + /// + /// Deletes a folder in the storage provider. + /// + /// The relative path to the folder to be deleted. + /// If the folder doesn't exist. + void DeleteFolder(string path); + + /// + /// Renames a folder in the storage provider. + /// + /// The relative path to the folder to be renamed. + /// The relative path to the new folder. + void RenameFolder(string path, string newPath); + + /// + /// Deletes a file in the storage provider. + /// + /// The relative path to the file to be deleted. + /// If the file doesn't exist. + void DeleteFile(string path); + + /// + /// Renames a file in the storage provider. + /// + /// The relative path to the file to be renamed. + /// The relative path to the new file. + void RenameFile(string path, string newPath); + + /// + /// Creates a file in the storage provider. + /// + /// The relative path to the file to be created. + /// If the file already exists. + /// The created file. + IFile CreateFile(string path); + + /// + /// Asynchronously creates a file in the storage provider. + /// + /// The relative path to the file to be created. + /// If the file already exists. + /// The created file. + Task CreateFileAsync(string path); + + /// + /// Copies a file in the storage provider. + /// + /// The relative path to the file to be copied. + /// The relative path to the new file. + void CopyFile(string path, string newPath); + + /// + /// Saves a stream in the storage provider. + /// + /// The relative path to the file to be created. + /// The stream to be saved. + /// If the stream can't be saved due to access permissions. + void SaveStream(string path, Stream inputStream); + + /// + /// Asynchronously saves a stream in the storage provider. + /// + /// The relative path to the file to be created. + /// The stream to be saved. + /// If the stream can't be saved due to access permissions. + Task SaveStreamAsync(string path, Stream inputStream); + + /// + /// Combines to paths. + /// + /// The parent path. + /// The child path. + /// The combined path. + string Combine(string path1, string path2); + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs b/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs new file mode 100644 index 0000000000..755aee74e1 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Core.IO +{ + public static class IFileSystemExtensions + { + public static void WriteAllText(this IFileSystem fileSystem, string path, string contents) + { + Guard.NotEmpty(path, nameof(path)); + Guard.NotEmpty(contents, nameof(contents)); + + if (fileSystem.FileExists(path)) + { + fileSystem.DeleteFile(path); + } + + var file = fileSystem.CreateFile(path); + using (var stream = file.OpenWrite()) + using (var streamWriter = new StreamWriter(stream)) + { + streamWriter.Write(contents); + } + } + + public static async Task WriteAllTextAsync(this IFileSystem fileSystem, string path, string contents) + { + Guard.NotEmpty(path, nameof(path)); + Guard.NotEmpty(contents, nameof(contents)); + + if (fileSystem.FileExists(path)) + { + fileSystem.DeleteFile(path); + } + + var file = fileSystem.CreateFile(path); + using (var stream = file.OpenWrite()) + using (var streamWriter = new StreamWriter(stream)) + { + await streamWriter.WriteAsync(contents); + } + } + + public static string ReadAllText(this IFileSystem fileSystem, string path) + { + Guard.NotEmpty(path, nameof(path)); + + if (!fileSystem.FileExists(path)) + { + return String.Empty; + } + + var file = fileSystem.GetFile(path); + using (var stream = file.OpenRead()) + using (var streamReader = new StreamReader(stream)) + { + return streamReader.ReadToEnd(); + } + } + + public static async Task ReadAllTextAsync(this IFileSystem fileSystem, string path) + { + Guard.NotEmpty(path, nameof(path)); + + if (!fileSystem.FileExists(path)) + { + return String.Empty; + } + + var file = fileSystem.GetFile(path); + using (var stream = file.OpenRead()) + using (var streamReader = new StreamReader(stream)) + { + return await streamReader.ReadToEndAsync(); + } + } + + + public static void WriteAllBytes(this IFileSystem fileSystem, string path, byte[] contents) + { + Guard.NotEmpty(path, nameof(path)); + Guard.NotNull(contents, nameof(contents)); + + if (fileSystem.FileExists(path)) + { + fileSystem.DeleteFile(path); + } + + var file = fileSystem.CreateFile(path); + using (var stream = file.OpenWrite()) + { + stream.Write(contents, 0, contents.Length); + } + } + + public static async Task WriteAllBytesAsync(this IFileSystem fileSystem, string path, byte[] contents) + { + Guard.NotEmpty(path, nameof(path)); + Guard.NotNull(contents, nameof(contents)); + + if (fileSystem.FileExists(path)) + { + fileSystem.DeleteFile(path); + } + + var file = await fileSystem.CreateFileAsync(path); + using (var stream = file.OpenWrite()) + { + await stream.WriteAsync(contents, 0, contents.Length); + } + } + + public static byte[] ReadAllBytes(this IFileSystem fileSystem, string path) + { + Guard.NotEmpty(path, nameof(path)); + + if (!fileSystem.FileExists(path)) + { + return null; + } + + var file = fileSystem.GetFile(path); + using (var stream = file.OpenRead()) + { + return stream.ToByteArray(); + } + } + + public static async Task ReadAllBytesAsync(this IFileSystem fileSystem, string path) + { + Guard.NotEmpty(path, nameof(path)); + + if (!fileSystem.FileExists(path)) + { + return null; + } + + var file = fileSystem.GetFile(path); + using (var stream = file.OpenRead()) + { + return await stream.ToByteArrayAsync(); + } + } + + + /// + /// Tries to save a stream in the storage provider. + /// + /// The relative path to the file to be created. + /// The stream to be saved. + /// True if success; False otherwise. + public static bool TrySaveStream(this IFileSystem fileSystem, string path, Stream inputStream) + { + try + { + if (fileSystem.FileExists(path)) + { + return false; + } + + fileSystem.SaveStream(path, inputStream); + } + catch + { + return false; + } + + return true; + } + + /// + /// Asynchronously tries to save a stream in the storage provider. + /// + /// The relative path to the file to be created. + /// The stream to be saved. + /// True if success; False otherwise. + public static async Task TrySaveStreamAsync(this IFileSystem fileSystem, string path, Stream inputStream) + { + try + { + if (fileSystem.FileExists(path)) + { + return false; + } + + await fileSystem.SaveStreamAsync(path, inputStream); + } + catch + { + return false; + } + + return true; + } + + /// + /// Tries to create a folder in the storage provider. + /// + /// The relative path to the folder to be created. + /// True if success; False otherwise. + public static bool TryCreateFolder(this IFileSystem fileSystem, string path) + { + try + { + // prevent unnecessary exception + if (fileSystem.FolderExists(path)) + { + return false; + } + + fileSystem.CreateFolder(path); + } + catch + { + return false; + } + + 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/IFolder.cs b/src/Libraries/SmartStore.Core/IO/IFolder.cs new file mode 100644 index 0000000000..a911def2c8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/IFolder.cs @@ -0,0 +1,14 @@ +using System; + +namespace SmartStore.Core.IO +{ + public interface IFolder + { + string Path { get; } + string Name { get; } + long Size { get; } + bool Exists { get; } + DateTime LastUpdated { get; } + IFolder Parent { get; } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..9591f17d81 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/LocalFileSystem.cs @@ -0,0 +1,649 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using System.Web.Hosting; +using SmartStore.Utilities; + +namespace SmartStore.Core.IO +{ + public class LocalFileSystem : IFileSystem + { + private string _root; + private string _publicPath; // /Shop/base + private string _storagePath; // C:\SMNET\base + private bool _isCloudStorage; // When public URL is outside of current app + + public LocalFileSystem() + : this(string.Empty, string.Empty) + { + } + + protected internal LocalFileSystem(string basePath) + : this(basePath, string.Empty) + { + } + + protected internal LocalFileSystem(string basePath, string publicPath) + { + basePath = basePath.EmptyNull(); + + var pathIsAbsolute = PathHelper.IsAbsolutePhysicalPath(basePath); + + NormalizeStoragePath(ref basePath, pathIsAbsolute); + + _publicPath = NormalizePublicPath(publicPath, basePath, pathIsAbsolute); + + _root = basePath; + } + + public bool IsCloudStorage + { + get { return _isCloudStorage; } + } + + private void NormalizeStoragePath(ref string basePath, bool basePathIsAbsolute) + { + if (basePathIsAbsolute) + { + // Path is fully qualified (UNC or absolute with drive letter) + + // In this case this is our root path... + basePath = basePath.TrimEnd(Path.DirectorySeparatorChar); + + // ...AND our physical storage path + _storagePath = basePath; + } + else + { + // Path is relative to the app root + basePath = basePath.TrimEnd('/').EnsureStartsWith("/"); + _storagePath = CommonHelper.MapPath("~" + basePath, false); + } + } + + private string NormalizePublicPath(string publicPath, string basePath, bool basePathIsAbsolute) + { + publicPath = publicPath.EmptyNull(); + + if (basePathIsAbsolute) + { + if (publicPath.IsEmpty() || (!publicPath.StartsWith("~/") && !publicPath.IsWebUrl(true))) + { + var streamMedia = CommonHelper.GetAppSetting("sm:StreamRemoteMedia", true); + if (!streamMedia) + { + throw new ArgumentException(@"When the base path is a fully qualified path and remote media streaming is disabled, + the public path must not be empty, and either be a fully qualified URL or a virtual path (e.g.: ~/Media)", nameof(publicPath)); + } + } + } + + var appVirtualPath = HostingEnvironment.IsHosted + ? HostingEnvironment.ApplicationVirtualPath.TrimEnd('/') + : string.Empty; + + if (publicPath.StartsWith("~/")) + { + // prepend application virtual path + return appVirtualPath + publicPath.Substring(1); + } + + if (publicPath.IsEmpty() && !basePathIsAbsolute) + { + // > /MyAppRoot/Media + return appVirtualPath + basePath; + } + + _isCloudStorage = true; + + return publicPath; + } + + public string Root + { + get { return _root; } + } + + /// + /// Maps a relative path into the storage path. + /// + /// The relative path to be mapped. + /// The relative path combined with the storage path. + protected virtual string MapStorage(string path) + { + var mappedPath = string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, Fix(path)); + return ValidatePath(_storagePath, mappedPath); + } + + /// + /// Maps a relative path into the public path. + /// + /// The relative path to be mapped. + /// The relative path combined with the public path in an URL friendly format ('/' character for directory separator). + protected virtual string MapPublic(string path) + { + return string.IsNullOrEmpty(path) ? _publicPath : HttpUtility.UrlDecode(Path.Combine(_publicPath, path).Replace(Path.DirectorySeparatorChar, '/')); + } + + static string Fix(string path) + { + return string.IsNullOrEmpty(path) + ? "" + : Path.DirectorySeparatorChar != '/' + ? path.Replace('/', Path.DirectorySeparatorChar).TrimStart('/', '\\') + : path.TrimStart('/', '\\'); + } + + public string GetPublicUrl(string path, bool forCloud = false) + { + return MapPublic(path); + } + + public virtual string GetStoragePath(string url) + { + if (url.HasValue()) + { + if (url.StartsWith("~/")) + { + url = VirtualPathUtility.ToAbsolute(url); + } + + if (url.StartsWith(_publicPath)) + { + return HttpUtility.UrlDecode(url.Substring(_publicPath.Length).Replace('/', Path.DirectorySeparatorChar)); + } + } + + return null; + } + + public bool FileExists(string path) + { + return File.Exists(MapStorage(path)); + } + + public bool FolderExists(string path) + { + return Directory.Exists(MapStorage(path)); + } + + public IFile GetFile(string path) + { + var fileInfo = new FileInfo(MapStorage(path)); + return new LocalFile(Fix(path), fileInfo); + } + + public IFolder GetFolder(string path) + { + var directoryInfo = new DirectoryInfo(MapStorage(path)); + return new LocalFolder(Fix(path), directoryInfo); + } + + public IFolder GetFolderForFile(string path) + { + var fileInfo = new FileInfo(MapStorage(path)); + if (!fileInfo.Exists) + { + throw new ArgumentException("File " + path + " does not exist"); + } + + if (!fileInfo.Directory.Exists) + { + throw new ArgumentException("Folder " + path + " does not exist"); + } + + // get relative path of the folder + var folderPath = Path.GetDirectoryName(path); + + return new LocalFolder(Fix(folderPath), fileInfo.Directory); + } + + public long CountFiles(string path, string pattern, Func predicate, bool deep = true) + { + 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, deep ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) + .Select(x => x.Substring(index)); + } + + public IEnumerable ListFiles(string path) + { + var directoryInfo = new DirectoryInfo(MapStorage(path)); + + if (!directoryInfo.Exists) + { + throw new ArgumentException("Directory " + path + " does not exist"); + } + + return directoryInfo + .EnumerateFiles() + .Where(fi => !IsHidden(fi)) + .Select(fi => new LocalFile(Path.Combine(Fix(path), fi.Name), fi)); + } + + public IEnumerable ListFolders(string path) + { + var directoryInfo = new DirectoryInfo(MapStorage(path)); + + if (!directoryInfo.Exists) + { + try + { + directoryInfo.Create(); + } + catch (Exception ex) + { + if (ex.IsFatal()) + { + throw; + } + throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); + } + } + + return directoryInfo + .EnumerateDirectories() + .Where(di => !IsHidden(di)) + .Select(di => new LocalFolder(Path.Combine(Fix(path), di.Name), di)); + } + + private static bool IsHidden(FileSystemInfo di) + { + return (di.Attributes & FileAttributes.Hidden) != 0; + } + + public void CreateFolder(string path) + { + var directoryInfo = new DirectoryInfo(MapStorage(path)); + + if (directoryInfo.Exists) + { + throw new ArgumentException("Directory " + path + " already exists"); + } + + Directory.CreateDirectory(directoryInfo.FullName); + } + + public void DeleteFolder(string path) + { + var directoryInfo = new DirectoryInfo(MapStorage(path)); + + if (!directoryInfo.Exists) + { + throw new ArgumentException("Directory " + path + " does not exist"); + } + + directoryInfo.Delete(true); + } + + public void RenameFolder(string path, string newPath) + { + var sourceDirectory = new DirectoryInfo(MapStorage(path)); + if (!sourceDirectory.Exists) + { + throw new ArgumentException("Directory " + path + "does not exist"); + } + + var targetDirectory = new DirectoryInfo(MapStorage(newPath)); + if (targetDirectory.Exists) + { + throw new ArgumentException("Directory " + newPath + " already exists"); + } + + Directory.Move(sourceDirectory.FullName, targetDirectory.FullName); + } + + public IFile CreateFile(string path) + { + var fileInfo = new FileInfo(MapStorage(path)); + + if (fileInfo.Exists) + { + throw new ArgumentException("File " + path + " already exists"); + } + + // ensure the directory exists + var dirName = Path.GetDirectoryName(fileInfo.FullName); + if (!Directory.Exists(dirName)) + { + Directory.CreateDirectory(dirName); + } + + File.WriteAllBytes(fileInfo.FullName, new byte[0]); + + return new LocalFile(Fix(path), fileInfo); + } + + public async Task CreateFileAsync(string path) + { + var fileInfo = new FileInfo(MapStorage(path)); + + if (fileInfo.Exists) + { + throw new ArgumentException("File " + path + " already exists"); + } + + // ensure the directory exists + var dirName = Path.GetDirectoryName(fileInfo.FullName); + if (!Directory.Exists(dirName)) + { + Directory.CreateDirectory(dirName); + } + + using (FileStream stream = new FileStream(fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true)) + { + await stream.WriteAsync(new byte[0], 0, 0); + } + + return new LocalFile(Fix(path), fileInfo); + } + + public void DeleteFile(string path) + { + var fileInfo = new FileInfo(MapStorage(path)); + + if (fileInfo.Exists) + { + fileInfo.Delete(); + } + } + + public void RenameFile(string path, string newPath) + { + var sourceFileInfo = new FileInfo(MapStorage(path)); + if (!sourceFileInfo.Exists) + { + throw new ArgumentException("File " + path + " does not exist"); + } + + var targetFileInfo = new FileInfo(MapStorage(newPath)); + if (targetFileInfo.Exists) + { + throw new ArgumentException("File " + newPath + " already exists"); + } + + File.Move(sourceFileInfo.FullName, targetFileInfo.FullName); + } + + public void CopyFile(string path, string newPath) + { + var sourceFileInfo = new FileInfo(MapStorage(path)); + if (!sourceFileInfo.Exists) + { + throw new ArgumentException("File " + path + " does not exist"); + } + + var targetFileInfo = new FileInfo(MapStorage(newPath)); + if (targetFileInfo.Exists) + { + throw new ArgumentException("File " + newPath + " already exists"); + } + + File.Copy(sourceFileInfo.FullName, targetFileInfo.FullName); + } + + public void SaveStream(string path, Stream inputStream) + { + // Create the file. + // The CreateFile method will map the still relative path + var file = CreateFile(path); + + using (var outputStream = file.OpenWrite()) + { + var buffer = new byte[8192]; + for (;;) + { + var length = inputStream.Read(buffer, 0, buffer.Length); + if (length <= 0) + break; + outputStream.Write(buffer, 0, length); + } + } + } + + public async Task SaveStreamAsync(string path, Stream inputStream) + { + // Create the file. + // The CreateFile method will map the still relative path + var file = await CreateFileAsync(path); + + using (var outputStream = file.OpenWrite()) + { + var buffer = new byte[8192]; + for (;;) + { + var length = await inputStream.ReadAsync(buffer, 0, buffer.Length); + if (length <= 0) + break; + await outputStream.WriteAsync(buffer, 0, length); + } + } + } + + public string Combine(string path1, string path2) + { + return Path.Combine(path1, path2); + } + + /// + /// Determines if a path lies within the base path boundaries. + /// If not, an exception is thrown. + /// + /// The base path which boundaries are not to be transposed. + /// The path to determine. + /// The mapped path if valid. + /// If the path is invalid. + internal static string ValidatePath(string basePath, string mappedPath) + { + bool valid = false; + string error = null; + + try + { + // Check that we are indeed within the storage directory boundaries + valid = Path.GetFullPath(mappedPath).StartsWith(Path.GetFullPath(basePath), StringComparison.OrdinalIgnoreCase); + } + catch (Exception exception) + { + // Make sure that if invalid for medium trust we give a proper exception + valid = false; + error = exception.Message; + } + + if (!valid) + { + throw new ArgumentException($"{error ?? "Invalid path."} mappedPath: {mappedPath.NaIfEmpty()}"); + } + + return mappedPath; + } + + + private class LocalFile : IFile + { + private readonly string _path; + private readonly FileInfo _fileInfo; + private Size? _dimensions; + + public LocalFile(string path, FileInfo fileInfo) + { + _path = path; + _fileInfo = fileInfo; + } + + 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; } + } + + public DateTime LastUpdated + { + get { return _fileInfo.LastWriteTimeUtc; } + } + + 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; } + } + + public Stream OpenRead() + { + return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true); + } + + public Stream OpenWrite() + { + return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true); + } + + public Stream CreateFile() + { + return new FileStream(_fileInfo.FullName, FileMode.Truncate, FileAccess.ReadWrite, FileShare.Read, bufferSize: 4096, useAsync: true); + } + + public Task CreateFileAsync() + { + return Task.Run(() => CreateFile()); + } + } + + private class LocalFolder : IFolder + { + private readonly string _path; + private readonly DirectoryInfo _directoryInfo; + + public LocalFolder(string path, DirectoryInfo directoryInfo) + { + _path = path; + _directoryInfo = directoryInfo; + } + + public string Path + { + get { return _path; } + } + + public string Name + { + get { return _directoryInfo.Name; } + } + + public DateTime LastUpdated + { + get { return _directoryInfo.LastWriteTimeUtc; } + } + + public long Size + { + get { return GetDirectorySize(_directoryInfo); } + } + + public bool Exists + { + get { return _directoryInfo.Exists; } + } + + public IFolder Parent + { + get + { + if (_directoryInfo.Parent != null) + { + return new LocalFolder(System.IO.Path.GetDirectoryName(_path), _directoryInfo.Parent); + } + throw new ArgumentException("Directory " + _directoryInfo.Name + " does not have a parent directory"); + } + } + + private static long GetDirectorySize(DirectoryInfo directoryInfo) + { + long size = 0; + + FileInfo[] fileInfos = directoryInfo.GetFiles(); + foreach (FileInfo fileInfo in fileInfos) + { + if (!IsHidden(fileInfo)) + { + size += fileInfo.Length; + } + } + DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories(); + foreach (DirectoryInfo dInfo in directoryInfos) + { + if (!IsHidden(dInfo)) + { + size += GetDirectorySize(dInfo); + } + } + + return size; + } + } + + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/LockFile/ILockFile.cs b/src/Libraries/SmartStore.Core/IO/LockFile/ILockFile.cs new file mode 100644 index 0000000000..1856377808 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/LockFile/ILockFile.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Core.IO +{ + /// + /// Represents a Lock File acquired on the file system + /// + /// + /// The instance needs to be disposed in order to release the lock explicitly + /// + public interface ILockFile : IDisposable + { + void Release(); + } +} diff --git a/src/Libraries/SmartStore.Core/IO/LockFile/ILockFileManager.cs b/src/Libraries/SmartStore.Core/IO/LockFile/ILockFileManager.cs new file mode 100644 index 0000000000..9cd529bf1a --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/LockFile/ILockFileManager.cs @@ -0,0 +1,28 @@ +using System; + +namespace SmartStore.Core.IO +{ + /// + /// Abstraction for application-wide lock files creation. + /// + /// + /// All virtual paths passed in or returned are relative to "~/App_Data". + /// + public interface ILockFileManager + { + /// + /// Attempts to acquire an exclusive lock file. + /// + /// The filename of the lock file to create relative to ~/App_Data. + /// A reference to the lock file object if the lock is granted. + /// true if the lock is granted; otherwise, false. + bool TryAcquireLock(string path, out ILockFile lockFile); + + /// + /// Wether a lock file is already existing. + /// + /// The filename of the lock file to test. + /// true if the lock file exists; otherwise, false. + bool IsLocked(string path); + } +} diff --git a/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs b/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs new file mode 100644 index 0000000000..1693635837 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; +using System.IO; +using SmartStore.Utilities.Threading; + +namespace SmartStore.Core.IO +{ + public class LockFile : ILockFile + { + private readonly string _path; + private readonly string _content; + private readonly IVirtualFolder _folder; + private readonly ReaderWriterLockSlim _rwLock; + private bool _released; + + public LockFile(IVirtualFolder folder, string path, string content, ReaderWriterLockSlim rwLock) + { + _folder = folder; + _content = content; + _rwLock = rwLock; + _path = path; + + // create the physical lock file + _folder.CreateTextFile(_path, content); + } + + public void Dispose() + { + Release(); + } + + public void Release() + { + using (_rwLock.GetWriteLock()) + { + 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; + } + + _released = true; + + // check it has not been granted in the meantime + var current = _folder.ReadFile(_path); + if (current == _content) + { + _folder.DeleteFile(_path); + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs b/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs new file mode 100644 index 0000000000..20d00e3441 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Globalization; +using System.Threading; +using System.IO; +using SmartStore.Utilities.Threading; + +namespace SmartStore.Core.IO +{ + public class LockFileManager : ILockFileManager + { + private readonly IApplicationEnvironment _env; + private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); + + static LockFileManager() + { + Expiration = TimeSpan.FromMinutes(10); + } + + public LockFileManager(IApplicationEnvironment env) + { + _env = env; + } + + public static TimeSpan Expiration + { + get; + private set; + } + + public bool TryAcquireLock(string path, out ILockFile lockFile) + { + lockFile = null; + + if (!_rwLock.TryEnterWriteLock(0)) + { + return false; + } + + try + { + if (IsLockedInternal(path)) + { + return false; + } + + lockFile = new LockFile(_env.TenantFolder, path, DateTime.UtcNow.ToString("u"), _rwLock); + return true; + } + catch + { + // an error occured while reading/creating the lock file + return false; + } + finally + { + _rwLock.ExitWriteLock(); + } + } + + public bool IsLocked(string path) + { + using (_rwLock.GetWriteLock()) + { + try + { + return IsLockedInternal(path); + } + catch + { + // an error occured while reading the file + return true; + } + } + } + + private bool IsLockedInternal(string 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); + + 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 + // or the next taker can do it, unless it also fails, again + return creationUtc.ToUniversalTime().Add(Expiration) > DateTime.UtcNow; + } + } + + return false; + } + } +} diff --git a/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs b/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs deleted file mode 100644 index 636c7fbee5..0000000000 --- a/src/Libraries/SmartStore.Core/IO/Media/FileSystemStorageProvider.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Web.Hosting; -using SmartStore.Utilities; - -namespace SmartStore.Core.IO.Media -{ - public class FileSystemStorageProvider : IStorageProvider - { - private readonly string _storagePath; - private readonly string _publicPath; - - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - public FileSystemStorageProvider(FileSystemSettings settings) - { - var mediaPath = CommonHelper.MapPath("~/Media/", false); - _storagePath = Path.Combine(mediaPath, settings.DirectoryName); - - var appPath = ""; - if (HostingEnvironment.IsHosted) { - appPath = HostingEnvironment.ApplicationVirtualPath; - } - if (!appPath.EndsWith("/")) - appPath = appPath + '/'; - if (!appPath.StartsWith("/")) - appPath = '/' + appPath; - - _publicPath = appPath + "Media/" + settings.DirectoryName + "/"; - } - - private string Map(string path) - { - return string.IsNullOrEmpty(path) ? _storagePath : Path.Combine(_storagePath, path); - } - - static string Fix(string path) - { - return string.IsNullOrEmpty(path) - ? "" - : Path.DirectorySeparatorChar != '/' - ? path.Replace('/', Path.DirectorySeparatorChar) - : path; - } - - #region Implementation of IStorageProvider - - public string GetPublicUrl(string path) { - - return _publicPath + path.Replace(Path.DirectorySeparatorChar, '/'); - } - - public IStorageFile GetFile(string path) { - if (!File.Exists(Map(path))) { - throw new ArgumentException("File " + path + " does not exist"); - } - return new FileSystemStorageFile(Fix(path), new FileInfo(Map(path))); - } - - public IEnumerable ListFiles(string path) { - if (!Directory.Exists(Map(path))) { - throw new ArgumentException("Directory " + path + " does not exist"); - } - - return new DirectoryInfo(Map(path)) - .GetFiles() - .Where(fi => !IsHidden(fi)) - .Select(fi => new FileSystemStorageFile(Path.Combine(Fix(path), fi.Name), fi)) - .ToList(); - } - - public IEnumerable ListFolders(string path) { - if (!Directory.Exists(Map(path))) { - try { - Directory.CreateDirectory(Map(path)); - } - catch (Exception ex) { - throw new ArgumentException(string.Format("The folder could not be created at path: {0}. {1}", path, ex)); - } - } - - return new DirectoryInfo(Map(path)) - .GetDirectories() - .Where(di => !IsHidden(di)) - .Select(di => new FileSystemStorageFolder(Path.Combine(Fix(path), di.Name), di)) - .ToList(); - } - - private static bool IsHidden(FileSystemInfo di) { - return (di.Attributes & FileAttributes.Hidden) != 0; - } - - public void CreateFolder(string path) { - if (Directory.Exists(Map(path))) { - throw new ArgumentException("Directory " + path + " already exists"); - } - - Directory.CreateDirectory(Map(path)); - } - - public void DeleteFolder(string path) { - if (!Directory.Exists(Map(path))) { - throw new ArgumentException("Directory " + path + " does not exist"); - } - - Directory.Delete(Map(path), true); - } - - public void RenameFolder(string path, string newPath) { - if (!Directory.Exists(Map(path))) { - throw new ArgumentException("Directory " + path + "does not exist"); - } - - if (Directory.Exists(Map(newPath))) { - throw new ArgumentException("Directory " + newPath + " already exists"); - } - - Directory.Move(Map(path), Map(newPath)); - } - - public IStorageFile CreateFile(string path) { - if (File.Exists(Map(path))) { - throw new ArgumentException("File " + path + " already exists"); - } - - var fileInfo = new FileInfo(Map(path)); - File.WriteAllBytes(Map(path), new byte[0]); - - return new FileSystemStorageFile(Fix(path), fileInfo); - } - - public void DeleteFile(string path) { - if (!File.Exists(Map(path))) { - throw new ArgumentException("File " + path + " does not exist"); - } - - File.Delete(Map(path)); - } - - public void RenameFile(string path, string newPath) { - if (!File.Exists(Map(path))) { - throw new ArgumentException("File " + path + " does not exist"); - } - - if (File.Exists(Map(newPath))) { - throw new ArgumentException("File " + newPath + " already exists"); - } - - File.Move(Map(path), Map(newPath)); - } - - #endregion - - private class FileSystemStorageFile : IStorageFile { - private readonly string _path; - private readonly FileInfo _fileInfo; - - public FileSystemStorageFile(string path, FileInfo fileInfo) { - _path = path; - _fileInfo = fileInfo; - } - - #region Implementation of IStorageFile - - public string GetPath() { - return _path; - } - - public string GetName() { - return _fileInfo.Name; - } - - public long GetSize() { - return _fileInfo.Length; - } - - public DateTime GetLastUpdated() { - return _fileInfo.LastWriteTime; - } - - public string GetFileType() { - return _fileInfo.Extension; - } - - public Stream OpenRead() { - return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.Read); - } - - public Stream OpenWrite() { - return new FileStream(_fileInfo.FullName, FileMode.Open, FileAccess.ReadWrite); - } - - #endregion - } - - private class FileSystemStorageFolder : IStorageFolder { - private readonly string _path; - private readonly DirectoryInfo _directoryInfo; - - public FileSystemStorageFolder(string path, DirectoryInfo directoryInfo) { - _path = path; - _directoryInfo = directoryInfo; - } - - #region Implementation of IStorageFolder - - public string GetPath() { - return _path; - } - - public string GetName() { - return _directoryInfo.Name; - } - - public DateTime GetLastUpdated() { - return _directoryInfo.LastWriteTime; - } - - public long GetSize() { - return GetDirectorySize(_directoryInfo); - } - - public IStorageFolder GetParent() { - if (_directoryInfo.Parent != null) { - return new FileSystemStorageFolder(Path.GetDirectoryName(_path), _directoryInfo.Parent); - } - throw new ArgumentException("Directory " + _directoryInfo.Name + " does not have a parent directory"); - } - - #endregion - - private static long GetDirectorySize(DirectoryInfo directoryInfo) { - long size = 0; - - FileInfo[] fileInfos = directoryInfo.GetFiles(); - foreach (FileInfo fileInfo in fileInfos) { - if (!IsHidden(fileInfo)) { - size += fileInfo.Length; - } - } - DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories(); - foreach (DirectoryInfo dInfo in directoryInfos) { - if (!IsHidden(dInfo)) { - size += GetDirectorySize(dInfo); - } - } - - return size; - } - } - - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/Media/IStorageFile.cs b/src/Libraries/SmartStore.Core/IO/Media/IStorageFile.cs deleted file mode 100644 index 6f154d5217..0000000000 --- a/src/Libraries/SmartStore.Core/IO/Media/IStorageFile.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.IO; - -namespace SmartStore.Core.IO.Media -{ - public interface IStorageFile - { - string GetPath(); - string GetName(); - long GetSize(); - DateTime GetLastUpdated(); - string GetFileType(); - - /// - /// Creates a stream for reading from the file. - /// - Stream OpenRead(); - - /// - /// Creates a stream for writing to the file. - /// - Stream OpenWrite(); - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/Media/IStorageFolder.cs b/src/Libraries/SmartStore.Core/IO/Media/IStorageFolder.cs deleted file mode 100644 index 54108aa3ca..0000000000 --- a/src/Libraries/SmartStore.Core/IO/Media/IStorageFolder.cs +++ /dev/null @@ -1,14 +0,0 @@ - -using System; - -namespace SmartStore.Core.IO.Media -{ - public interface IStorageFolder - { - string GetPath(); - string GetName(); - long GetSize(); - DateTime GetLastUpdated(); - IStorageFolder GetParent(); - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs b/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs deleted file mode 100644 index 38a8958457..0000000000 --- a/src/Libraries/SmartStore.Core/IO/Media/IStorageProvider.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SmartStore.Core.IO.Media -{ - public interface IStorageProvider - { - /// - /// Retrieves the public URL for a given file within the storage provider. - /// - /// The relative path within the storage provider. - /// The public URL. - string GetPublicUrl(string path); - - /// - /// Retrieves a file within the storage provider. - /// - /// The relative path to the file within the storage provider. - /// The file. - /// If the file is not found. - IStorageFile GetFile(string path); - - /// - /// Lists the files within a storage provider's path. - /// - /// The relative path to the folder which files to list. - /// The list of files in the folder. - IEnumerable ListFiles(string path); - - /// - /// Lists the folders within a storage provider's path. - /// - /// The relative path to the folder which folders to list. - /// The list of folders in the folder. - IEnumerable ListFolders(string path); - - /// - /// Creates a folder in the storage provider. - /// - /// The relative path to the folder to be created. - /// If the folder already exists. - void CreateFolder(string path); - - /// - /// Deletes a folder in the storage provider. - /// - /// The relative path to the folder to be deleted. - /// If the folder doesn't exist. - void DeleteFolder(string path); - - /// - /// Renames a folder in the storage provider. - /// - /// The relative path to the folder to be renamed. - /// The relative path to the new folder. - void RenameFolder(string path, string newPath); - - /// - /// Deletes a file in the storage provider. - /// - /// The relative path to the file to be deleted. - /// If the file doesn't exist. - void DeleteFile(string path); - - /// - /// Renames a file in the storage provider. - /// - /// The relative path to the file to be renamed. - /// The relative path to the new file. - void RenameFile(string path, string newPath); - - /// - /// Creates a file in the storage provider. - /// - /// The relative path to the file to be created. - /// If the file already exists. - /// The created file. - IStorageFile CreateFile(string path); - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs index e44c81b0a2..02ecb8dd82 100644 --- a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs +++ b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs @@ -1,23 +1,765 @@ using System; -using System.Collections.Concurrent; +using System.IO; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Security; -using System.Text; -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; } /// @@ -31,39 +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 102649e1b2..2d78ea0447 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs @@ -2,207 +2,160 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Web; using System.Web.Hosting; +using System.Web.Caching; +using SmartStore.Utilities; +using System.Web; using SmartStore.Core.Logging; -namespace SmartStore.Core.IO.VirtualPath +namespace SmartStore.Core.IO { + public class DefaultVirtualPathProvider : IVirtualPathProvider + { + private readonly ILogger _logger; + + public DefaultVirtualPathProvider(ILogger logger) + { + _logger = logger; + } + + public virtual string Combine(params string[] paths) + { + return Path.Combine(paths).Replace(Path.DirectorySeparatorChar, '/'); + } + + public virtual bool DirectoryExists(string virtualPath) + { + return HostingEnvironment.VirtualPathProvider.DirectoryExists(virtualPath); + } + + public virtual bool FileExists(string virtualPath) + { + return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath); + } + + public virtual CacheDependency GetCacheDependency(string virtualPath, IEnumerable dependencies, DateTime utcStart) + { + return HostingEnvironment.VirtualPathProvider.GetCacheDependency(virtualPath, dependencies, utcStart); + } - public class DefaultVirtualPathProvider : IVirtualPathProvider - { - private readonly ILogger _logger; - - public DefaultVirtualPathProvider(ILogger logger) - { - this._logger = logger; - } - - public virtual string GetDirectoryName(string virtualPath) - { - return Path.GetDirectoryName(virtualPath).Replace(Path.DirectorySeparatorChar, '/'); - } - - public virtual IEnumerable ListFiles(string path) - { - return HostingEnvironment - .VirtualPathProvider - .GetDirectory(path) - .Files - .OfType() - .Select(f => VirtualPathUtility.ToAppRelative(f.VirtualPath)); - } - - public virtual IEnumerable ListDirectories(string path) - { - return HostingEnvironment - .VirtualPathProvider - .GetDirectory(path) - .Directories - .OfType() - .Select(d => VirtualPathUtility.ToAppRelative(d.VirtualPath)); - } - - public virtual string Combine(params string[] paths) - { - return Path.Combine(paths).Replace(Path.DirectorySeparatorChar, '/'); - } - - public virtual string ToAppRelative(string virtualPath) - { - if (IsMalformedVirtualPath(virtualPath)) - return null; - - try - { - string result = VirtualPathUtility.ToAppRelative(virtualPath); - - // In some cases, ToAppRelative doesn't normalize the path. In those cases, - // the path is invalid. - // Example: - // ApplicationPath: /Foo - // VirtualPath : ~/Bar/../Blah/Blah2 - // Result : /Blah/Blah2 <= that is not an app relative path! - if (!result.StartsWith("~/")) - { - _logger.Information("Path '{0}' cannot be made app relative: Path returned ('{1}') is not app relative.".FormatCurrent(virtualPath, result)); - return null; - } - return result; - } - catch (Exception e) - { - // The initial path might have been invalid (e.g. path indicates a path outside the application root) - _logger.Information("Path '{0}' cannot be made app relative".FormatCurrent(virtualPath), e); - return null; - } - } - - /// - /// We want to reject path that contains ".." going outside of the application root. - /// ToAppRelative does that already, but we want to do the same while avoiding exceptions. - /// - /// Note: This method doesn't detect all cases of malformed paths, it merely checks - /// for *some* cases of malformed paths, so this is not a replacement for full virtual path - /// verification through VirtualPathUtilty methods. - /// In other words, !IsMalformed does *not* imply "IsWellformed". - /// - public bool IsMalformedVirtualPath(string virtualPath) - { - if (string.IsNullOrEmpty(virtualPath)) - return true; - - if (virtualPath.IndexOf("..", StringComparison.OrdinalIgnoreCase) >= 0) - { - virtualPath = virtualPath.Replace(Path.DirectorySeparatorChar, '/'); - string rootPrefix = virtualPath.StartsWith("~/") ? "~/" : virtualPath.StartsWith("/") ? "/" : ""; - if (!string.IsNullOrEmpty(rootPrefix)) - { - string[] terms = virtualPath.Substring(rootPrefix.Length).Split('/'); - int depth = 0; - foreach (var term in terms) - { - if (term == "..") - { - if (depth == 0) - { - _logger.Information("Path '{0}' cannot be made app relative: Too many '..'".FormatCurrent(virtualPath)); - return true; - } - depth--; - } - else - { - depth++; - } - } - } - } - - return false; - } - - public virtual Stream OpenFile(string virtualPath) - { - return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open(); - } - - public virtual StreamWriter CreateText(string virtualPath) - { - return File.CreateText(MapPath(virtualPath)); - } - - public virtual Stream CreateFile(string virtualPath) - { - return File.Create(MapPath(virtualPath)); - } - - public virtual DateTime GetFileLastWriteTimeUtc(string virtualPath) - { - return File.GetLastWriteTimeUtc(MapPath(virtualPath)); - } - - public string GetFileHash(string virtualPath) - { - return GetFileHash(virtualPath, new[] { virtualPath }); - } - - public string GetFileHash(string virtualPath, IEnumerable dependencies) - { - return HostingEnvironment.VirtualPathProvider.GetFileHash(virtualPath, dependencies); - } - - public virtual void DeleteFile(string virtualPath) - { - File.Delete(MapPath(virtualPath)); - } - - public virtual string MapPath(string virtualPath) - { - return HostingEnvironment.MapPath(virtualPath); - } + public virtual string GetCacheKey(string virtualPath) + { + return HostingEnvironment.VirtualPathProvider.GetCacheKey(virtualPath); + } + + public virtual IEnumerable ListDirectories(string virtualPath) + { + return HostingEnvironment + .VirtualPathProvider + .GetDirectory(virtualPath) + .Directories + .OfType() + .Select(d => VirtualPathUtility.ToAppRelative(d.VirtualPath)); + } + + public virtual IEnumerable ListFiles(string virtualPath) + { + return HostingEnvironment + .VirtualPathProvider + .GetDirectory(virtualPath) + .Files + .OfType() + .Select(f => VirtualPathUtility.ToAppRelative(f.VirtualPath)); + } + + public virtual string GetFileHash(string virtualPath, IEnumerable dependencies) + { + return HostingEnvironment.VirtualPathProvider.GetFileHash(virtualPath, dependencies); + } + + public virtual string MapPath(string virtualPath) + { + return CommonHelper.MapPath(virtualPath); + } public virtual string Normalize(string virtualPath) { return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).VirtualPath; } - public virtual bool FileExists(string virtualPath) - { - return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath); - } - - public virtual bool TryFileExists(string virtualPath) - { - if (IsMalformedVirtualPath(virtualPath)) - return false; - - try - { - return FileExists(virtualPath); - } - catch (Exception e) - { - _logger.Information("File '{0}' can not be checked for existence. Assuming doesn't exist.".FormatCurrent(virtualPath), e); - return false; - } - } - - public virtual bool DirectoryExists(string virtualPath) - { - return HostingEnvironment.VirtualPathProvider.DirectoryExists(virtualPath); - } - - public virtual void CreateDirectory(string virtualPath) - { - Directory.CreateDirectory(MapPath(virtualPath)); - } - - public virtual void DeleteDirectory(string virtualPath) - { - Directory.Delete(MapPath(virtualPath)); - } + public virtual Stream OpenFile(string virtualPath) + { + return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open(); + } - } + public virtual string ToAppRelative(string virtualPath) + { + if (IsMalformedVirtualPath(virtualPath)) + return null; + + try + { + string result = VirtualPathUtility.ToAppRelative(virtualPath); + + // In some cases, ToAppRelative doesn't normalize the path. In those cases, + // the path is invalid. + // Example: + // ApplicationPath: /Foo + // VirtualPath : ~/Bar/../Blah/Blah2 + // Result : /Blah/Blah2 <= that is not an app relative path! + if (!result.StartsWith("~/")) + { + _logger.Info("Path '{0}' cannot be made app relative: Path returned ('{1}') is not app relative.".FormatCurrent(virtualPath, result)); + return null; + } + return result; + } + catch (Exception e) + { + // The initial path might have been invalid (e.g. path indicates a path outside the application root) + _logger.Info(e, "Path '{0}' cannot be made app relative".FormatCurrent(virtualPath)); + return null; + } + } + /// + /// We want to reject path that contains ".." going outside of the application root. + /// ToAppRelative does that already, but we want to do the same while avoiding exceptions. + /// + /// Note: This method doesn't detect all cases of malformed paths, it merely checks + /// for *some* cases of malformed paths, so this is not a replacement for full virtual path + /// verification through VirtualPathUtilty methods. + /// In other words, !IsMalformed does *not* imply "IsWellformed". + /// + public bool IsMalformedVirtualPath(string virtualPath) + { + if (string.IsNullOrEmpty(virtualPath)) + return true; + + if (virtualPath.IndexOf("..", StringComparison.OrdinalIgnoreCase) >= 0) + { + virtualPath = virtualPath.Replace(Path.DirectorySeparatorChar, '/'); + string rootPrefix = virtualPath.StartsWith("~/") ? "~/" : virtualPath.StartsWith("/") ? "/" : ""; + if (!string.IsNullOrEmpty(rootPrefix)) + { + string[] terms = virtualPath.Substring(rootPrefix.Length).Split('/'); + int depth = 0; + foreach (var term in terms) + { + if (term == "..") + { + if (depth == 0) + { + _logger.Info("Path '{0}' cannot be made app relative: Too many '..'".FormatCurrent(virtualPath)); + return true; + } + depth--; + } + else + { + depth++; + } + } + } + } + + return false; + } + } } diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualFolder.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualFolder.cs new file mode 100644 index 0000000000..e4177560ad --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualFolder.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Core.IO +{ + /// + /// Abstraction over the virtual files/directories of a web site. + /// + public interface IVirtualFolder + { + IVirtualPathProvider VirtualPathProvider { get; } + + /// + /// Virtual root path, e.g. ~/App_Data or ~/Themes + /// + string RootPath { get; } + + string GetVirtualPath(string relativePath); + + string MapPath(string relativePath); + string Combine(params string[] paths); + + bool DirectoryExists(string relativePath); + bool FileExists(string relativePath); + + IEnumerable ListDirectories(string relativePath); + IEnumerable ListFiles(string relativePath, bool deep = false); + string GetDirectoryName(string relativePath); + + Stream OpenFile(string relativePath); + void CreateTextFile(string relativePath, string content); + Task CreateTextFileAsync(string relativePath, string content); + Stream CreateFile(string relativePath); + void CreateDirectory(string relativePath); + + void DeleteFile(string relativePath); + void DeleteDirectory(string relativePath); + + string ReadFile(string relativePath); + Task ReadFileAsync(string relativePath); + void CopyFile(string relativePath, Stream destination); + + DateTime GetFileLastWriteTimeUtc(string relativePath); + } + + public static class IVirtualFolderExtensions + { + public static bool TryDeleteDirectory(this IVirtualFolder folder, string relativePath) + { + try + { + folder.DeleteDirectory(relativePath); + return true; + } + catch + { + return false; + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualPathProvider.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualPathProvider.cs index 1991bf1a68..8fd1c36c90 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualPathProvider.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualPathProvider.cs @@ -2,34 +2,40 @@ using System.Collections.Generic; using System.Linq; using System.IO; +using System.Web.Caching; -namespace SmartStore.Core.IO.VirtualPath -{ - +namespace SmartStore.Core.IO +{ public interface IVirtualPathProvider { - string Combine(params string[] paths); - string ToAppRelative(string virtualPath); - string MapPath(string virtualPath); + string MapPath(string virtualPath); + string Combine(params string[] paths); string Normalize(string virtualPath); + string ToAppRelative(string virtualPath); - bool FileExists(string virtualPath); - bool TryFileExists(string virtualPath); - Stream OpenFile(string virtualPath); - StreamWriter CreateText(string virtualPath); - Stream CreateFile(string virtualPath); - DateTime GetFileLastWriteTimeUtc(string virtualPath); - string GetFileHash(string virtualPath); - string GetFileHash(string virtualPath, IEnumerable dependencies); - void DeleteFile(string virtualPath); + bool DirectoryExists(string virtualPath); + bool FileExists(string virtualPath); - bool DirectoryExists(string virtualPath); - void CreateDirectory(string virtualPath); - string GetDirectoryName(string virtualPath); - void DeleteDirectory(string virtualPath); + CacheDependency GetCacheDependency(string virtualPath, IEnumerable dependencies, DateTime utcStart); + string GetCacheKey(string virtualPath); + string GetFileHash(string virtualPath, IEnumerable dependencies); - IEnumerable ListFiles(string path); - IEnumerable ListDirectories(string path); + IEnumerable ListDirectories(string virtualPath); + IEnumerable ListFiles(string virtualPath); + + Stream OpenFile(string virtualPath); } + public static class IVirtualPathProviderExtensions + { + public static string GetFileHash(this IVirtualPathProvider vpp, string virtualPath) + { + return vpp.GetFileHash(virtualPath, new[] { virtualPath }); + } + + public static CacheDependency GetCacheDependency(this IVirtualPathProvider vpp, string virtualPath, DateTime utcStart) + { + return vpp.GetCacheDependency(virtualPath, new[] { virtualPath }, utcStart); + } + } } diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs new file mode 100644 index 0000000000..6355143bc6 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using SmartStore.Core.Logging; + +namespace SmartStore.Core.IO +{ + public class VirtualFolder : IVirtualFolder + { + private readonly IVirtualPathProvider _vpp; + private readonly ILogger _logger; + private readonly string _root; + + public VirtualFolder(string root, IVirtualPathProvider vpp) + : this(root, vpp, NullLogger.Instance) + { + } + + public VirtualFolder(string root, IVirtualPathProvider vpp, ILogger logger) + { + Guard.NotEmpty(root, nameof(root)); + Guard.NotNull(vpp, nameof(vpp)); + Guard.NotNull(logger, nameof(logger)); + + if (!root.StartsWith("~/")) + { + throw new ArgumentException("Root path must be a valid application relative path starting with '~/'", nameof(root)); + } + + _root = root.Replace(Path.DirectorySeparatorChar, '/').EnsureEndsWith("/"); + _vpp = vpp; + _logger = logger; + } + + public IVirtualPathProvider VirtualPathProvider + { + get { return _vpp; } + } + + public string RootPath + { + get { return _root; } + } + + public virtual string MapPath(string relativePath) + { + return _vpp.MapPath(GetVirtualPath(relativePath)); + } + + public virtual string Combine(params string[] paths) + { + return _vpp.Combine(paths); + } + + public virtual bool DirectoryExists(string relativePath) + { + return _vpp.DirectoryExists(GetVirtualPath(relativePath)); + } + + public virtual bool FileExists(string relativePath) + { + return _vpp.FileExists(GetVirtualPath(relativePath)); + } + + public virtual IEnumerable ListDirectories(string relativePath) + { + var path = GetVirtualPath(relativePath); + + if (!_vpp.DirectoryExists(path)) + { + return Enumerable.Empty(); + } + + return _vpp.ListDirectories(path).Select(x => x.Substring(_root.Length)); + } + + public virtual IEnumerable ListFiles(string relativePath, bool deep = false) + { + var path = GetVirtualPath(relativePath); + + if (!_vpp.DirectoryExists(path)) + { + return Enumerable.Empty(); + } + + var files = _vpp.ListFiles(path).Select(x => x.Substring(_root.Length)); + + if (deep) + { + return files.Concat(ListDirectories(path).SelectMany(d => ListFiles(d, true))); + } + + return files; + } + + public virtual string GetDirectoryName(string relativePath) + { + return Path.GetDirectoryName(relativePath).Replace(Path.DirectorySeparatorChar, '/'); + } + + public virtual Stream OpenFile(string relativePath) + { + return _vpp.OpenFile(GetVirtualPath(relativePath)); + } + + public void CreateTextFile(string relativePath, string content) + { + Guard.NotEmpty(relativePath, nameof(relativePath)); + + using (var stream = CreateFile(relativePath)) + { + using (var tw = new StreamWriter(stream)) + { + tw.Write(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); + var folderPath = Path.GetDirectoryName(filePath); + + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + + return File.Create(filePath); + } + + public virtual DateTime GetFileLastWriteTimeUtc(string relativePath) + { + return File.GetLastWriteTimeUtc(MapPath(relativePath)); + } + + public virtual void DeleteFile(string relativePath) + { + File.Delete(MapPath(relativePath)); + } + + public virtual void CreateDirectory(string relativePath) + { + Directory.CreateDirectory(MapPath(relativePath)); + } + + public virtual void DeleteDirectory(string relativePath) + { + Directory.Delete(MapPath(relativePath), true); + } + + public virtual string ReadFile(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 reader.ReadToEnd(); + } + } + } + + 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)); + Guard.NotNull(destination, nameof(destination)); + + var path = GetVirtualPath(relativePath); + + if (!_vpp.FileExists(path)) + { + throw Error.InvalidOperation("File '{0}' does not exist".FormatInvariant(path)); + } + + using (var stream = _vpp.OpenFile(path)) + { + stream.CopyTo(destination); + } + } + + public string GetVirtualPath(string relativePath) + { + Guard.NotNull(relativePath, nameof(relativePath)); + + if (relativePath.StartsWith("~/")) + { + return relativePath; + } + + return _root + relativePath.EmptyNull().Replace(Path.DirectorySeparatorChar, '/').TrimStart('/'); + } + } +} diff --git a/src/Libraries/SmartStore.Core/IO/WebSite/IWebSiteFolder.cs b/src/Libraries/SmartStore.Core/IO/WebSite/IWebSiteFolder.cs deleted file mode 100644 index 410ec034c4..0000000000 --- a/src/Libraries/SmartStore.Core/IO/WebSite/IWebSiteFolder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web.Hosting; - -namespace SmartStore.Core.IO.WebSite -{ - - /// - /// Abstraction over the virtual files/directories of a web site. - /// - public interface IWebSiteFolder - { - IEnumerable ListDirectories(string virtualPath); - IEnumerable ListFiles(string virtualPath, bool recursive); - - bool FileExists(string virtualPath); - string ReadFile(string virtualPath); - void CopyFileTo(string virtualPath, Stream destination); - } - -} diff --git a/src/Libraries/SmartStore.Core/IO/WebSite/WebSiteFolder.cs b/src/Libraries/SmartStore.Core/IO/WebSite/WebSiteFolder.cs deleted file mode 100644 index ff81c85b35..0000000000 --- a/src/Libraries/SmartStore.Core/IO/WebSite/WebSiteFolder.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Hosting; -using System.IO; -using SmartStore.Core.IO.VirtualPath; - -namespace SmartStore.Core.IO.WebSite -{ - - public class WebSiteFolder : IWebSiteFolder - { - private readonly IVirtualPathProvider _virtualPathProvider; - - public WebSiteFolder(IVirtualPathProvider virtualPathProvider) - { - _virtualPathProvider = virtualPathProvider; - } - - public IEnumerable ListDirectories(string virtualPath) - { - if (!_virtualPathProvider.DirectoryExists(virtualPath)) - { - return Enumerable.Empty(); - } - - return _virtualPathProvider.ListDirectories(virtualPath); - } - - private IEnumerable ListFiles(IEnumerable directories) - { - return directories.SelectMany(d => ListFiles(d, true)); - } - - public IEnumerable ListFiles(string virtualPath, bool recursive) - { - if (!recursive) - { - return _virtualPathProvider.ListFiles(virtualPath); - } - return _virtualPathProvider.ListFiles(virtualPath).Concat(ListFiles(ListDirectories(virtualPath))); - } - - public bool FileExists(string virtualPath) - { - return _virtualPathProvider.FileExists(virtualPath); - } - - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")] - public string ReadFile(string virtualPath) - { - if (!_virtualPathProvider.FileExists(virtualPath)) - { - return null; - } - - using (var stream = _virtualPathProvider.OpenFile(Normalize(virtualPath))) - { - using (var reader = new StreamReader(stream)) - { - return reader.ReadToEnd(); - } - } - } - - public void CopyFileTo(string virtualPath, Stream destination) - { - using (var stream = _virtualPathProvider.OpenFile(Normalize(virtualPath))) - { - stream.CopyTo(destination); - } - } - - private string Normalize(string virtualPath) - { - return _virtualPathProvider.Normalize(virtualPath); - } - } - -} diff --git a/src/Libraries/SmartStore.Core/IPageable.cs b/src/Libraries/SmartStore.Core/IPageable.cs index 60d6b3f63f..ce270c8e4e 100644 --- a/src/Libraries/SmartStore.Core/IPageable.cs +++ b/src/Libraries/SmartStore.Core/IPageable.cs @@ -2,16 +2,16 @@ using System.Runtime.Serialization; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace SmartStore.Core -{ - +{ /// /// A collection of objects that has been split into pages. /// public interface IPageable : IEnumerable { - /// /// The 0-based current page index /// @@ -82,7 +82,38 @@ public interface IPageable : IEnumerable /// public interface IPagedList : IPageable, IList { - // codehint: sm-delete (members of IPageable now) + /// + /// 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 + /// + /// The query + /// 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/IStoreContext.cs b/src/Libraries/SmartStore.Core/IStoreContext.cs index 2ab8bfede9..1802ddbf23 100644 --- a/src/Libraries/SmartStore.Core/IStoreContext.cs +++ b/src/Libraries/SmartStore.Core/IStoreContext.cs @@ -34,7 +34,8 @@ public interface IStoreContext /// /// Gets or sets the current store /// - Store CurrentStore { get; } + /// Setter is for virtualization and testing purposes + Store CurrentStore { get; set; } /// /// IsSingleStoreMode ? 0 : CurrentStore.Id diff --git a/src/Libraries/SmartStore.Core/IWebHelper.cs b/src/Libraries/SmartStore.Core/IWebHelper.cs index 03b4f128c8..2d089bd64d 100644 --- a/src/Libraries/SmartStore.Core/IWebHelper.cs +++ b/src/Libraries/SmartStore.Core/IWebHelper.cs @@ -13,11 +13,21 @@ public partial interface IWebHelper /// URL referrer string GetUrlReferrer(); - /// - /// Get context IP address - /// - /// URL referrer - string GetCurrentIpAddress(); + /// + /// Gets a unique client identifier + /// + /// A unique identifier + /// + /// The client identifier is a hashed combination of client ip address and user agent. + /// This method returns null if IP or user agent (or both) cannot be determined. + /// + string GetClientIdent(); + + /// + /// Get context IP address + /// + /// URL referrer + string GetCurrentIpAddress(); /// /// Gets this page name diff --git a/src/Libraries/SmartStore.Core/IWorkContext.cs b/src/Libraries/SmartStore.Core/IWorkContext.cs index d3a54cf702..0c78e00452 100644 --- a/src/Libraries/SmartStore.Core/IWorkContext.cs +++ b/src/Libraries/SmartStore.Core/IWorkContext.cs @@ -59,10 +59,5 @@ public interface IWorkContext /// Gets or sets a value indicating whether we're in admin area /// bool IsAdmin { get; set; } - - ///// - ///// Gets a value indicating whether we're in the public shop - ///// - //bool IsPublic { get; } } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs index 79a7a41f22..4ed72318a1 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Core.Infrastructure @@ -17,27 +18,26 @@ namespace SmartStore.Core.Infrastructure /// public class AppDomainTypeFinder : ITypeFinder { - #region Private Fields - private static object s_lock = new object(); - private string _assemblySkipLoadingPattern = @"^System|^mscorlib|^Microsoft|^CppCodeProvider|^VJSharpCodeProvider|^WebDev|^Nuget|^Castle|^Iesi|^log4net|^Autofac|^AutoMapper|^EntityFramework|^EPPlus|^nunit|^TestDriven|^MbUnit|^Rhino|^QuickGraph|^TestFu|^Telerik|^Antlr3|^Recaptcha|^FluentValidation|^ImageResizer|^itextsharp|^MiniProfiler|^Newtonsoft|^Pandora|^WebGrease|^Noesis|^DotNetOpenAuth|^Facebook|^LinqToTwitter|^PerceptiveMCAPI|^CookComputing|^GCheckout|^Mono\.Math|^Org\.Mentalis|^App_Web|^BundleTransformer|^ClearScript|^JavaScriptEngineSwitcher|^MsieJavaScriptEngine|^Glimpse|^Ionic|^App_GlobalResources|^AjaxMin|^MaxMind|^NReco|^OffAmazonPayments|^UAParser"; - private string _assemblyRestrictToLoadingPattern = ".*"; - private readonly IDictionary _assemblyMatchTable = new Dictionary(); - - private Regex _assemblySkipLoadingRegex; - private Regex _assemblyRestrictToLoadingRegex; + private readonly Regex _systemAssembliesPattern = new Regex(@"^System|^mscorlib|^Microsoft", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); private bool _ignoreReflectionErrors = true; private bool _loadAppDomainAssemblies = true; private IList _customAssemblyNames = new List(); - #endregion + public AppDomainTypeFinder() + { + Logger = NullLogger.Instance; + } - #region Properties + public ILogger Logger + { + get; + set; + } - /// The app domain to look for types in. - public virtual AppDomain App + public virtual AppDomain App { get { return AppDomain.CurrentDomain; } } @@ -52,7 +52,7 @@ public bool LoadAppDomainAssemblies } /// - /// Gets or sets assemblies loaded at startup in addition to those loaded in the AppDomain. + /// Gets or sets assemblies to load at startup in addition to those loaded in the AppDomain. /// public IList CustomAssemblyNames { @@ -60,45 +60,10 @@ public IList CustomAssemblyNames set { _customAssemblyNames = value; } } - /// - /// Gets the pattern for dlls that we know don't need to be investigated. - /// - public string AssemblySkipLoadingPattern - { - get { return _assemblySkipLoadingPattern; } - set - { - _assemblySkipLoadingPattern = value; - _assemblySkipLoadingRegex = null; - _assemblyMatchTable.Clear(); - } - } - - /// - /// Gets or sets the pattern for dll that will be investigated. For ease of use this defaults to match all but to increase performance you might want to configure a pattern that includes assemblies and your own. - /// - /// - /// If you change this so that SmartStore assemblies aren't investigated (e.g. by not including something like "^SmartStore|..." you may break core functionality. - /// - public string AssemblyRestrictToLoadingPattern - { - get { return _assemblyRestrictToLoadingPattern; } - set - { - _assemblyRestrictToLoadingPattern = value; - _assemblyRestrictToLoadingRegex = null; - _assemblyMatchTable.Clear(); - } - } - - #endregion - - #region ITypeFinder - public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses = true) { var result = new List(); - + try { foreach (var a in assemblies) @@ -106,7 +71,7 @@ public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable - /// Gets the assemblies related to the current implementation. - /// - /// Indicates whether uninstalled plugin's assemblies should be filtered out - /// A list of assemblies that should be loaded by the SmartStore factory. - public virtual IList GetAssemblies(bool ignoreInactivePlugins = false) - { - var addedAssemblyNames = new HashSet(); - var assemblies = new List(); + public virtual IEnumerable GetAssemblies(bool ignoreInactivePlugins = false) + { + var assemblies = new HashSet(); - if (LoadAppDomainAssemblies) + var addedAssemblyNames = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (_loadAppDomainAssemblies) { - AddAssembliesInAppDomain(addedAssemblyNames, assemblies); + AddAppDomainAssemblies(addedAssemblyNames, assemblies); } - AddCustomAssemblies(addedAssemblyNames, assemblies); + + AddCustomAssemblies(addedAssemblyNames, assemblies); if (ignoreInactivePlugins) { - return assemblies.Where(x => PluginManager.IsActivePluginAssembly(x)).ToList(); + return assemblies.Where(x => PluginManager.IsActivePluginAssembly(x)).AsReadOnly(); } - else - { - return assemblies; - } - } - #endregion + return assemblies.AsReadOnly(); + } - /// - /// Iterates all assemblies in the AppDomain and if it's name matches the configured patterns add it to our list. + /// + /// Iterates all assemblies in the AppDomain and if its name matches the configured patterns adds it to our list. /// - /// - /// - private void AddAssembliesInAppDomain(HashSet addedAssemblyNames, List assemblies) + /// + /// + private void AddAppDomainAssemblies(HashSet addedAssemblyNames, HashSet assemblies) { - var curDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in curDomainAssemblies) + var domainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var assembly in domainAssemblies) { - if (Matches(assembly.FullName)) + if (Matches(assembly)) { if (!addedAssemblyNames.Contains(assembly.FullName)) { @@ -205,120 +167,58 @@ private void AddAssembliesInAppDomain(HashSet addedAssemblyNames, List - /// Adds specificly configured assemblies. + /// Explicitly adds custom assemblies. /// - protected virtual void AddCustomAssemblies(HashSet addedAssemblyNames, List assemblies) + protected virtual void AddCustomAssemblies(HashSet addedAssemblyNames, HashSet assemblies) { foreach (string assemblyName in CustomAssemblyNames) { - Assembly assembly = Assembly.Load(assemblyName); - if (!addedAssemblyNames.Contains(assembly.FullName)) + if (!addedAssemblyNames.Contains(assemblyName)) { - assemblies.Add(assembly); - addedAssemblyNames.Add(assembly.FullName); + try + { + var assembly = Assembly.Load(assemblyName); + assemblies.Add(assembly); + Logger.DebugFormat("Loaded custom assembly '{0}'", assemblyName); + } + finally + { + addedAssemblyNames.Add(assemblyName); + } } } } - /// - /// Check if a dll is one of the shipped dlls that we know don't need to be investigated. + /// + /// Checks whether an assembly is one of the shipped system or 3rd party assemblies that don't need to be investigated. /// - /// The name of the assembly to check. - /// True if the assembly should be loaded into SmartStore. - public virtual bool Matches(string assemblyFullName) - { - if (_assemblyMatchTable.ContainsKey(assemblyFullName)) + /// The assembly to check. + /// True if the assembly should be loaded into SmartStore. + public virtual bool Matches(Assembly assembly) + { + var name = assembly.FullName; + + // Check if passed assembly is a system assembly + if (_systemAssembliesPattern.IsMatch(name)) { - return _assemblyMatchTable[assemblyFullName]; + return false; } - if (_assemblySkipLoadingRegex == null) - { - lock (s_lock) - { - if (_assemblySkipLoadingRegex == null) - { - _assemblySkipLoadingRegex = new Regex(_assemblySkipLoadingPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline); - } - } - } - - if (_assemblySkipLoadingRegex.IsMatch(assemblyFullName)) + // Check if the passed assembly starts with "SmartStore." + if (name.StartsWith("SmartStore.", StringComparison.OrdinalIgnoreCase)) { - _assemblyMatchTable[assemblyFullName] = false; - return false; + return true; } - if (_assemblyRestrictToLoadingRegex == null) + // Check if any of the referenced assemblies of the passed assembly start with "SmartStore." + // In this case it's obviously a SmartStore plugin. + if (assembly.GetReferencedAssemblies().Any(x => x.FullName.StartsWith("SmartStore.", StringComparison.OrdinalIgnoreCase))) { - lock (s_lock) - { - if (_assemblyRestrictToLoadingRegex == null) - { - _assemblyRestrictToLoadingRegex = new Regex(_assemblyRestrictToLoadingPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline); - } - } + return true; } - var matches = _assemblyRestrictToLoadingRegex.IsMatch(assemblyFullName); - _assemblyMatchTable[assemblyFullName] = matches; - - return matches; - - //return !Matches(assemblyFullName, AssemblySkipLoadingPattern) && Matches(assemblyFullName, AssemblyRestrictToLoadingPattern); - } - - ///// - ///// Check if a dll is one of the shipped dlls that we know don't need to be investigated. - ///// - ///// The assembly name to match. - ///// The regular expression pattern to match against the assembly name. - ///// True if the pattern matches the assembly name. - //protected virtual bool Matches(string assemblyFullName, string pattern) - //{ - // return Regex.IsMatch(assemblyFullName, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline); - //} - - /// - /// Makes sure matching assemblies in the supplied folder are loaded in the app domain. - /// - /// The physical path to a directory containing dlls to load in the app domain. - protected virtual void LoadMatchingAssemblies(string directoryPath) - { - var loadedAssemblyNames = new List(); - foreach (Assembly a in GetAssemblies()) - { - loadedAssemblyNames.Add(a.FullName); - } - - if (!Directory.Exists(directoryPath)) - { - return; - } - - foreach (string dllPath in Directory.GetFiles(directoryPath, "*.dll")) - { - try - { - var an = AssemblyName.GetAssemblyName(dllPath); - if (Matches(an.FullName) && !loadedAssemblyNames.Contains(an.FullName)) - { - App.Load(an); - } - - //old loading stuff - //Assembly a = Assembly.ReflectionOnlyLoadFrom(dllPath); - //if (Matches(a.FullName) && !loadedAssemblyNames.Contains(a.FullName)) - //{ - // App.Load(a.FullName); - //} - } - catch (BadImageFormatException ex) - { - Trace.TraceError(ex.ToString()); - } - } - } + return false; + } protected virtual bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric) { @@ -333,8 +233,10 @@ protected virtual bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric) var isMatch = genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition()); return isMatch; } + return false; - }catch + } + catch { return false; } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ApplicationEnvironment.cs b/src/Libraries/SmartStore.Core/Infrastructure/ApplicationEnvironment.cs new file mode 100644 index 0000000000..f2582a6096 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/ApplicationEnvironment.cs @@ -0,0 +1,47 @@ +using System; +using SmartStore.Core.Data; +using SmartStore.Core.IO; +using SmartStore.Core.Logging; +using SmartStore.Utilities; + +namespace SmartStore.Core +{ + public class ApplicationEnvironment : IApplicationEnvironment + { + public ApplicationEnvironment(IVirtualPathProvider vpp, ILogger logger) + { + WebRootFolder = new VirtualFolder("~/", vpp, logger); + AppDataFolder = new VirtualFolder("~/App_Data/", vpp, logger); + ThemesFolder = new VirtualFolder(CommonHelper.GetAppSetting("sm:ThemesBasePath", "~/Themes/"), vpp, logger); + PluginsFolder = new VirtualFolder("~/Plugins/", vpp, logger); + + if (DataSettings.Current.IsValid()) + { + TenantFolder = new VirtualFolder(DataSettings.Current.TenantPath, vpp, logger); + } + } + + public virtual string MachineName + { + get + { + return Environment.MachineName; + } + } + + public virtual string EnvironmentIdentifier + { + get + { + // use the current host and the process id as two servers could run on the same machine + return Environment.MachineName + "-" + System.Diagnostics.Process.GetCurrentProcess().Id; + } + } + + public virtual IVirtualFolder WebRootFolder { get; private set; } + public virtual IVirtualFolder AppDataFolder { get; private set; } + public virtual IVirtualFolder ThemesFolder { get; private set; } + public virtual IVirtualFolder PluginsFolder { get; private set; } + public virtual IVirtualFolder TenantFolder { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs index a53cc21140..153405382b 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs @@ -161,7 +161,7 @@ protected virtual string[] GetSignaturePropertyNamesCore() /// that no duplicates can occur within the global descriptor collection. protected void RegisterSignatureProperty(string propertyName) { - Guard.ArgumentNotEmpty(() => propertyName); + Guard.NotEmpty(propertyName, nameof(propertyName)); _extraSignatureProperties.Add(propertyName); } @@ -175,7 +175,6 @@ protected void RegisterSignatureProperty(string propertyName) [Serializable] public abstract class ComparableObject : ComparableObject, IEquatable { - /// /// Adds an extra property to the type specific signature properties list. /// @@ -184,7 +183,7 @@ public abstract class ComparableObject : ComparableObject, IEquatable /// that no duplicates can occur within the global descriptor collection. protected void RegisterSignatureProperty(Expression> expression) { - Guard.ArgumentNotNull(() => expression); + Guard.NotNull(expression, nameof(expression)); base.RegisterSignatureProperty(expression.ExtractPropertyInfo().Name); } @@ -198,7 +197,6 @@ public virtual bool Equals(T other) return base.Equals(other); } - } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs b/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs index 996493f80d..e59bd5f44a 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ContextState.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.Remoting.Messaging; -using System.Threading; using System.Web; namespace SmartStore.Core.Infrastructure @@ -27,55 +26,64 @@ public ContextState(string name, Func defaultValue) public T GetState() { + var key = BuildKey(); + if (HttpContext.Current == null) { - var data = CallContext.GetData(_name); - + var data = CallContext.GetData(key); + if (data == null) { if (_defaultValue != null) { - CallContext.SetData(_name, data = _defaultValue()); + CallContext.SetData(key, data = _defaultValue()); return data as T; } } - + return data as T; } - if (HttpContext.Current.Items[_name] == null) + if (HttpContext.Current.Items[key] == null) { - HttpContext.Current.Items[_name] = _defaultValue == null ? null : _defaultValue(); + HttpContext.Current.Items[key] = _defaultValue?.Invoke(); } - return HttpContext.Current.Items[_name] as T; + return HttpContext.Current.Items[key] as T; } public void SetState(T state) { if (HttpContext.Current == null) { - CallContext.SetData(_name, state); + CallContext.SetData(BuildKey(), state); } else { - HttpContext.Current.Items[_name] = state; + HttpContext.Current.Items[BuildKey()] = state; } } public void RemoveState() { + var key = BuildKey(); + if (HttpContext.Current == null) { - CallContext.FreeNamedDataSlot(_name); + CallContext.FreeNamedDataSlot(key); } else { - if (HttpContext.Current.Items.Contains(_name)) + if (HttpContext.Current.Items.Contains(key)) { - HttpContext.Current.Items.Remove(_name); + HttpContext.Current.Items.Remove(key); } } } + + private string BuildKey() + { + return "__ContextState." + _name; + } } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs index a3fa432497..f101841492 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Web; using Autofac.Integration.Mvc; @@ -10,10 +11,9 @@ namespace SmartStore.Core.Infrastructure.DependencyManagement /// public class AutofacRequestLifetimeHttpModule : IHttpModule { - public void Init(HttpApplication context) { - Guard.ArgumentNotNull(() => context); + Guard.NotNull(context, nameof(context)); context.EndRequest += OnEndRequest; } @@ -24,15 +24,42 @@ public static void OnEndRequest(object sender, EventArgs e) { LifetimeScopeProvider.EndLifetimeScope(); } + + // Dispose all other disposable object in HttpContext.Items + PurgeContextItems(sender as HttpApplication); } - public static void SetLifetimeScopeProvider(ILifetimeScopeProvider lifetimeScopeProvider) + private static void PurgeContextItems(HttpApplication app) { - if (lifetimeScopeProvider == null) + var items = app?.Context?.Items; + + if (items != null) { - throw new ArgumentNullException("lifetimeScopeProvider"); + int size = items.Count; + if (size > 0) + { + var keys = new object[size]; + items.Keys.CopyTo(keys, 0); + + for (int i = 0; i < size; i++) + { + var obj = items[keys[i]] as IDisposable; + if (obj != null) + { + try + { + obj.Dispose(); + } + catch { } + } + } + } } - LifetimeScopeProvider = lifetimeScopeProvider; + } + + public static void SetLifetimeScopeProvider(ILifetimeScopeProvider 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 59f39184a2..8a934935e0 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Autofac; using Autofac.Builder; using SmartStore.ComponentModel; using SmartStore.Core.Caching; +using SmartStore.Core.Logging; namespace SmartStore.Core.Infrastructure.DependencyManagement { @@ -25,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(); } @@ -65,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) @@ -89,6 +97,7 @@ public object ResolveUnregistered(Type type, ILifetimeScope scope = null) { TryResolveAll(activator.ParameterTypes, out parameterInstances, scope); } + if (parameterInstances != null) { return activator.Activate(parameterInstances); @@ -120,20 +129,41 @@ private bool TryResolveAll(Type[] types, out object[] instances, ILifetimeScope instances = instances2; return true; } - catch + catch (Exception ex) { + _container.Resolve().GetLogger(this.GetType()).Error(ex); return false; } } - public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) + [DebuggerStepThrough] + public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) { - return (scope ?? Scope()).TryResolve(serviceType, out instance); + instance = null; + + try + { + return (scope ?? Scope()).TryResolve(serviceType, out instance); + } + catch + { + return false; + } } - public bool TryResolve(ILifetimeScope scope, out T instance) + [DebuggerStepThrough] + public bool TryResolve(ILifetimeScope scope, out T instance) { - return (scope ?? Scope()).TryResolve(out instance); + instance = default(T); + + try + { + return (scope ?? Scope()).TryResolve(out instance); + } + catch + { + return false; + } } public bool IsRegistered(Type serviceType, ILifetimeScope scope = null) @@ -156,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 03e0fbbba7..1c7fed864c 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs @@ -17,7 +17,7 @@ public class DefaultLifetimeScopeAccessor : ILifetimeScopeAccessor public DefaultLifetimeScopeAccessor(ILifetimeScope rootContainer) { - Guard.ArgumentNotNull(() => rootContainer); + Guard.NotNull(rootContainer, nameof(rootContainer)); //rootContainer.ChildLifetimeScopeBeginning += OnScopeBeginning; @@ -46,13 +46,6 @@ public void EndLifetimeScope() var scope = _state.GetState(); if (scope != null) { - try - { - if (DataSettings.DatabaseIsInstalled()) - scope.Resolve().Flush(); - } - catch { } - scope.Dispose(); _state.RemoveState(); } @@ -68,6 +61,7 @@ public ILifetimeScope GetLifetimeScope(Action configurationAct _state.SetState((scope = BeginLifetimeScope(configurationAction))); //scope.CurrentScopeEnding += OnScopeEnding; } + return scope; } @@ -83,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/DefaultLifetimeScopeProvider.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeProvider.cs index 9f01c73248..78421250f6 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeProvider.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeProvider.cs @@ -10,7 +10,7 @@ public class DefaultLifetimeScopeProvider : ILifetimeScopeProvider public DefaultLifetimeScopeProvider(ILifetimeScopeAccessor accessor) { - Guard.ArgumentNotNull(() => accessor); + Guard.NotNull(accessor, nameof(accessor)); this._accessor = accessor; AutofacRequestLifetimeHttpModule.SetLifetimeScopeProvider(this); 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/DependencyManagement/Work.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/Work.cs new file mode 100644 index 0000000000..afcba925eb --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/Work.cs @@ -0,0 +1,22 @@ +using System; + +namespace SmartStore.Core.Infrastructure.DependencyManagement +{ + public class Work where T : class + { + private readonly Func, T> _resolve; + + public Work(Func, T> resolve) + { + _resolve = resolve; + } + + public T Value + { + get + { + return _resolve(this); + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs b/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs index aa7c8497ee..e610c7b134 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/EngineContext.cs @@ -46,11 +46,18 @@ public static IEngine CreateEngineInstance() if (engineTypeSetting.HasValue()) { var engineType = Type.GetType(engineTypeSetting); + if (engineType == null) + { throw new ConfigurationErrorsException("The type '" + engineType + "' could not be found. Please check the configuration at /configuration/appSettings/add[@key=sm:EngineType] or check for missing assemblies."); + } + if (!typeof(IEngine).IsAssignableFrom(engineType)) + { throw new ConfigurationErrorsException("The type '" + engineType + "' doesn't implement 'SmartStore.Core.Infrastructure.IEngine' and cannot be configured in /configuration/appSettings/add[@key=sm:EngineType] for that purpose."); - return Activator.CreateInstance(engineType) as IEngine; + } + + return Activator.CreateInstance(engineType) as IEngine; } return new SmartStoreEngine(); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Error.cs b/src/Libraries/SmartStore.Core/Infrastructure/Error.cs index 47f03f6f2e..6ef52f2275 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Error.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Error.cs @@ -93,8 +93,8 @@ public static Exception InvalidOperation(string message, Func member) [DebuggerStepThrough] public static Exception InvalidOperation(string message, Exception innerException, Func member) { - Guard.ArgumentNotNull(message, "message"); - Guard.ArgumentNotNull(member, "member"); + Guard.NotNull(message, "message"); + Guard.NotNull(member, "member"); return new InvalidOperationException(message.FormatCurrent(member.Method.Name), innerException); } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs b/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs index 5a15aba9f3..a1d8f6c73a 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs @@ -8,109 +8,177 @@ 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() { } - /// - /// Throws proper exception if the class reference is null. - /// - /// - /// Class reference to check. - /// If class reference is null. - [DebuggerStepThrough] - public static void NotNull(Func value) - { - if (value() == null) - throw new InvalidOperationException("'{0}' cannot be null.".FormatInvariant(value)); - } + #region 3.0 - [DebuggerStepThrough] - public static void ArgumentNotNull(object arg, string argName) - { - if (arg == null) - throw new ArgumentNullException(argName); - } - - [DebuggerStepThrough] - public static void ArgumentNotNull(Func arg) - { - if (arg() == null) - throw new ArgumentNullException(GetParamName(arg)); - } + [DebuggerStepThrough] + public static void NotNull(object arg, string argName) + { + if (arg == null) + throw new ArgumentNullException(argName); + } - [DebuggerStepThrough] - public static void Arguments(Func arg1, Func arg2) - { - if (arg1() == null) - throw new ArgumentNullException(GetParamName(arg1)); + [DebuggerStepThrough] + public static void NotEmpty(string arg, string argName) + { + if (string.IsNullOrWhiteSpace(arg)) + throw Error.Argument(argName, "String parameter '{0}' cannot be null or all whitespace.", argName); + } + + [DebuggerStepThrough] + public static void NotEmpty(ICollection arg, string argName) + { + if (arg == null || !arg.Any()) + throw Error.Argument(argName, "Collection cannot be null and must contain at least one item."); + } + + [DebuggerStepThrough] + public static void NotEmpty(Guid arg, string argName) + { + if (arg == Guid.Empty) + throw Error.Argument(argName, "Argument '{0}' cannot be an empty guid.", argName); + } + + [DebuggerStepThrough] + public static void InRange(T arg, T min, T max, string argName) where T : struct, IComparable + { + if (arg.CompareTo(min) < 0 || arg.CompareTo(max) > 0) + throw Error.ArgumentOutOfRange(argName, "The argument '{0}' must be between '{1}' and '{2}'.", argName, min, max); + } + + [DebuggerStepThrough] + public static void NotOutOfLength(string arg, int maxLength, string argName) + { + if (arg.Trim().Length > maxLength) + { + throw Error.Argument(argName, "Argument '{0}' cannot be more than {1} characters long.", argName, maxLength); + } + } + + [DebuggerStepThrough] + public static void NotNegative(T arg, string argName, string message = NotNegativeMessage) where T : struct, IComparable + { + if (arg.CompareTo(default(T)) < 0) + throw Error.ArgumentOutOfRange(argName, message.FormatInvariant(argName, arg)); + } + + [DebuggerStepThrough] + public static void NotZero(T arg, string argName) where T : struct, IComparable + { + if (arg.CompareTo(default(T)) == 0) + throw Error.ArgumentOutOfRange(argName, "Argument '{0}' must be greater or less than zero. Value: '{1}'.", argName, arg); + } + + [DebuggerStepThrough] + public static void Against(bool assertion, string message = AgainstMessage) where TException : Exception + { + if (assertion) + throw (TException)Activator.CreateInstance(typeof(TException), message); + } + + [DebuggerStepThrough] + public static void Against(Func assertion, string message = AgainstMessage) where TException : Exception + { + //Execute the lambda and if it evaluates to true then throw the exception. + if (assertion()) + throw (TException)Activator.CreateInstance(typeof(TException), message); + } + + [DebuggerStepThrough] + public static void IsPositive(T arg, string argName, string message = IsPositiveMessage) where T : struct, IComparable + { + if (arg.CompareTo(default(T)) < 1) + throw Error.ArgumentOutOfRange(argName, message.FormatInvariant(argName)); + } + + [DebuggerStepThrough] + public static void IsTrue(bool arg, string argName, string message = IsTrueMessage) + { + if (!arg) + throw Error.Argument(argName, message.FormatInvariant(argName)); + } + + [DebuggerStepThrough] + 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); + } + + [DebuggerStepThrough] + 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); + } + } - if (arg2() == null) - throw new ArgumentNullException(GetParamName(arg2)); - } + [DebuggerStepThrough] + public static void NotDisposed(DisposableObject arg, string argName) + { + NotNull(arg, argName); - [DebuggerStepThrough] - public static void Arguments(Func arg1, Func arg2, Func arg3) - { - if (arg1() == null) - throw new ArgumentNullException(GetParamName(arg1)); - - if (arg2() == null) - throw new ArgumentNullException(GetParamName(arg2)); - - if (arg3() == null) - throw new ArgumentNullException(GetParamName(arg3)); - } + if (arg.IsDisposed) + throw Error.ObjectDisposed(argName); + } - [DebuggerStepThrough] - public static void Arguments(Func arg1, Func arg2, Func arg3, Func arg4) - { - if (arg1() == null) - throw new ArgumentNullException(GetParamName(arg1)); + [DebuggerStepThrough] + public static void PagingArgsValid(int indexArg, int sizeArg, string indexArgName, string sizeArgName) + { + NotNegative(indexArg, indexArgName, "PageIndex cannot be below 0"); - if (arg2() == null) - throw new ArgumentNullException(GetParamName(arg2)); + if (indexArg > 0) + { + // if pageIndex is specified (> 0), PageSize CANNOT be 0 + IsPositive(sizeArg, sizeArgName, "PageSize cannot be below 1 if a PageIndex greater 0 was provided."); + } + else + { + // pageIndex 0 actually means: take all! + NotNegative(sizeArg, sizeArgName); + } + } - if (arg3() == null) - throw new ArgumentNullException(GetParamName(arg3)); + #endregion - if (arg4() == null) - throw new ArgumentNullException(GetParamName(arg4)); + [DebuggerStepThrough] + [Obsolete("Use NotNull() with nameof operator instead")] + public static void ArgumentNotNull(object arg, string argName) + { + if (arg == null) + throw new ArgumentNullException(argName); } [DebuggerStepThrough] - public static void Arguments(Func arg1, Func arg2, Func arg3, Func arg4, Func arg5) + [Obsolete("Use NotNull() with nameof operator instead")] + public static void ArgumentNotNull(Func arg) { - if (arg1() == null) - throw new ArgumentNullException(GetParamName(arg1)); - - if (arg2() == null) - throw new ArgumentNullException(GetParamName(arg2)); - - if (arg3() == null) - throw new ArgumentNullException(GetParamName(arg3)); - - if (arg4() == null) - throw new ArgumentNullException(GetParamName(arg4)); - - if (arg5() == null) - throw new ArgumentNullException(GetParamName(arg5)); + if (arg() == null) + throw new ArgumentNullException(GetParamName(arg)); } [DebuggerStepThrough] - public static void ArgumentNotEmpty(Func arg) + [Obsolete("Use NotEmpty() with nameof operator instead")] + public static void ArgumentNotEmpty(Func arg) { if (arg().IsEmpty()) { @@ -120,81 +188,13 @@ public static void ArgumentNotEmpty(Func arg) } [DebuggerStepThrough] - public static void ArgumentNotEmpty(Func arg) - { - if (arg() == Guid.Empty) - { - string argName = GetParamName(arg); - throw Error.Argument(argName, "Argument '{0}' cannot be an empty guid.", argName); - } - } - - [DebuggerStepThrough] - public static void ArgumentNotEmpty(string arg, string argName) + [Obsolete("Use NotEmpty() with nameof operator instead")] + public static void ArgumentNotEmpty(string arg, string argName) { if (arg.IsEmpty()) throw Error.Argument(argName, "String parameter '{0}' cannot be null or all whitespace.", argName); } - [DebuggerStepThrough] - public static void ArgumentNotEmpty(ICollection arg, string argName) - { - if (arg != null && !arg.Any()) - throw Error.Argument(argName, "Collection cannot be null and must have at least one item."); - } - - [DebuggerStepThrough] - public static void ArgumentNotEmpty(Guid arg, string argName) - { - if (arg == Guid.Empty) - throw Error.Argument(argName, "Argument '{0}' cannot be an empty guid.", argName); - } - - [DebuggerStepThrough] - public static void ArgumentInRange(T arg, T min, T max, string argName) where T : struct, IComparable - { - if (arg.CompareTo(min) < 0 || arg.CompareTo(max) > 0) - throw Error.ArgumentOutOfRange(argName, "The argument '{0}' must be between '{1}' and '{2}'.", argName, min, max); - } - - [DebuggerStepThrough] - public static void ArgumentNotOutOfLength(string arg, int maxLength, string argName) - { - if (arg.Trim().Length > maxLength) - { - throw Error.Argument(argName, "Argument '{0}' cannot be more than {1} characters long.", argName, maxLength); - } - } - - [DebuggerStepThrough] - public static void ArgumentNotNegative(T arg, string argName, string message = NotNegativeMessage) where T : struct, IComparable - { - if (arg.CompareTo(default(T)) < 0) - throw Error.ArgumentOutOfRange(argName, message.FormatInvariant(argName, arg)); - } - - [DebuggerStepThrough] - public static void ArgumentNotZero(T arg, string argName) where T : struct, IComparable - { - if (arg.CompareTo(default(T)) == 0) - throw Error.ArgumentOutOfRange(argName, "Argument '{0}' must be greater or less than zero. Value: '{1}'.", argName, arg); - } - - [DebuggerStepThrough] - public static void Against(bool assertion, string message = AgainstMessage) where TException : Exception - { - if (assertion) - throw (TException)Activator.CreateInstance(typeof(TException), message); - } - - [DebuggerStepThrough] - public static void Against(Func assertion, string message = AgainstMessage) where TException : Exception - { - //Execute the lambda and if it evaluates to true then throw the exception. - if (assertion()) - throw (TException)Activator.CreateInstance(typeof(TException), message); - } - [DebuggerStepThrough] public static void InheritsFrom(Type type) { @@ -258,64 +258,6 @@ public static void HasDefaultConstructor(Type t) throw Error.InvalidOperation("The type '{0}' must have a default parameterless constructor.", t.FullName); } - [DebuggerStepThrough] - public static void ArgumentIsPositive(T arg, string argName, string message = IsPositiveMessage) where T : struct, IComparable - { - if (arg.CompareTo(default(T)) < 1) - throw Error.ArgumentOutOfRange(argName, message.FormatInvariant(argName)); - } - - [DebuggerStepThrough] - public static void ArgumentIsTrue(bool arg, string argName, string message = IsTrueMessage) - { - if (!arg) - throw Error.Argument(argName, message.FormatInvariant(argName)); - } - - - [DebuggerStepThrough] - public static void ArgumentIsEnumType(Type arg, string argName) - { - ArgumentNotNull(arg, argName); - if (!arg.IsEnum) - throw Error.Argument(argName, "Type '{0}' must be a valid Enum type.", arg.FullName); - } - - [DebuggerStepThrough] - public static void ArgumentIsEnumType(Type enumType, object arg, string argName) - { - ArgumentNotNull(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); - } - } - - - [DebuggerStepThrough] - public static void ArgumentNotDisposed(DisposableObject arg, string argName) - { - ArgumentNotNull(arg, argName); - if (arg.IsDisposed) - throw Error.ObjectDisposed(argName); - } - - - [DebuggerStepThrough] - public static void PagingArgsValid(int indexArg, int sizeArg, string indexArgName, string sizeArgName) - { - ArgumentNotNegative(indexArg, indexArgName, "PageIndex cannot be below 0"); - if (indexArg > 0) - { - // if pageIndex is specified (> 0), PageSize CANNOT be 0 - ArgumentIsPositive(sizeArg, sizeArgName, "PageSize cannot be below 1 if a PageIndex greater 0 was provided."); - } - else - { - // pageIndex 0 actually means: take all! - ArgumentNotNegative(sizeArg, sizeArgName); - } - } [DebuggerStepThrough] [SuppressMessage("ReSharper", "UnusedMember.Local")] diff --git a/src/Libraries/SmartStore.Core/Infrastructure/IApplicationEnvironment.cs b/src/Libraries/SmartStore.Core/Infrastructure/IApplicationEnvironment.cs new file mode 100644 index 0000000000..c105628a46 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Infrastructure/IApplicationEnvironment.cs @@ -0,0 +1,17 @@ +using System; +using SmartStore.Core.IO; + +namespace SmartStore.Core +{ + public interface IApplicationEnvironment + { + string MachineName { get; } + string EnvironmentIdentifier { get; } + + IVirtualFolder WebRootFolder { get; } + IVirtualFolder AppDataFolder { get; } + IVirtualFolder ThemesFolder { get; } + IVirtualFolder PluginsFolder { get; } + IVirtualFolder TenantFolder { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs b/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs index 43d05ecce3..4454dfc343 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/IEngine.cs @@ -20,6 +20,12 @@ public interface IEngine ///
void Initialize(); + /// + /// Determines whether the app has been installed, successfully bootstrapped + /// AND the very first HTTP request has been issued. + /// + bool IsFullyInitialized { get; set; } + T Resolve(string name = null) where T : class; object Resolve(Type type, string name = null); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ITypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/ITypeFinder.cs index 76efaf68b7..a198846e58 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ITypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ITypeFinder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; namespace SmartStore.Core.Infrastructure @@ -11,10 +12,35 @@ namespace SmartStore.Core.Infrastructure ///
public interface ITypeFinder { - IList GetAssemblies(bool ignoreInactivePlugins = false); + ///// + ///// Gets all SmartStore specific assemblies (core & all plugins) + ///// + ///// Indicates whether uninstalled plugin's assemblies should be ignored + ///// A list of assemblies that should be loaded by the SmartStore factory. + IEnumerable GetAssemblies(bool ignoreInactivePlugins = false); IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses = true); } + public class NullTypeFinder : ITypeFinder + { + private static readonly ITypeFinder s_instance = new NullTypeFinder(); + + public static ITypeFinder Instance + { + get { return s_instance; } + } + + public IEnumerable GetAssemblies(bool ignoreInactivePlugins = false) + { + return Enumerable.Empty(); + } + + public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses = true) + { + return Enumerable.Empty(); + } + } + public static class ITypeFinderExtensions { public static IEnumerable FindClassesOfType(this ITypeFinder finder, bool onlyConcreteClasses = true, bool ignoreInactivePlugins = false) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs index a9143bed56..cd098742ce 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs @@ -19,14 +19,12 @@ public static class RegularExpressions public static readonly Regex IsPositiveInteger = new Regex(@"\d{1,10}", RegexOptions.Compiled); public static readonly Regex IsNumeric = new Regex("(" + ValidRealPattern + ")|(" + ValidIntegerPattern + ")", RegexOptions.Compiled); - //public static readonly Regex IsWebUrl = new Regex(@"(http|https)://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?", RegexOptions.Singleline | RegexOptions.Compiled); - /// see https://msdn.microsoft.com/en-us/library/ms998267.aspx - public static readonly Regex IsWebUrl = new Regex(@"^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_\=~]*)?$", RegexOptions.Singleline | RegexOptions.Compiled); + // //public static readonly Regex IsWebUrl = new Regex(@"(http|https)://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?", RegexOptions.Singleline | RegexOptions.Compiled); + ///// see https://msdn.microsoft.com/en-us/library/ms998267.aspx + //public static readonly Regex IsWebUrl = new Regex(@"^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_\=~]*)?$", RegexOptions.Singleline | RegexOptions.Compiled); public static readonly Regex IsEmail = new Regex("^(?:[\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`\\{\\|\\}\\~]+\\.)*[\\w\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`\\{\\|\\}\\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-](?!\\.)){0,61}[a-zA-Z0-9]?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\\[(?:(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.){3}(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\]))$", RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); - public static readonly Regex RemoveHTML = new Regex(@"<[/]{0,1}\s*(?\w*)\s*(?.*?=['""].*?[""'])*?\s*[/]{0,1}>", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); - public static readonly Regex IsGuid = new Regex(@"\{?[a-fA-F0-9]{8}(?:-(?:[a-fA-F0-9]){4}){3}-[a-fA-F0-9]{12}\}?", RegexOptions.Compiled); public static readonly Regex IsBase64Guid = new Regex(@"[a-zA-Z0-9+/=]{22,24}", RegexOptions.Compiled); 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 accd095b46..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; @@ -7,6 +8,7 @@ using Autofac.Integration.Mvc; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Core.Infrastructure @@ -15,9 +17,18 @@ public class SmartStoreEngine : IEngine { private ContainerManager _containerManager; - #region Utilities + public SmartStoreEngine() + { + Logger = NullLogger.Instance; + } + + protected ILogger Logger + { + get; + private set; + } - protected virtual void RunStartupTasks() + protected virtual void RunStartupTasks() { var typeFinder = _containerManager.Resolve(); var startUpTaskTypes = typeFinder.FindClassesOfType(ignoreInactivePlugins: true); @@ -32,7 +43,18 @@ protected virtual void RunStartupTasks() var groupedTasks = startUpTasks.OrderBy(st => st.Order).ToLookup(x => x.Order); foreach (var tasks in groupedTasks) { - Parallel.ForEach(tasks, task => { task.Execute(); }); + Parallel.ForEach(tasks, task => + { + try + { + Logger.DebugFormat("Executing startup task '{0}'", task.GetType().FullName); + task.Execute(); + } + catch (Exception ex) + { + Logger.ErrorFormat(ex, "Error while executing startup task '{0}'", task.GetType().FullName); + } + }); } } @@ -54,6 +76,8 @@ protected virtual ContainerManager RegisterDependencies() var container = builder.Build(); var typeFinder = CreateTypeFinder(); + _containerManager = new ContainerManager(container); + // core dependencies builder = new ContainerBuilder(); builder.RegisterInstance(this).As(); @@ -69,9 +93,19 @@ protected virtual ContainerManager RegisterDependencies() builder.RegisterInstance(dependencyResolver); DependencyResolver.SetResolver(dependencyResolver); + // Logging dependencies should be available very early + builder.RegisterModule(new LoggingModule()); + +#pragma warning disable 612, 618 builder.Update(container); +#pragma warning restore 612, 618 + + // Propagate logger + var logger = container.Resolve().GetLogger("SmartStore.Bootstrapper"); + this.Logger = logger; + ((AppDomainTypeFinder)typeFinder).Logger = logger; - // register dependencies provided by other assemblies + // Register dependencies provided by other assemblies builder = new ContainerBuilder(); var registrarTypes = typeFinder.FindClassesOfType(); var registrarInstances = new List(); @@ -79,34 +113,44 @@ protected virtual ContainerManager RegisterDependencies() { registrarInstances.Add((IDependencyRegistrar)Activator.CreateInstance(type)); } - // sort - registrarInstances = registrarInstances.AsQueryable().OrderBy(t => t.Order).ToList(); + + // Sort + registrarInstances = registrarInstances.OrderBy(t => t.Order).ToList(); foreach (var registrar in registrarInstances) { - registrar.Register(builder, typeFinder, PluginManager.IsActivePluginAssembly(registrar.GetType().Assembly)); + var type = registrar.GetType(); + logger.DebugFormat("Executing dependency registrar '{0}'", type.FullName); + registrar.Register(builder, typeFinder, PluginManager.IsActivePluginAssembly(type.Assembly)); } + +#pragma warning disable 612, 618 builder.Update(container); +#pragma warning restore 612, 618 - return new ContainerManager(container); + return _containerManager; } - - #endregion - - #region Methods /// - /// Initialize components and plugins in the sm environment. + /// Initialize components and plugins /// public void Initialize() { - _containerManager = RegisterDependencies(); + RegisterDependencies(); + if (DataSettings.DatabaseIsInstalled()) { RunStartupTasks(); } } - public T Resolve(string name = null) where T : class + public bool IsFullyInitialized + { + get; + set; + } + + [DebuggerStepThrough] + public T Resolve(string name = null) where T : class { if (name.HasValue()) { @@ -115,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()) @@ -124,16 +169,12 @@ public object Resolve(Type type, string name = null) return ContainerManager.Resolve(type); } - + [DebuggerStepThrough] public T[] ResolveAll() { return ContainerManager.ResolveAll(); } - #endregion - - #region Properties - public IContainer Container { get { return _containerManager.Container; } @@ -143,8 +184,5 @@ public ContainerManager ContainerManager { get { return _containerManager; } } - - #endregion - } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs index 90dc70012f..a437eebd7d 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; using System.Reflection; using System.Web; using System.Web.Hosting; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; using SmartStore.Utilities; namespace SmartStore.Core.Infrastructure @@ -13,37 +18,27 @@ namespace SmartStore.Core.Infrastructure /// public class WebAppTypeFinder : AppDomainTypeFinder { - #region Fields - private bool _ensureBinFolderAssembliesLoaded; - private bool _binFolderAssembliesLoaded; - - #endregion - #region Ctor + private readonly Lazy> _allAssembliesResolver; + private readonly Lazy> _activeAssembliesResolver; - public WebAppTypeFinder() + public WebAppTypeFinder() { - this._ensureBinFolderAssembliesLoaded = CommonHelper.GetAppSetting("sm:EnableDynamicDiscovery", true); - } - - #endregion - - #region Properties + _ensureBinFolderAssembliesLoaded = CommonHelper.GetAppSetting("sm:EnableDynamicDiscovery", true); + _allAssembliesResolver = new Lazy>(() => this.GetAssembliesInternal(false)); + _activeAssembliesResolver = new Lazy>(() => this.GetAssembliesInternal(true)); + } /// - /// Gets or sets wether assemblies in the bin folder of the web application should be specificly checked for beeing loaded on application load. - /// This is needed in situations where plugins need to be loaded in the AppDomain after the application been reloaded. + /// Gets or sets wether assemblies in the bin folder of the web application should be explicitly checked for being loaded on application load. + /// This is needed in situations where plugins need to be loaded into the AppDomain after the application has been reloaded. /// public bool EnsureBinFolderAssembliesLoaded { get { return _ensureBinFolderAssembliesLoaded; } set { _ensureBinFolderAssembliesLoaded = value; } - } - - #endregion - - #region Methods + } /// /// Gets a physical disk path of \Bin directory @@ -53,27 +48,112 @@ public virtual string GetBinDirectory() { if (HostingEnvironment.IsHosted) { - //hosted + // Hosted return HttpRuntime.BinDirectory; } else { - //not hosted. For example, run either in unit tests + // Not hosted. For example, run either in unit tests return AppDomain.CurrentDomain.BaseDirectory; } } - public override IList GetAssemblies(bool ignoreInactivePlugins = false) - { - if (this.EnsureBinFolderAssembliesLoaded && !_binFolderAssembliesLoaded) - { - _binFolderAssembliesLoaded = true; - string binPath = GetBinDirectory(); - LoadMatchingAssemblies(binPath); - } + public override IEnumerable GetAssemblies(bool ignoreInactivePlugins = false) + { + if (ignoreInactivePlugins) + { + return _activeAssembliesResolver.Value; + } + else + { + return _allAssembliesResolver.Value; + } + } - return base.GetAssemblies(ignoreInactivePlugins); - } - #endregion - } + private IEnumerable GetAssembliesInternal(bool ignoreInactivePlugins) + { + if (ignoreInactivePlugins) + { + var allAssemblies = _allAssembliesResolver.Value; + return allAssemblies.Where(x => PluginManager.IsActivePluginAssembly(x)).AsReadOnly(); + } + + if (this.EnsureBinFolderAssembliesLoaded) + { + LoadPluginAssemblies(); + } + + return base.GetAssemblies(false); + } + + /// + /// Makes sure matching plugin assemblies are loaded into the app domain. + /// + protected virtual void LoadPluginAssemblies() + { + var alreadyLoadedAssemblyNames = new HashSet( + AppDomain.CurrentDomain.GetAssemblies().Select(x => x.FullName), + StringComparer.OrdinalIgnoreCase); + + var pluginAssemblies = new HashSet(PluginManager.ReferencedPlugins.Select(x => x.Assembly.Assembly)); + + foreach (var assembly in pluginAssemblies) + { + try + { + if (!alreadyLoadedAssemblyNames.Contains(assembly.FullName)) + { + App.Load(assembly.FullName); + Logger.DebugFormat("Loaded plugin assembly '{0}'", assembly.FullName); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } + } + } + + #region Obsolete + + ///// + ///// Makes sure matching assemblies in the supplied folder are loaded into the app domain. + ///// + ///// The physical path to a directory containing dlls to load into the app domain. + //protected virtual void LoadCandidateAssembliesFromFolder(string path) + //{ + // var alreadyLoadedAssemblyNames = new HashSet(); + // var appDomainAssemblies = base.GetAssemblies(); + + // foreach (var a in appDomainAssemblies) + // { + // alreadyLoadedAssemblyNames.Add(a.FullName); + // } + + // if (!Directory.Exists(path)) + // { + // return; + // } + + // var dlls = Directory.GetFiles(path, "*.dll"); + + // foreach (string dllPath in dlls) + // { + // try + // { + // var an = AssemblyName.GetAssemblyName(dllPath); + // if (!alreadyLoadedAssemblyNames.Contains(an.FullName) && Matches(an.FullName)) + // { + // App.Load(an); + // } + // } + // catch (BadImageFormatException ex) + // { + // Trace.TraceError(ex.ToString()); + // } + // } + //} + + #endregion + } } diff --git a/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs b/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs index 4c7c1ec45d..ce8c87c8b1 100644 --- a/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs +++ b/src/Libraries/SmartStore.Core/Linq/Expanders/LambdaPathExpander.cs @@ -2,14 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Linq.Dynamic; -using System.ComponentModel; using System.Reflection; using SmartStore.Utilities; namespace SmartStore.Linq { - public class LambdaPathExpander : IPathExpander { private readonly IList _expands; @@ -34,7 +31,7 @@ public virtual void Expand(Expression> path) public virtual void Expand(Expression> path) { - Guard.ArgumentNotNull(path, "path"); + Guard.NotNull(path, "path"); _expands.Add(path); } @@ -45,8 +42,8 @@ public void Expand(string path) public virtual void Expand(Type type, string path) { - Guard.ArgumentNotNull(type, "type"); - Guard.ArgumentNotEmpty(path, "path"); + Guard.NotNull(type, "type"); + Guard.NotEmpty(path, "path"); Type t = type; @@ -118,8 +115,8 @@ private void DoExpand(Type type, string path) #region Old //public virtual void Expand(Type type, string path) //{ - // Guard.ArgumentNotNull(type, "type"); - // Guard.ArgumentNotEmpty(path, "path"); + // Guard.NotNull(type, "type"); + // Guard.NotEmpty(path, "path"); // Type t = type; // IDictionary paths = new Dictionary(); @@ -211,7 +208,7 @@ private void DoExpand(Type type, string path) // public PathExpressionParser(string path) // { - // Guard.ArgumentNotEmpty(path, "path"); + // Guard.NotEmpty(path, "path"); // Path = path; // SetPos(0); diff --git a/src/Libraries/SmartStore.Core/Linq/Expanders/StringPathExpander.cs b/src/Libraries/SmartStore.Core/Linq/Expanders/StringPathExpander.cs index b1d950c37a..ca3f29bc24 100644 --- a/src/Libraries/SmartStore.Core/Linq/Expanders/StringPathExpander.cs +++ b/src/Libraries/SmartStore.Core/Linq/Expanders/StringPathExpander.cs @@ -32,7 +32,7 @@ public virtual void Expand(Expression> path) public virtual void Expand(Expression> path) { - Guard.ArgumentNotNull(path, "path"); + Guard.NotNull(path, "path"); string pathExpression = String.Empty; @@ -64,7 +64,7 @@ where targetType.IsAssignableFrom(property.PropertyType) public virtual void Expand(string path) { - Guard.ArgumentNotEmpty(path, "path"); + Guard.NotEmpty(path, "path"); _expands.Add(path); } diff --git a/src/Libraries/SmartStore.Core/Linq/LinqContainsPredicateBuilder.cs b/src/Libraries/SmartStore.Core/Linq/LinqContainsPredicateBuilder.cs index 3f45e7e852..909318e2c3 100644 --- a/src/Libraries/SmartStore.Core/Linq/LinqContainsPredicateBuilder.cs +++ b/src/Libraries/SmartStore.Core/Linq/LinqContainsPredicateBuilder.cs @@ -19,8 +19,8 @@ public static class LinqContainsPredicateBuilder /// The LINQ expression. public static Expression> Build(ICollection collection, string targetProperty) { - Guard.ArgumentNotEmpty(collection, "collection"); - Guard.ArgumentNotEmpty(targetProperty, "targetProperty"); + Guard.NotEmpty(collection, nameof(collection)); + Guard.NotEmpty(targetProperty, nameof(targetProperty)); Expression completeExpression = null; ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "t"); diff --git a/src/Libraries/SmartStore.Core/Linq/PredicateBuilder.cs b/src/Libraries/SmartStore.Core/Linq/PredicateBuilder.cs index 74d7a123ea..06101c7c6d 100644 --- a/src/Libraries/SmartStore.Core/Linq/PredicateBuilder.cs +++ b/src/Libraries/SmartStore.Core/Linq/PredicateBuilder.cs @@ -1,12 +1,9 @@ using System; using System.Linq; using System.Linq.Expressions; -using System.Collections.Generic; -using System.Reflection; namespace SmartStore.Linq { - public static class PredicateBuilder { public static Expression> True() @@ -34,7 +31,5 @@ public static Expression> And(this Expression> ex return Expression.Lambda> (Expression.And(expr1.Body, invokedExpr), expr1.Parameters); } - } - } \ No newline at end of file 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/LocalizationHelper.cs b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs index ed9beed2ad..c4bbff8a75 100644 --- a/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs +++ b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -10,13 +11,47 @@ public static class LocalizationHelper new HashSet( CultureInfo.GetCultures(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures | CultureTypes.UserCustomCulture) .Select(x => x.IetfLanguageTag) - .Where(x => !string.IsNullOrWhiteSpace(x))); + .Where(x => !string.IsNullOrWhiteSpace(x)), StringComparer.OrdinalIgnoreCase); - public static bool IsValidCultureCode(string cultureCode) + public static bool IsValidCultureCode(string locale) { - return _cultureCodes.Contains(cultureCode); + return locale.HasValue() && _cultureCodes.Contains(locale); } + /// + /// Enumerates all parent cultures, excluding the top-most invariant culture + /// + /// The ISO culture code, e.g. de-DE, en-US or just en + /// Parent cultures + public static IEnumerable EnumerateParentCultures(string locale) + { + if (locale.IsEmpty() || !_cultureCodes.Contains(locale)) + { + return Enumerable.Empty(); + } + + return EnumerateParentCultures(CultureInfo.GetCultureInfo(locale)); + } + + /// + /// Enumerates all parent cultures, excluding the top-most invariant culture + /// + /// The culture info to enumerate parents for + /// Parent cultures + public static IEnumerable EnumerateParentCultures(CultureInfo culture) + { + if (culture == null) + { + yield break; + } + + while (culture.Parent.TwoLetterISOLanguageName != "iv") + { + yield return culture.Parent; + culture = culture.Parent; + } + } + public static string GetLanguageNativeName(string locale) { try diff --git a/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs b/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs index daa9fbf041..5965d20b51 100644 --- a/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs +++ b/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs @@ -3,7 +3,6 @@ namespace SmartStore.Core.Localization { - [Serializable] public class LocalizedString : IHtmlString { @@ -27,6 +26,7 @@ public static LocalizedString TextOrDefault(string text, LocalizedString default { if (string.IsNullOrEmpty(text)) return defaultValue; + return new LocalizedString(text); } @@ -45,7 +45,18 @@ public string Text get { return _localized; } } - public static implicit operator string(LocalizedString obj) + /// + /// Returns a js encoded string which already contains delimiters. + /// + public IHtmlString JsText + { + get + { + return System.Web.Mvc.MvcHtmlString.Create(_localized.EncodeJsString()); + } + } + + public static implicit operator string(LocalizedString obj) { return obj.Text; } @@ -81,6 +92,5 @@ public override bool Equals(object obj) var that = (LocalizedString)obj; return string.Equals(_localized, that._localized); } - } } diff --git a/src/Libraries/SmartStore.Core/Localization/Localizer.cs b/src/Libraries/SmartStore.Core/Localization/Localizer.cs index 82e62d9d24..bb96ea085b 100644 --- a/src/Libraries/SmartStore.Core/Localization/Localizer.cs +++ b/src/Libraries/SmartStore.Core/Localization/Localizer.cs @@ -1,4 +1,5 @@ namespace SmartStore.Core.Localization { public delegate LocalizedString Localizer(string key, params object[] args); + public delegate LocalizedString LocalizerEx(string key, int languageId, params object[] args); } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Localization/NullLocalizer.cs b/src/Libraries/SmartStore.Core/Localization/NullLocalizer.cs index f325439740..0b43a01c1d 100644 --- a/src/Libraries/SmartStore.Core/Localization/NullLocalizer.cs +++ b/src/Libraries/SmartStore.Core/Localization/NullLocalizer.cs @@ -1,22 +1,26 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace SmartStore.Core.Localization { public static class NullLocalizer { - private static readonly Localizer s_instance; - + private static readonly Localizer _instance; + private static readonly LocalizerEx _instanceEx; + static NullLocalizer() { - s_instance = (format, args) => new LocalizedString((args == null || args.Length == 0) ? format : string.Format(format, args)); + _instance = (format, args) => new LocalizedString((args == null || args.Length == 0) ? format : string.Format(format, args)); + _instanceEx = (format, languageId, args) => new LocalizedString((args == null || args.Length == 0) ? format : string.Format(format, args)); } public static Localizer Instance { - get { return s_instance; } + get { return _instance; } + } + + public static LocalizerEx InstanceEx + { + get { return _instanceEx; } } } } diff --git a/src/Libraries/SmartStore.Core/Logging/IChronometer.cs b/src/Libraries/SmartStore.Core/Logging/IChronometer.cs new file mode 100644 index 0000000000..fb02512a32 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Logging/IChronometer.cs @@ -0,0 +1,46 @@ +using System; +using SmartStore.Utilities; + +namespace SmartStore.Core.Logging +{ + public interface IChronometer : IDisposable + { + void StepStart(string key, string message); + void StepStop(string key); + } + + public static class IChronometerExtensions + { + public static IDisposable Step(this IChronometer chronometer, string message) + { + Guard.NotEmpty(message, nameof(message)); + + var key = "step" + CommonHelper.GenerateRandomDigitCode(10); + + chronometer.StepStart(key, message); + return new ActionDisposable(() => chronometer.StepStop(key)); + } + } + + public class NullChronometer : IChronometer + { + private static readonly IChronometer _instance = new NullChronometer(); + + public static IChronometer Instance + { + get { return _instance; } + } + + public void StepStart(string key, string message) + { + } + + public void StepStop(string key) + { + } + + public void Dispose() + { + } + } +} diff --git a/src/Libraries/SmartStore.Core/Logging/ILogger.cs b/src/Libraries/SmartStore.Core/Logging/ILogger.cs index 098d77c6ed..edec0f8dd0 100644 --- a/src/Libraries/SmartStore.Core/Logging/ILogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/ILogger.cs @@ -1,81 +1,26 @@ using System; -using System.Collections.Generic; -using SmartStore.Core; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Logging; namespace SmartStore.Core.Logging { - /// - /// Logger interface - /// - public partial interface ILogger + /// + /// Interface that all loggers implement + /// + public partial interface ILogger { - /// - /// Determines whether a log level is enabled - /// - /// Log level - /// Result - bool IsEnabled(LogLevel level); - - /// - /// Deletes a log item - /// - /// Log item - void DeleteLog(Log log); - - /// - /// Clears a log - /// - void ClearLog(); - void ClearLog(DateTime toUtc, LogLevel logLevel); - - /// - /// Gets all log items - /// - /// Log item creation from; null to load all records - /// Log item creation to; null to load all records - /// Message - /// Log level; null to load all records - /// Page index - /// Page size - /// Log item collection - IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, string message, LogLevel? logLevel, int pageIndex, int pageSize, int minFrequency); - - /// - /// Gets a log item - /// - /// Log item identifier - /// Log item - Log GetLogById(int logId); - - /// - /// Get log items by identifiers - /// - /// Log item identifiers - /// Log items - IList GetLogByIds(int[] logIds); - /// - /// Inserts a log item + /// Checks if this logger is enabled for a given passed as parameter. /// - /// The log context - /// Always return null - void InsertLog(LogContext context); - - /// - /// Inserts a log item - /// - /// Log level - /// The short message - /// The full message - /// The customer to associate log record with - /// Always return null - void InsertLog(LogLevel logLevel, string shortMessage, string fullMessage = "", Customer customer = null); + /// true if this logger is enabled for level, otherwise false + /// Result + bool IsEnabledFor(LogLevel level); /// - /// Commits log entries to the data store + /// Generates a logging event for the specified level using the message and exception /// - void Flush(); + /// The level of the message to be logged + /// The exception to log, including its stack trace. Pass null to not log an exception + /// The message object to log + /// An Object array containing zero or more objects to format. Can be null. + void Log(LogLevel level, Exception exception, string message, object[] args); } } diff --git a/src/Libraries/SmartStore.Core/Logging/ILoggerFactory.cs b/src/Libraries/SmartStore.Core/Logging/ILoggerFactory.cs new file mode 100644 index 0000000000..89f159f187 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Logging/ILoggerFactory.cs @@ -0,0 +1,19 @@ +using System; + +namespace SmartStore.Core.Logging +{ + public interface ILoggerFactory + { + ILogger GetLogger(Type type); + ILogger GetLogger(string name); + void FlushAll(); + } + + public static class ILoggerFactoryExtensions + { + public static ILogger CreateLogger(this ILoggerFactory factory) where T : class + { + return factory.GetLogger(typeof(T)); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Logging/LogContext.cs b/src/Libraries/SmartStore.Core/Logging/LogContext.cs deleted file mode 100644 index 58bbff7824..0000000000 --- a/src/Libraries/SmartStore.Core/Logging/LogContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Logging; - -namespace SmartStore.Core.Logging -{ - public class LogContext - { - public string ShortMessage { get; set; } - public string FullMessage { get; set; } - public LogLevel LogLevel { get; set; } - public Customer Customer { get; set; } - - public bool HashNotFullMessage { get; set; } - public bool HashIpAddress { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Core/Domain/Logging/LogLevel.cs b/src/Libraries/SmartStore.Core/Logging/LogLevel.cs similarity index 84% rename from src/Libraries/SmartStore.Core/Domain/Logging/LogLevel.cs rename to src/Libraries/SmartStore.Core/Logging/LogLevel.cs index c1ecd011a1..8792dbee72 100644 --- a/src/Libraries/SmartStore.Core/Domain/Logging/LogLevel.cs +++ b/src/Libraries/SmartStore.Core/Logging/LogLevel.cs @@ -1,4 +1,4 @@ -namespace SmartStore.Core.Domain.Logging +namespace SmartStore.Core.Logging { /// /// Represents a log level diff --git a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs index fb8a10be35..2d7d6e82ee 100644 --- a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs +++ b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs @@ -1,71 +1,72 @@ using System; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Logging; namespace SmartStore.Core.Logging { public static class LoggingExtensions { - - public static void Debug(this ILogger logger, string message, Exception exception = null, Customer customer = null) - { - FilteredLog(logger, LogLevel.Debug, message, null, exception, customer); - } + public static bool IsDebugEnabled(this ILogger l) => l.IsEnabledFor(LogLevel.Debug); + public static bool IsInfoEnabled(this ILogger l) => l.IsEnabledFor(LogLevel.Information); + public static bool IsWarnEnabled(this ILogger l) => l.IsEnabledFor(LogLevel.Warning); + public static bool IsErrorEnabled(this ILogger l) => l.IsEnabledFor(LogLevel.Error); + public static bool IsFatalEnabled(this ILogger l) => l.IsEnabledFor(LogLevel.Fatal); - public static void Information(this ILogger logger, string message, Exception exception = null, Customer customer = null) - { - FilteredLog(logger, LogLevel.Information, message, null, exception, customer); - } - public static void Warning(this ILogger logger, string message, Exception exception = null, Customer customer = null) - { - FilteredLog(logger, LogLevel.Warning, message, null, exception, customer); - } - public static void Error(this ILogger logger, string message, Exception exception = null, Customer customer = null) - { - FilteredLog(logger, LogLevel.Error, message, null, exception, customer); - } + public static void Debug(this ILogger l, string msg) => FilteredLog(l, LogLevel.Debug, null, msg, null); + public static void Debug(this ILogger l, Func msgFactory) => FilteredLog(l, LogLevel.Debug, null, msgFactory); + public static void Debug(this ILogger l, Exception ex, string msg) => FilteredLog(l, LogLevel.Debug, ex, msg, null); + public static void DebugFormat(this ILogger l, string msg, params object[] args) => FilteredLog(l, LogLevel.Debug, null, msg, args); + public static void DebugFormat(this ILogger l, Exception ex, string msg, params object[] args) => FilteredLog(l, LogLevel.Debug, ex, msg, args); - public static void Error(this ILogger logger, string message, string fullMessage, Exception exception = null, Customer customer = null) - { - FilteredLog(logger, LogLevel.Error, message, fullMessage, exception, customer); - } + public static void Info(this ILogger l, string msg) => FilteredLog(l, LogLevel.Information, null, msg, null); + public static void Info(this ILogger l, Func msgFactory) => FilteredLog(l, LogLevel.Information, null, msgFactory); + public static void Info(this ILogger l, Exception ex, string msg) => FilteredLog(l, LogLevel.Information, ex, msg, null); + public static void InfoFormat(this ILogger l, string msg, params object[] args) => FilteredLog(l, LogLevel.Information, null, msg, args); + public static void InfoFormat(this ILogger l, Exception ex, string msg, params object[] args) => FilteredLog(l, LogLevel.Information, ex, msg, args); - public static void Fatal(this ILogger logger, string message, Exception exception = null, Customer customer = null) - { - FilteredLog(logger, LogLevel.Fatal, message, null, exception, customer); - } + public static void Warn(this ILogger l, string msg) => FilteredLog(l, LogLevel.Warning, null, msg, null); + public static void Warn(this ILogger l, Func msgFactory) => FilteredLog(l, LogLevel.Warning, null, msgFactory); + public static void Warn(this ILogger l, Exception ex, string msg) => FilteredLog(l, LogLevel.Warning, ex, msg, null); + public static void WarnFormat(this ILogger l, string msg, params object[] args) => FilteredLog(l, LogLevel.Warning, null, msg, args); + public static void WarnFormat(this ILogger l, Exception ex, string msg, params object[] args) => FilteredLog(l, LogLevel.Warning, ex, msg, args); - public static void Error(this ILogger logger, Exception exception, Customer customer = null) - { - FilteredLog(logger, LogLevel.Error, exception.ToAllMessages(), null, exception, customer); - } + public static void Error(this ILogger l, string msg) => FilteredLog(l, LogLevel.Error, null, msg, null); + public static void Error(this ILogger l, Func msgFactory) => FilteredLog(l, LogLevel.Error, null, msgFactory); + public static void Error(this ILogger l, Exception ex) => FilteredLog(l, LogLevel.Error, ex, null, null); + public static void Error(this ILogger l, Exception ex, string msg) => FilteredLog(l, LogLevel.Error, ex, msg, null); + public static void ErrorFormat(this ILogger l, string msg, params object[] args) => FilteredLog(l, LogLevel.Error, null, msg, args); + public static void ErrorFormat(this ILogger l, Exception ex, string msg, params object[] args) => FilteredLog(l, LogLevel.Error, ex, msg, args); - public static void ErrorsAll(this ILogger logger, Exception exception, Customer customer = null) + public static void Fatal(this ILogger l, string msg) => FilteredLog(l, LogLevel.Fatal, null, msg, null); + public static void Fatal(this ILogger l, Func msgFactory) => FilteredLog(l, LogLevel.Fatal, null, msgFactory); + public static void Fatal(this ILogger l, Exception ex) => FilteredLog(l, LogLevel.Fatal, ex, null, null); + public static void Fatal(this ILogger l, Exception ex, string msg) => FilteredLog(l, LogLevel.Fatal, ex, msg, null); + public static void FatalFormat(this ILogger l, string msg, params object[] args) => FilteredLog(l, LogLevel.Fatal, null, msg, args); + public static void FatalFormat(this ILogger l, Exception ex, string msg, params object[] args) => FilteredLog(l, LogLevel.Fatal, ex, msg, args); + + public static void ErrorsAll(this ILogger logger, Exception exception) { while (exception != null) { - FilteredLog(logger, LogLevel.Error, exception.Message, exception.StackTrace, exception, customer); + FilteredLog(logger, LogLevel.Error, exception, exception.Message, null); exception = exception.InnerException; } } - private static void FilteredLog(ILogger logger, LogLevel level, string message, string fullMessage, Exception exception = null, Customer customer = null) - { - // don't log thread abort exception - if ((exception != null) && (exception is System.Threading.ThreadAbortException)) - return; - - if (logger.IsEnabled(level)) - { - if (exception != null && fullMessage.IsEmpty()) - { - fullMessage = "{0}\n{1}\n{2}".FormatCurrent(exception.Message, new String('-', 20), exception.StackTrace); - } + private static void FilteredLog(ILogger logger, LogLevel level, Exception exception, string message, object[] objects) + { + if (logger.IsEnabledFor(level)) + { + logger.Log(level, exception, message, objects); + } + } - logger.InsertLog(level, message, fullMessage, customer); - } - } - } + private static void FilteredLog(ILogger logger, LogLevel level, Exception exception, Func messageFactory) + { + if (logger.IsEnabledFor(level)) + { + logger.Log(level, exception, messageFactory(), null); + } + } + } } diff --git a/src/Libraries/SmartStore.Core/Logging/LoggingModule.cs b/src/Libraries/SmartStore.Core/Logging/LoggingModule.cs new file mode 100644 index 0000000000..131da553b9 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Logging/LoggingModule.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Autofac; +using Autofac.Core; +using Autofac.Core.Activators.Reflection; +using SmartStore.ComponentModel; +using SmartStore.Core.Data; + +namespace SmartStore.Core.Logging +{ + public class LoggingModule : Autofac.Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + + builder.RegisterType().As().SingleInstance(); + + // call GetLogger in response to the request for an ILogger implementation + if (DataSettings.DatabaseIsInstalled()) + { + builder.Register(GetContextualLogger).As().ExternallyOwned(); + } + else + { + // the install logger should append to a rolling text file only. + builder.Register(GetInstallLogger).As().ExternallyOwned(); + } + } + + protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) + { + bool hasCtorLogger = false; + bool hasPropertyLogger = false; + + FastProperty[] loggerProperties = null; + + var ra = registration.Activator as ReflectionActivator; + if (ra != null) + { + // // Look for ctor parameters of type "ILogger" + var ctors = ra.ConstructorFinder.FindConstructors(ra.LimitType); + var loggerParameters = ctors.SelectMany(ctor => ctor.GetParameters()).Where(pi => pi.ParameterType == typeof(ILogger)); + hasCtorLogger = loggerParameters.Any(); + + // Autowire properties + // Look for settable properties of type "ILogger" + loggerProperties = ra.LimitType + .GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance) + .Select(p => new + { + PropertyInfo = p, + p.PropertyType, + IndexParameters = p.GetIndexParameters(), + Accessors = p.GetAccessors(false) + }) + .Where(x => x.PropertyType == typeof(ILogger)) // must be a logger + .Where(x => x.IndexParameters.Count() == 0) // must not be an indexer + .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)) //must have get/set, or only set + .Select(x => new FastProperty(x.PropertyInfo)) + .ToArray(); + + hasPropertyLogger = loggerProperties.Length > 0; + + // Ignore components known to be without logger dependencies + if (!hasCtorLogger && !hasPropertyLogger) + return; + + if (hasPropertyLogger) + { + registration.Metadata.Add("LoggerProperties", loggerProperties); + } + } + + if (hasCtorLogger) + { + registration.Preparing += (sender, args) => + { + var logger = GetLoggerFor(args.Component.Activator.LimitType, args.Context); + args.Parameters = new[] { TypedParameter.From(logger) }.Concat(args.Parameters); + }; + } + + if (hasPropertyLogger) + { + registration.Activating += (sender, args) => + { + var logger = GetLoggerFor(args.Component.Activator.LimitType, args.Context); + var loggerProps = args.Component.Metadata.Get("LoggerProperties") as FastProperty[]; + if (loggerProps != null) + { + foreach (var prop in loggerProps) + { + prop.SetValue(args.Instance, logger); + } + } + }; + } + } + + private ILogger GetLoggerFor(Type componentType, IComponentContext ctx) + { + return ctx.Resolve(new TypedParameter(typeof(Type), componentType)); + } + + private static ILogger GetContextualLogger(IComponentContext context, IEnumerable parameters) + { + // return an ILogger in response to Resolve(componentTypeParameter) + var loggerFactory = context.Resolve(); + + Type containingType = null; + + if (parameters != null && parameters.Any()) + { + if (parameters.Any(x => x is TypedParameter)) + { + containingType = parameters.TypedAs(); + } + else if (parameters.Any(x => x is NamedParameter)) + { + containingType = parameters.Named("Autofac.AutowiringPropertyInjector.InstanceType"); + } + } + + if (containingType != null) + { + return loggerFactory.GetLogger(containingType); + } + else + { + return loggerFactory.GetLogger("SmartStore"); + } + } + + private static ILogger GetInstallLogger(IComponentContext context) + { + return context.Resolve().GetLogger("Install"); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Logging/Notifier.cs b/src/Libraries/SmartStore.Core/Logging/Notifier.cs index be0e903d61..ed2a00ba94 100644 --- a/src/Libraries/SmartStore.Core/Logging/Notifier.cs +++ b/src/Libraries/SmartStore.Core/Logging/Notifier.cs @@ -6,7 +6,6 @@ namespace SmartStore.Core.Logging { - public interface INotifier { void Add(NotifyType type, LocalizedString message, bool durable = true); diff --git a/src/Libraries/SmartStore.Core/Logging/NullLogger.cs b/src/Libraries/SmartStore.Core/Logging/NullLogger.cs index e2cb89133a..ffdb5139a5 100644 --- a/src/Libraries/SmartStore.Core/Logging/NullLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/NullLogger.cs @@ -1,14 +1,9 @@ using System; -using System.Collections.Generic; -using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Logging; namespace SmartStore.Core.Logging { - /// - /// Null logger - /// public partial class NullLogger : ILogger { private static readonly ILogger s_instance = new NullLogger(); @@ -21,89 +16,20 @@ public static ILogger Instance } } - /// - /// Determines whether a log level is enabled - /// - /// Log level - /// Result - public bool IsEnabled(LogLevel level) + public bool IsEnabledFor(LogLevel level) { return false; } - /// - /// Deletes a log item - /// - /// Log item - public void DeleteLog(Log log) - { - } - - /// - /// Clears a log - /// - public void ClearLog() - { - } - - public void ClearLog(DateTime toUtc, LogLevel logLevel) - { - } - - /// - /// Gets all log items - /// - /// Log item creation from; null to load all records - /// Log item creation to; null to load all records - /// Message - /// Log level; null to load all records - /// Page index - /// Page size - /// Log item collection - public IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, - string message, LogLevel? logLevel, int pageIndex, int pageSize, int minFrequency) - { - return new PagedList(new List(), pageIndex, pageSize); - } - - /// - /// Gets a log item - /// - /// Log item identifier - /// Log item - public Log GetLogById(int logId) - { - return null; - } - - /// - /// Get log items by identifiers - /// - /// Log item identifiers - /// Log items - public virtual IList GetLogByIds(int[] logIds) - { - return new List(); - } - - public void InsertLog(LogContext context) + public void Log(LogLevel level, Exception exception, string message, object[] args) { } + } - /// - /// Inserts a log item - /// - /// Log level - /// The short message - /// The full message - /// The customer to associate log record with - /// A log item - public void InsertLog(LogLevel logLevel, string shortMessage, string fullMessage = "", Customer customer = null) - { - } - - public void Flush() - { - } + public class NullLoggerFactory : ILoggerFactory + { + public ILogger GetLogger(Type type) => NullLogger.Instance; + public ILogger GetLogger(string name) => NullLogger.Instance; + public void FlushAll() { } } } diff --git a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs index 593a39ddb0..10da774027 100644 --- a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Logging; @@ -20,7 +20,7 @@ public TraceLogger() : this(CommonHelper.MapPath("~/App_Data/SmartStore.log")) public TraceLogger(string fileName) { - Guard.ArgumentNotEmpty(() => fileName); + Guard.NotEmpty(fileName, nameof(fileName)); _traceSource = new TraceSource("SmartStore"); _traceSource.Switch = new SourceSwitch("LogSwitch", "Error"); @@ -60,66 +60,28 @@ public TraceLogger(string fileName) _traceSource.Switch.Level = SourceLevels.All; } - public bool IsEnabled(LogLevel level) + public bool IsEnabledFor(LogLevel level) { return true; } - public void DeleteLog(Log log) + public void Log(LogLevel level, Exception exception, string message, object[] args) { - // not supported - } - - public void ClearLog() - { - // not supported - } - - public void ClearLog(DateTime toUtc, LogLevel logLevel) - { - // not supported - } - - public IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, string message, LogLevel? logLevel, int pageIndex, int pageSize, int minFrequency) - { - // not supported - return null; - } + var type = LogLevelToEventType(level); - public Log GetLogById(int logId) - { - // not supported - return null; - } - - public IList GetLogByIds(int[] logIds) - { - // not supported - return null; - } - - public void InsertLog(LogContext context) - { - var type = LogLevelToEventType(context.LogLevel); - var msg = context.ShortMessage.Grow(context.FullMessage, Environment.NewLine); - - if (msg.HasValue()) + if (exception != null && !exception.IsFatal()) { - _traceSource.TraceEvent(type, (int)type, "{0}: {1}".FormatCurrent(type.ToString().ToUpper(), msg)); + message = message.Grow(exception.ToString(), Environment.NewLine); } - } - public void InsertLog(LogLevel logLevel, string shortMessage, string fullMessage = "", Customer customer = null) - { - var context = new LogContext + if (message.HasValue()) { - LogLevel = logLevel, - ShortMessage = shortMessage, - FullMessage = fullMessage, - Customer = customer - }; + var msg = args != null && args.Any() + ? message.FormatInvariant(args) + : message; - InsertLog(context); + _traceSource.TraceEvent(type, (int)type, "{0}: {1}".FormatCurrent(type.ToString().ToUpper(), msg)); + } } private TraceEventType LogLevelToEventType(LogLevel level) diff --git a/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs new file mode 100644 index 0000000000..6bd1f49e02 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Web; +using log4net; +using log4net.Core; +using log4net.Util; +using SmartStore.Core.Data; +using SmartStore.Core.Infrastructure; +using SmartStore.Utilities; + +namespace SmartStore.Core.Logging +{ + [DebuggerDisplay("Logger: {Name}")] + public class Log4netLogger : ILogger + { + private static readonly Type _declaringType = typeof(Log4netLogger); + private static readonly ContextState> _state; + + private readonly log4net.Core.ILogger _logger; + + static Log4netLogger() + { + _state = new ContextState>("Log4netLogger.LogicalThreadContext", () => new Dictionary(StringComparer.OrdinalIgnoreCase)); + } + + public Log4netLogger(log4net.Core.ILogger logger) + { + Guard.NotNull(logger, nameof(logger)); + + _logger = logger; + } + + public string Name + { + get { return _logger.Name; } + } + + public bool IsEnabledFor(LogLevel level) + { + return _logger.IsEnabledFor(ConvertLevel(level)); + } + + public void Log(LogLevel level, Exception exception, string message, object[] args) + { + var logLevel = ConvertLevel(level); + + if (message == null && exception != null) + { + message = exception.Message; + } + + object messageObj = message; + + if (args != null && args.Length > 0) + { + messageObj = new SystemStringFormat(CultureInfo.CurrentUICulture, message, args); + } + + var loggingEvent = new LoggingEvent( + _declaringType, + _logger.Repository, + _logger.Name, + logLevel, + messageObj, + exception); + + loggingEvent.Properties["LevelId"] = (int)level; + TryAddExtendedThreadInfo(loggingEvent); + + _logger.Log(loggingEvent); + } + + protected internal void TryAddExtendedThreadInfo(LoggingEvent loggingEvent) + { + var isAppInitialized = EngineContext.Current.IsFullyInitialized; + + // 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; + loggingEvent.Properties["Referrer"] = DBNull.Value; + loggingEvent.Properties["HttpMethod"] = DBNull.Value; + loggingEvent.Properties["Ip"] = DBNull.Value; + + return; + } + + var props = _state.GetState(); + + // Load the log4net thread with additional properties if they are available + var threadInfoMissing = !props.ContainsKey("sm:ThreadInfoAdded"); + + if (threadInfoMissing) + { + using (new ActionDisposable(() => props["sm:ThreadInfoAdded"] = true)) + { + if (DataSettings.DatabaseIsInstalled() && isAppInitialized) + { + var container = EngineContext.Current.ContainerManager; + + // CustomerId + if (container.TryResolve(null, out IWorkContext workContext)) + { + try + { + props["CustomerId"] = workContext.CurrentCustomer.Id; + } + catch + { + props["CustomerId"] = DBNull.Value; + } + } + + + // Url & stuff + if (container.TryResolve(null, out IWebHelper webHelper)) + { + try + { + props["Url"] = webHelper.GetThisPageUrl(true); + props["Referrer"] = webHelper.GetUrlReferrer(); + props["HttpMethod"] = httpRequest?.HttpMethod; + props["Ip"] = webHelper.GetCurrentIpAddress(); + } + catch { } + } + } + } + } + + loggingEvent.Properties["CustomerId"] = props.Get("CustomerId") ?? DBNull.Value; + loggingEvent.Properties["Url"] = props.Get("Url") ?? DBNull.Value; + loggingEvent.Properties["Referrer"] = props.Get("Referrer") ?? DBNull.Value; + loggingEvent.Properties["HttpMethod"] = props.Get("HttpMethod") ?? DBNull.Value; + loggingEvent.Properties["Ip"] = props.Get("Ip") ?? DBNull.Value; + } + + private static Level ConvertLevel(LogLevel level) + { + switch (level) + { + case LogLevel.Information: + return Level.Info; + case LogLevel.Warning: + return Level.Warn; + case LogLevel.Error: + return Level.Error; + case LogLevel.Fatal: + return Level.Fatal; + default: + return Level.Debug; + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLoggerFactory.cs b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLoggerFactory.cs new file mode 100644 index 0000000000..6b6f26299d --- /dev/null +++ b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLoggerFactory.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Concurrent; +using System.Data; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Web.Hosting; +using log4net; +using log4net.Appender; +using log4net.Config; +using log4net.Core; +using log4net.Repository; +using log4net.Util; +using SmartStore.Core.Data; +using SmartStore.Utilities; + +namespace SmartStore.Core.Logging +{ + public class Log4netLoggerFactory : ILoggerFactory, IRegisteredObject + { + private readonly ConcurrentDictionary _loggerCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + public Log4netLoggerFactory() + { + if (HostingEnvironment.IsHosted) + { + var configFile = GetConfigFile(CommonHelper.GetAppSetting("log4net.Config", @"Config\log4net.config")); + + XmlConfigurator.ConfigureAndWatch(configFile); + + var repository = LogManager.GetRepository(); + repository.ConfigurationChanged += OnConfigurationChanged; + TryConfigureDbAppender(repository); + + HostingEnvironment.RegisterObject(this); + } + } + + private void OnConfigurationChanged(object sender, EventArgs e) + { + _loggerCache.Clear(); + TryConfigureDbAppender(sender as ILoggerRepository); + } + + private void TryConfigureDbAppender(ILoggerRepository repository) + { + if (repository == null || !DataSettings.DatabaseIsInstalled()) + return; + + var adoNetAppenders = repository.GetAppenders().OfType().Where(x => x.Name == "database").ToList(); + foreach (var appender in adoNetAppenders) + { + appender.ConnectionString = DataSettings.Current.DataConnectionString; + appender.ConnectionType = DataSettings.Current.DataConnectionType; + appender.ActivateOptions(); + } + } + + private static FileInfo GetConfigFile(string fileName) + { + FileInfo result; + + if (Path.IsPathRooted(fileName)) + { + result = new FileInfo(fileName); + } + else + { + string baseDirectory = AppDomain.CurrentDomain.BaseDirectory; + result = new FileInfo(Path.Combine(baseDirectory, fileName)); + } + + return result; + } + + public ILogger GetLogger(Type type) + { + Guard.NotNull(type, nameof(type)); + + return GetLogger(type.FullName); + } + + public ILogger GetLogger(string name) + { + Guard.NotEmpty(name, nameof(name)); + + var logger = _loggerCache.GetOrAdd(name, key => new Log4netLogger(LogManager.GetLogger(name).Logger)); + return logger; + } + + public void FlushAll() + { + var bufferingAppenders = LogManager.GetRepository().GetAppenders().OfType(); + foreach (var appender in bufferingAppenders) + { + appender.Flush(); + } + } + + + #region IRegisteredObject + + public void Stop(bool immediate) + { + RemoveEmptyLogFiles(); + HostingEnvironment.UnregisterObject(this); + } + + internal static void RemoveEmptyLogFiles() + { + var fileAppenders = LogManager.GetRepository()?.GetAppenders()?.OfType(); + if (fileAppenders != null) + { + foreach (var appender in fileAppenders) + { + // Delete log file if it's empty + var logFile = new FileInfo(appender.File); + if (logFile.Exists && logFile.Length <= 0) + { + logFile.Delete(); + } + } + } + } + + #endregion + } + + //public class DbAppender : AdoNetAppender + //{ + // protected override void SendBuffer(IDbTransaction dbTran, LoggingEvent[] events) + // { + // try + // { + // base.SendBuffer(dbTran, events); + // } + // catch (Exception ex) + // { + // Debug.WriteLine(ex.Message); + // } + // } + //} +} diff --git a/src/Libraries/SmartStore.Core/Logging/log4net/SmartFileAppender.cs b/src/Libraries/SmartStore.Core/Logging/log4net/SmartFileAppender.cs new file mode 100644 index 0000000000..e27eab6420 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Logging/log4net/SmartFileAppender.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using log4net.Appender; +using log4net.Util; + +namespace SmartStore.Core.Logging +{ + public class SmartFileAppender : RollingFileAppender + { + /// + /// Dictionary of already known suffixes (based on previous attempts) for a given filename. + /// + private static readonly Dictionary _suffixes = new Dictionary(); + + /// + /// The number of suffix attempts that will be made on each OpenFile method call. + /// + private const int Retries = 50; + + /// + /// Maximum number of suffixes recorded before a cleanup happens to recycle memory. + /// + private const int MaxSuffixes = 100; + + /// + /// Opens the log file adding an incremental suffix to the filename if required due to an opening failure (usually, locking). + /// + /// The filename as specified in the configuration file. + /// Boolean flag indicating weather the log file should be appended if it already exists. + protected override void OpenFile(string fileName, bool append) + { + lock (this) + { + bool fileOpened = false; + string completeFilename = GetNextOutputFileName(fileName); + string currentFilename = fileName; + + if (_suffixes.Count > MaxSuffixes) + { + _suffixes.Clear(); + } + + if (!_suffixes.ContainsKey(completeFilename)) + { + _suffixes[completeFilename] = 0; + } + + int newSuffix = _suffixes[completeFilename]; + + for (int i = 1; !fileOpened && i <= Retries; i++) + { + try + { + if (newSuffix > 0) + { + currentFilename = string.Format("{0}-{1}", fileName, newSuffix); + } + + BaseOpenFile(currentFilename, append); + + fileOpened = true; + } + catch + { + newSuffix = _suffixes[completeFilename] + i; + + LogLog.Error(typeof(SmartFileAppender), string.Format("SmartFileAppender: Failed to open [{0}]. Attempting [{1}-{2}] instead.", fileName, fileName, newSuffix)); + } + } + + _suffixes[completeFilename] = newSuffix; + } + } + + /// + /// Calls the base class OpenFile method. Allows this method to be mocked. + /// + /// The filename as specified in the configuration file. + /// Boolean flag indicating weather the log file should be appended if it already exists. + protected virtual void BaseOpenFile(string fileName, bool append) + { + base.OpenFile(fileName, append); + } + } +} 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/NuGet/ExtensionReferenceRepository.cs b/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs index f5ca4be0fe..8a06699b59 100644 --- a/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs +++ b/src/Libraries/SmartStore.Core/Packaging/NuGet/ExtensionReferenceRepository.cs @@ -13,8 +13,8 @@ internal abstract class ExtensionReferenceRepository : PackageRepositoryBase public ExtensionReferenceRepository(IProjectSystem project, IPackageRepository sourceRepository) { - Guard.ArgumentNotNull(() => project); - Guard.ArgumentNotNull(() => sourceRepository); + Guard.NotNull(project, nameof(project)); + Guard.NotNull(sourceRepository, nameof(sourceRepository)); Project = project; SourceRepository = sourceRepository; diff --git a/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs b/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs index 325ad372c6..f2c4c8a65f 100644 --- a/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs +++ b/src/Libraries/SmartStore.Core/Packaging/NuGet/NugetLogger.cs @@ -19,16 +19,16 @@ public void Log(MessageLevel level, string message, params object[] args) switch (level) { case MessageLevel.Debug: - //_logger.Debug(String.Format(message, args)); + _logger.Debug(String.Format(message, args)); break; case MessageLevel.Error: _logger.Error(String.Format(message, args)); break; case MessageLevel.Info: - _logger.Information(String.Format(message, args)); + _logger.Info(String.Format(message, args)); break; case MessageLevel.Warning: - _logger.Warning(String.Format(message, args)); + _logger.Warn(String.Format(message, args)); break; } } diff --git a/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs b/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs index ee78bbc8fd..c156852da3 100644 --- a/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs +++ b/src/Libraries/SmartStore.Core/Packaging/PackageBuilder.cs @@ -6,22 +6,18 @@ using SmartStore.Core.Themes; using NuGet; using NuGetPackageBuilder = NuGet.PackageBuilder; -using System.Xml.Linq; -using SmartStore.Core.IO.WebSite; +using SmartStore.Core.IO; using System.Runtime.Versioning; -using System.Net.Mime; -using SmartStore.Core.IO.VirtualPath; namespace SmartStore.Core.Packaging { - public class PackageBuilder : IPackageBuilder { - private readonly IWebSiteFolder _webSiteFolder; + private readonly IApplicationEnvironment _env; - public PackageBuilder(IWebSiteFolder webSiteFolder) + public PackageBuilder(IApplicationEnvironment env) { - this._webSiteFolder = webSiteFolder; + this._env = env; } private static readonly string[] _ignoredThemeExtensions = new[] { @@ -56,7 +52,7 @@ private Stream BuildPackage(ExtensionDescriptor extensionDescriptor) BeginPackage(context); try { - EstablishPaths(context, _webSiteFolder, extensionDescriptor.Id, extensionDescriptor.ExtensionType); + EstablishPaths(context, _env.WebRootFolder, extensionDescriptor.Id, extensionDescriptor.ExtensionType); SetCoreProperties(context, extensionDescriptor); EmbedFiles(context); } @@ -98,17 +94,17 @@ private static void SetCoreProperties(BuildContext context, ExtensionDescriptor } } - private static void EstablishPaths(BuildContext context, IWebSiteFolder webSiteFolder, string extensionName, string extensionType = "Plugin") + private static void EstablishPaths(BuildContext context, IVirtualFolder webRootFolder, string extensionName, string extensionType = "Plugin") { - context.SourceFolder = webSiteFolder; + context.SourceFolder = webRootFolder; if (extensionType.IsCaseInsensitiveEqual("theme")) { - context.SourcePath = "~/Themes/" + extensionName + "/"; + context.SourcePath = "Themes/" + extensionName + "/"; context.TargetPath = "\\Content\\Themes\\" + extensionName + "\\"; } else { - context.SourcePath = "~/Plugins/" + extensionName + "/"; + context.SourcePath = "Plugins/" + extensionName + "/"; context.TargetPath = "\\Content\\Plugins\\" + extensionName + "\\"; } } @@ -116,17 +112,17 @@ private static void EstablishPaths(BuildContext context, IWebSiteFolder webSiteF private static void EmbedFiles(BuildContext context) { var basePath = context.SourcePath; - foreach (var virtualPath in context.SourceFolder.ListFiles(context.SourcePath, true)) + foreach (var path in context.SourceFolder.ListFiles(context.SourcePath, true)) { // skip ignores files - if (IgnoreFile(virtualPath)) + if (IgnoreFile(path)) { continue; } // full virtual path given but we need the relative path so it can be put into // the package that way (the package itself is the logical base path). // Get it by stripping the basePath off including the slash. - var relativePath = virtualPath.Replace(basePath, ""); + var relativePath = path.Replace(basePath, ""); EmbedVirtualFile(context, relativePath); } } @@ -148,7 +144,7 @@ private class BuildContext public Stream Stream { get; set; } public NuGetPackageBuilder Builder { get; set; } - public IWebSiteFolder SourceFolder { get; set; } + public IVirtualFolder SourceFolder { get; set; } public string SourcePath { get; set; } public string TargetPath { get; set; } } @@ -159,14 +155,14 @@ private class BuildContext private class VirtualPackageFile : IPackageFile { - private readonly IWebSiteFolder _webSiteFolder; - private readonly string _virtualPath; + private readonly IVirtualFolder _webRootFolder; + private readonly string _relativePath; private readonly string _packagePath; - public VirtualPackageFile(IWebSiteFolder webSiteFolder, string virtualPath, string packagePath) + public VirtualPackageFile(IVirtualFolder webRootFolder, string relativePath, string packagePath) { - _webSiteFolder = webSiteFolder; - _virtualPath = virtualPath; + _webRootFolder = webRootFolder; + _relativePath = relativePath; _packagePath = packagePath; } @@ -174,7 +170,7 @@ public VirtualPackageFile(IWebSiteFolder webSiteFolder, string virtualPath, stri public Stream GetStream() { var stream = new MemoryStream(); - _webSiteFolder.CopyFileTo(_virtualPath, stream); + _webRootFolder.CopyFile(_relativePath, stream); stream.Seek(0, SeekOrigin.Begin); return stream; } @@ -201,7 +197,5 @@ public IEnumerable SupportedFrameworks } #endregion - } - } diff --git a/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs b/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs index 5a0664ef51..935b00e542 100644 --- a/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs +++ b/src/Libraries/SmartStore.Core/Packaging/PackageInstaller.cs @@ -9,13 +9,12 @@ using Log = SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Core.Themes; -using SmartStore.Core.IO.VirtualPath; +using SmartStore.Core.IO; using SmartStore.Core; using SmartStore.Core.Localization; namespace SmartStore.Core.Packaging { - public class PackageInstaller : IPackageInstaller { private readonly IVirtualPathProvider _virtualPathProvider; @@ -61,7 +60,7 @@ public PackageInstaller( public PackageInfo Install(Stream packageStream, string location, string applicationPath) { - Guard.ArgumentNotNull(() => packageStream); + Guard.NotNull(packageStream, nameof(packageStream)); IPackage package; try @@ -89,7 +88,6 @@ public PackageInfo Install(Stream packageStream, string location, string applica /// An instance of type protected PackageInfo InstallPackage(IPackage package, IPackageRepository packageRepository, string location, string applicationPath) { - bool previousInstalled; // 1. See if extension was previous installed and backup its folder if so @@ -192,7 +190,6 @@ protected PackageInfo ExecuteInstall(IPackage package, IPackageRepository packag public void Uninstall(string packageId, string applicationFolder) { - string extensionFullPath = string.Empty; if (packageId.StartsWith(PackagingUtils.GetExtensionPrefix("Theme"))) diff --git a/src/Libraries/SmartStore.Core/Packaging/PackageManager.cs b/src/Libraries/SmartStore.Core/Packaging/PackageManager.cs index 2479a6988e..2d1285007f 100644 --- a/src/Libraries/SmartStore.Core/Packaging/PackageManager.cs +++ b/src/Libraries/SmartStore.Core/Packaging/PackageManager.cs @@ -87,7 +87,7 @@ public PackagingResult BuildThemePackage(string themeName) } return new PackagingResult { - ExtensionType = "Plugin", + ExtensionType = "Theme", PackageName = themeManifest.ThemeName, PackageVersion = themeManifest.Version, PackageStream = _packageBuilder.BuildPackage(themeManifest) 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 f54398153e..a645c43d9a 100644 --- a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs +++ b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs @@ -1,24 +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"; @@ -91,7 +86,7 @@ internal bool TryUpdateFromPackage() } catch (Exception ex) { - _logger.Error("An error occured while updating the application: {0}".FormatCurrent(ex.Message), ex); + _logger.Error(ex, "An error occured while updating the application"); return false; } } @@ -124,7 +119,7 @@ private IPackage FindPackage(bool createLogger, out string path) if (createLogger) { _logger = CreateLogger(package); - _logger.Information("Found update package '{0}'".FormatInvariant(package.GetFullName())); + _logger.Info("Found update package '{0}'".FormatInvariant(package.GetFullName())); } return package; } @@ -168,7 +163,7 @@ private void Backup() if (localTempPath == null) { var exception = new SmartException("Too many backups in '{0}'.".FormatInvariant(tempPath)); - _logger.Error(exception.Message, exception); + _logger.Error(exception); throw exception; } @@ -176,7 +171,7 @@ private void Backup() var folderUpdater = new FolderUpdater(_logger); folderUpdater.Backup(source, backupFolder, "App_Data", "Media"); - _logger.Information("Backup successfully created in folder '{0}'.".FormatInvariant(localTempPath)); + _logger.Info("Backup successfully created in folder '{0}'.".FormatInvariant(localTempPath)); } private PackageInfo ExecuteUpdate(IPackage package) @@ -208,36 +203,123 @@ private PackageInfo ExecuteUpdate(IPackage package) Path = appPath }; - _logger.Information("Update '{0}' successfully executed.".FormatInvariant(info.Name)); + _logger.Info("Update '{0}' successfully executed.".FormatInvariant(info.Name)); return info; } #endregion - #region Migrations internal void ExecuteMigrations() { + TryMigrateDefaultTenant(); + if (!DataSettings.DatabaseIsInstalled()) return; 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)) + 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.Save(); + } + + private bool TryMigrateDefaultTenant() + { + // We introduced basic multi-tenancy in V3 [...] + + if (!IsPreTenancyVersion()) { - // we introduced app migrations in V2.1. So any version prior 2.1 - // has to perform the initial migration - MigrateInitial(); + return false; } - DataSettings.Current.AppVersion = currentVersion; - DataSettings.Current.Save(); + var tenantDir = Directory.CreateDirectory(CommonHelper.MapPath("~/App_Data/Tenants/Default")); + var tenantTempDir = tenantDir.CreateSubdirectory("_temp"); + + var appDataDir = CommonHelper.MapPath("~/App_Data"); + + // Move Settings.txt + File.Move(Path.Combine(appDataDir, "Settings.txt"), Path.Combine(tenantDir.FullName, "Settings.txt")); + + // Move InstalledPlugins.txt + File.Move(Path.Combine(appDataDir, "InstalledPlugins.txt"), Path.Combine(tenantDir.FullName, "InstalledPlugins.txt")); + + // Move SmartStore.db.sdf + var path = Path.Combine(appDataDir, "SmartStore.db.sdf"); + if (File.Exists(path)) + { + File.Move(path, Path.Combine(tenantDir.FullName, "SmartStore.db.sdf")); + } + + Func moveTenantFolder = (sourceFolder, targetFolder) => + { + var sourcePath = Path.Combine(appDataDir, sourceFolder); + + if (Directory.Exists(sourcePath)) + { + Directory.Move(sourcePath, Path.Combine(tenantDir.FullName, targetFolder ?? sourceFolder)); + return true; + } + + return false; + }; + + // Move tenant specific Folders + moveTenantFolder("ImportProfiles", null); + moveTenantFolder("ExportProfiles", null); + moveTenantFolder("Indexing", null); + moveTenantFolder("Lucene", null); + moveTenantFolder("_temp\\BizBackups", null); + moveTenantFolder("_temp\\ShopConnector", null); + + // Move all media files and folders to new subfolder "Default" + var mediaInfos = (new DirectoryInfo(CommonHelper.MapPath("~/Media"))).EnumerateFileSystemInfos().Where(x => !x.Name.IsCaseInsensitiveEqual("Default")); + var mediaFiles = mediaInfos.OfType(); + var mediaDirs = mediaInfos.OfType().ToArray(); + var tenantMediaDir = new DirectoryInfo(CommonHelper.MapPath("~/Media/Default")); + if (!tenantMediaDir.Exists) + { + tenantMediaDir.Create(); + } + + foreach (var dir in mediaDirs) + { + dir.MoveTo(Path.Combine(tenantMediaDir.FullName, dir.Name)); + } + + foreach (var file in mediaFiles) + { + file.MoveTo(Path.Combine(tenantMediaDir.FullName, file.Name)); + } + + return true; + } + + private bool IsPreTenancyVersion() + { + var appDataDir = CommonHelper.MapPath("~/App_Data"); + + return File.Exists(Path.Combine(appDataDir, "Settings.txt")) + && File.Exists(Path.Combine(appDataDir, "InstalledPlugins.txt")) + && !Directory.Exists(Path.Combine(appDataDir, "Tenants\\Default")); } private void MigrateInitial() @@ -334,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.cs b/src/Libraries/SmartStore.Core/PagedList.cs index 4f4689496e..87af23cda8 100644 --- a/src/Libraries/SmartStore.Core/PagedList.cs +++ b/src/Libraries/SmartStore.Core/PagedList.cs @@ -1,12 +1,12 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Linq; namespace SmartStore.Core { public abstract class PagedListBase : IPageable { - protected PagedListBase() { this.PageIndex = 0; @@ -36,7 +36,7 @@ public void LoadPagedList(IPagedList pagedList) public void Init(IPageable pageable) { - Guard.ArgumentNotNull(pageable, "pageable"); + Guard.NotNull(pageable, "pageable"); this.PageIndex = pageable.PageIndex; this.PageSize = pageable.PageSize; @@ -141,7 +141,6 @@ public virtual IEnumerator GetEnumerator() { return Enumerable.Empty().GetEnumerator(); } - } public class PagedList : PagedListBase @@ -150,6 +149,16 @@ public PagedList(int pageIndex, int pageSize, int totalItemsCount) : base(pageIndex, pageSize, totalItemsCount) { } - } + + public static PagedList Create(IEnumerable source, int pageIndex, int pageSize) + { + return new PagedList(source, pageIndex, pageSize); + } + + public static PagedList Create(IEnumerable source, int pageIndex, int pageSize, int totalCount) + { + return new PagedList(source, pageIndex, pageSize, totalCount); + } + } } diff --git a/src/Libraries/SmartStore.Core/PagedList`T.cs b/src/Libraries/SmartStore.Core/PagedList`T.cs index af5039d810..0fa22d90e1 100644 --- a/src/Libraries/SmartStore.Core/PagedList`T.cs +++ b/src/Libraries/SmartStore.Core/PagedList`T.cs @@ -3,102 +3,170 @@ using System.Linq; using System.Data.Entity; using System.Data.Entity.Infrastructure; +using System.Collections; +using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace SmartStore.Core { - /// - /// Paged list - /// - /// T - public class PagedList : List, IPagedList + public class PagedList : IPagedList, IReadOnlyList, IReadOnlyCollection { - /// - /// Ctor - /// - /// source - /// Page index - /// Page size - public PagedList(IQueryable source, int pageIndex, int pageSize) + private bool _isEfQuery; + private bool _queryIsPagedAlready; + private int? _totalCount; + + private List _list; + + /// The 0-based page index + public PagedList(IEnumerable source, int pageIndex, int pageSize) + { + Guard.NotNull(source, "source"); + + Init(source.AsQueryable(), pageIndex, pageSize, null); + } + + /// The 0-based page index + public PagedList(IEnumerable source, int pageIndex, int pageSize, int totalCount) { - Guard.ArgumentNotNull(source, "source"); + Guard.NotNull(source, "source"); + + Init(source.AsQueryable(), pageIndex, pageSize, totalCount); + } + + private void Init(IQueryable source, int pageIndex, int pageSize, int? totalCount) + { + Guard.NotNull(source, nameof(source)); + Guard.PagingArgsValid(pageIndex, pageSize, "pageIndex", "pageSize"); + + SourceQuery = source; + PageIndex = pageIndex; + PageSize = pageSize; + + _totalCount = totalCount; + _queryIsPagedAlready = totalCount.HasValue; + _isEfQuery = source.Provider is IDbAsyncQueryProvider; + } + + private void EnsureIsLoaded() + { + if (_list == null) + { + if (_totalCount == null) + { + _totalCount = SourceQuery.Count(); + } + + if (_queryIsPagedAlready) + { + _list = SourceQuery.ToList(); + } + else + { + _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 { get; private set; } + + public IPagedList AlterQuery(Func, IQueryable> alterer) + { + var result = alterer?.Invoke(SourceQuery); + SourceQuery = result ?? throw new InvalidOperationException("The '{0}' delegate must not return NULL.".FormatInvariant(nameof(alterer))); + + return this; + } - if (pageIndex == 0 && pageSize == int.MaxValue) + public IQueryable ApplyPaging(IQueryable query) + { + if (PageIndex == 0 && PageSize == int.MaxValue) { - // avoid unnecessary SQL - Init(source, pageIndex, pageSize, source.Count()); + // Paging unnecessary + return query; } else { - if (source.Provider is IDbAsyncQueryProvider) + var skip = PageIndex * PageSize; + if (_isEfQuery) { - // the Lambda overloads for Skip() and Take() let EF use cached query plans, thus slightly increasing performance. - var skip = pageIndex * pageSize; - Init(source.Skip(() => skip).Take(() => pageSize), pageIndex, pageSize, source.Count()); + return query.Skip(() => skip).Take(() => PageSize); } else { - Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count()); + return query.Skip(skip).Take(PageSize); } - } - } + } + } - /// - /// Ctor - /// - /// source - /// Page index - /// Page size - public PagedList(IList source, int pageIndex, int pageSize) - { - Guard.ArgumentNotNull(source, "source"); + public IPagedList Load(bool force = false) + { + // Returns instance for chaining. + if (force && _list != null) + { + _list.Clear(); + _list = null; + } - Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count); - } - - /// - /// Ctor - /// - /// source - /// Page index - /// Page size - /// Total count - public PagedList(IEnumerable source, int pageIndex, int pageSize, int totalCount) - { - Guard.ArgumentNotNull(source, "source"); - Init(source, pageIndex, pageSize, totalCount); - } + EnsureIsLoaded(); - private void Init(IEnumerable source, int pageIndex, int pageSize, int totalCount) - { - Guard.PagingArgsValid(pageIndex, pageSize,"pageIndex", "pageSize"); + return this; + } - this.PageIndex = pageIndex; - this.PageSize = pageSize; - this.TotalCount = totalCount; + public async Task> LoadAsync(bool force = false) + { + // Returns instance for chaining. + if (force && _list != null) + { + _list.Clear(); + _list = null; + } - this.AddRange(source); - } + await EnsureIsLoadedAsync(); - #region IPageable Members + return this; + } - // codehint: sm-add/edit + public int PageIndex { get; set; } - public int PageIndex - { - get; - set; - } + public int PageSize { get; set; } - public int PageSize + public int TotalCount { - get; - set; - } + get + { + if (!_totalCount.HasValue) + { + _totalCount = SourceQuery.Count(); + } - public int TotalCount - { - get; - set; + return _totalCount.Value; + } + set + { + _totalCount = value; + } } public int PageNumber @@ -174,7 +242,122 @@ public bool IsLastPage } } - #endregion + #endregion + + #region IList Members + + public void Add(T item) + { + EnsureIsLoaded(); + _list.Add(item); + } + + public void Clear() + { + if (_list != null) + { + _list.Clear(); + _list = null; + } + } + + public bool Contains(T item) + { + EnsureIsLoaded(); + return _list.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + EnsureIsLoaded(); + _list.CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + if (_list != null) + { + return _list.Remove(item); + } + + return false; + } + + public int Count + { + get + { + EnsureIsLoaded(); + return _list.Count; + } + } + + public bool IsReadOnly + { + get { return false; } + } + + public int IndexOf(T item) + { + EnsureIsLoaded(); + return _list.IndexOf(item); + } + + public void Insert(int index, T item) + { + EnsureIsLoaded(); + _list.Insert(index, item); + } + + public void RemoveAt(int index) + { + if (_list != null) + { + _list.RemoveAt(index); + } + } + + public T this[int index] + { + get + { + EnsureIsLoaded(); + return _list[index]; + } + set + { + EnsureIsLoaded(); + _list[index] = value; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + EnsureIsLoaded(); + return _list.GetEnumerator(); + } + + #endregion + + #region Utils + + public void AddRange(IEnumerable collection) + { + EnsureIsLoaded(); + _list.AddRange(collection); + } + + public ReadOnlyCollection AsReadOnly() + { + EnsureIsLoaded(); + return _list.AsReadOnly(); + } - } + #endregion + } } diff --git a/src/Libraries/SmartStore.Core/Plugins/LoadPluginResult.cs b/src/Libraries/SmartStore.Core/Plugins/LoadPluginResult.cs index 72bc94b446..2e51056df1 100644 --- a/src/Libraries/SmartStore.Core/Plugins/LoadPluginResult.cs +++ b/src/Libraries/SmartStore.Core/Plugins/LoadPluginResult.cs @@ -6,7 +6,6 @@ namespace SmartStore.Core.Plugins { - public class LoadPluginResult { public FileInfo DescriptionFile { get; set; } @@ -14,5 +13,4 @@ public class LoadPluginResult public bool IsIncompatible { get; set; } public bool Success { get; set; } } - } diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs index 0409454f60..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,23 +108,23 @@ public string BrandImageFileName } /// - /// Plugin type + /// Plugin runtime type /// - public Type PluginType { get; set; } + public Type PluginClrType { get; set; } - /// - /// The assembly that has been shadow copied that is active in the application - /// - public Assembly ReferencedAssembly { get; internal set; } + /// + /// The plugin assembly. + /// + public AssemblyReference Assembly { get; internal set; } = new AssemblyReference(); - /// - /// The original assembly file that a shadow copy was made from it - /// - public FileInfo OriginalAssemblyFile { get; internal set; } + /// + /// List of assemblies files found in the plugin folder, except the main plugin assembly. + /// + 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; } @@ -108,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 /// @@ -157,11 +185,17 @@ public bool IsInKnownGroup public int DisplayOrder { get; set; } /// - /// Gets or sets the value indicating whether plugin is installed + /// Gets or sets a value indicating whether the plugin is installed /// [DataMember] public bool Installed { get; set; } + /// + /// Gets a value indicating whether the plugin is incompatible with the current application version + /// + [DataMember] + public bool Incompatible { get; set; } + /// /// Gets or sets the value indicating whether the plugin is configurable /// @@ -174,41 +208,44 @@ public bool IsInKnownGroup /// /// Gets or sets the root key of string resources. /// - /// Tries to get it from first entry of resource XML file if not specified. In that case the first resource name should not contain a dot if it's not part of the root key. - /// Otherwise you get the wrong root key. + /// + /// Tries to get it from first entry of resource XML file if not specified. + /// In that case the first resource name should not contain a dot if it's not part of the root key. + /// Otherwise you get the wrong root key. + /// public string ResourceRootKey { get { if (_resourceRootKey == null) { _resourceRootKey = ""; + try { - // try to get root-key from first entry of XML file - string localizationDir = Path.Combine(PhysicalPath, "Localization"); + // Try to get root-key from first entry of XML file + var localizationDir = new DirectoryInfo(Path.Combine(PhysicalPath, "Localization")); - if (System.IO.Directory.Exists(localizationDir)) + if (localizationDir.Exists) { - string filePath = System.IO.Directory.EnumerateFiles(localizationDir, "*.xml").FirstOrDefault(); - if (filePath.HasValue()) + var localizationFile = localizationDir.EnumerateFiles("*.xml").FirstOrDefault(); + if (localizationFile != null) { XmlDocument doc = new XmlDocument(); - doc.Load(filePath); - var node = doc.SelectSingleNode(@"//Language/LocaleResource"); - if (node != null) + doc.Load(localizationFile.FullName); + var key = doc.SelectSingleNode(@"//Language/LocaleResource")?.Attributes["Name"]?.InnerText; + if (key.HasValue() && key.Contains('.')) { - string key = node.Attributes["Name"].InnerText; - if (key.HasValue() && key.Contains('.')) - _resourceRootKey = key.Substring(0, key.LastIndexOf('.')); - } + _resourceRootKey = key.Substring(0, key.LastIndexOf('.')); + } } } } - catch (Exception exc) + catch (Exception ex) { - exc.Dump(); + ex.Dump(); } } + return _resourceRootKey; } set { @@ -219,14 +256,16 @@ 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 resolved - instance = EngineContext.Current.ContainerManager.ResolveUnregistered(PluginType); + // Not registered + instance = EngineContext.Current.ContainerManager.ResolveUnregistered(PluginClrType); } + var typedInstance = instance as T; if (typedInstance != null) typedInstance.PluginDescriptor = this; + return typedInstance; } diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs b/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs index a9cea1331e..10e0ce284f 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; +using SmartStore.Core.Data; using SmartStore.Utilities; namespace SmartStore.Core.Plugins @@ -41,28 +42,34 @@ public override int Compare(string x, string y) "Sales", "Design", "Performance", - "Misc" + "B2B", + "Storefront", + "Law" }; public readonly static IComparer KnownGroupComparer = new GroupComparer(); - public readonly static string InstalledPluginsFilePath = CommonHelper.MapPath("~/App_Data/InstalledPlugins.txt"); + public readonly static string InstalledPluginsFilePath; - public static HashSet ParseInstalledPluginsFile(string filePath = null) + static PluginFileParser() + { + InstalledPluginsFilePath = Path.Combine(CommonHelper.MapPath(DataSettings.Current.TenantPath), "InstalledPlugins.txt"); + } + + public static HashSet ParseInstalledPluginsFile(string filePath = null) { filePath = filePath ?? InstalledPluginsFilePath; var lines = new HashSet(StringComparer.OrdinalIgnoreCase); - // read and parse the file + // Read and parse the file if (!File.Exists(filePath)) return lines; var text = File.ReadAllText(filePath); - if (String.IsNullOrEmpty(text)) + if (text.IsEmpty()) + { return lines; - - //Old way of file reading. This leads to unexpected behavior when a user's FTP program transfers these files as ASCII (\r\n becomes \n). - //var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + } using (var reader = new StringReader(text)) { @@ -71,13 +78,15 @@ public static HashSet ParseInstalledPluginsFile(string filePath = null) { if (str.IsEmpty() || lines.Contains(str, StringComparer.CurrentCultureIgnoreCase)) continue; + lines.Add(str.Trim()); } } + return lines; } - public static void SaveInstalledPluginsFile(ICollection pluginSystemNames, string filePath = null) + public static void SaveInstalledPluginsFile(ICollection pluginSystemNames, string filePath = null) { if (pluginSystemNames == null || pluginSystemNames.Count == 0) return; @@ -152,7 +161,7 @@ public static PluginDescriptor ParsePluginDescriptionFile(string filePath) case "SupportedVersions": // compat case "MinAppVersion": { - //parse supported min app version + // Parse supported min app version descriptor.MinAppVersion = value.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .FirstOrDefault() // since V1.2 take the first only diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs b/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs index d911e202e6..4eddd033dd 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs @@ -1,33 +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; @@ -35,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; + } } } @@ -48,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(); } @@ -75,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; @@ -100,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; @@ -119,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 new file mode 100644 index 0000000000..dff9c225b5 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.Cache.cs @@ -0,0 +1,327 @@ +using SmartStore.Core.Data; +using SmartStore.Utilities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Diagnostics; + +namespace SmartStore.Core.Plugins +{ + public partial class PluginManager + { + private static int? _lastPluginsHash; + private static ICollection _lastPluginAssemblies; + + /// + /// This checks if any of our plugins have changed, if so it will go + /// + /// + /// Returns true if there were changes to plugins and a cleanup was required, otherwise false is returned. + /// + private static bool DetectAndCleanStalePlugins(IEnumerable plugins) + { + var currentHash = ComputePluginsHash(plugins); + var lastHash = GetLastPluginsHash(); + + // Check if anything has been changed, or if we are in debug mode then always perform cleanup + if (currentHash != lastHash) + { + Debug.WriteLine("Plugin changes detected in hash"); + + var lastAssemblies = GetLastPluginsAssemblies(); + + // We need to read the old assembly list and clean them out from the shadow copy folder + var staleFiles = lastAssemblies + .Select(x => new FileInfo(Path.Combine(_shadowCopyDir.FullName, x))) + .ToArray(); + + // 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) + { + staleFiles.AsParallel().ForAll(f => TryDeleteStaleFile(f)); + } + else + { + staleFiles.Each(f => TryDeleteStaleFile(f)); + } + + return true; + } + + // No plugin changes found + return false; + } + + /// + /// Loads the hash code of the last loaded plugins from disk + /// + /// + private static int? GetLastPluginsHash() + { + if (_lastPluginsHash == null) + { + var filePath = Path.Combine(CommonHelper.MapPath(DataSettings.Current.TenantPath), "plugins.hash"); + if (!File.Exists(filePath)) + { + _lastPluginsHash = 0; + } + else + { + var hash = File.ReadAllText(filePath, Encoding.UTF8); + _lastPluginsHash = ConvertPluginsHash(hash); + } + } + + if (_lastPluginsHash == 0) + { + return null; + } + + return _lastPluginsHash; + } + + private static void SavePluginsHash(int hashCode) + { + var filePath = Path.Combine(CommonHelper.MapPath(DataSettings.Current.TenantPath), "plugins.hash"); + File.WriteAllText(filePath, hashCode.ToString(CultureInfo.InvariantCulture), Encoding.UTF8); + _lastPluginsHash = null; + } + + /// + /// Loads the file names of the last shadow copied plugin assemblies from disk + /// + /// + private static ICollection GetLastPluginsAssemblies() + { + if (_lastPluginAssemblies == null) + { + var filePath = Path.Combine(CommonHelper.MapPath(DataSettings.Current.TenantPath), "plugins.assemblies"); + + if (!File.Exists(filePath)) + { + _lastPluginAssemblies = new List(); + } + else + { + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + + var file = File.ReadAllText(filePath, Encoding.UTF8); + var sr = new StringReader(file); + while (true) + { + var f = sr.ReadLine(); + if (f != null && f.HasValue()) + { + list.Add(f); + } + else + { + break; + } + } + + _lastPluginAssemblies = list; + } + } + + return _lastPluginAssemblies; + } + + private static void SavePluginsAssemblies(IEnumerable plugins) + { + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var p in plugins) + { + p.ReferencedLocalAssemblies.Each(x => list.Add(x.OriginalFile.Name)); + + if (p.Assembly.OriginalFile != null) + { + list.Add(p.Assembly.OriginalFile.Name); + } + } + + var sb = new StringBuilder(); + foreach (var f in list) + { + sb.AppendLine(f); + } + + var filePath = Path.Combine(CommonHelper.MapPath(DataSettings.Current.TenantPath), "plugins.assemblies"); + File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); + + _lastPluginAssemblies = null; + } + + /// + /// Returns the hash for the passed plugins + /// + /// + private static int ComputePluginsHash(IEnumerable plugins) + { + var hashCombiner = new HashCodeCombiner(); + + // Add each *.dll, Description.txt, web.config to the hash + foreach (var p in plugins) + { + if (p.Assembly.OriginalFile != null) + { + hashCombiner.Add(p.Assembly.OriginalFile); + } + + p.ReferencedLocalAssemblies.Each(x => hashCombiner.Add(x.OriginalFile)); + + hashCombiner.Add(Path.Combine(p.PhysicalPath, "Description.txt")); + hashCombiner.Add(Path.Combine(p.PhysicalPath, "web.config")); + } + + return hashCombiner.CombinedHash; + } + + /// + /// Tries to delete the file. If it doesn't go nicely, we'll force rename it to .delete, if that already exists and we cannot + /// remove the .delete file then we rename to Guid + .delete + /// + /// + /// If it deletes nicely return true, otherwise false + private static bool TryDeleteStaleFile(FileInfo f) + { + Debug.WriteLine("Trying to delete shadow copied file " + f.FullName); + + // This is a special case: people may have been usign the CodeGen folder before so we need to cleanup those + // files too, even if we are no longer using it + if (CleanupDeletePluginFiles(new FileInfo(f.FullName + ".delete"))) + return true; + + try + { + f.Delete(); + return true; + } + catch { } + + // If the file doesn't delete, then create the .delete file for it, hopefully the BuildManager will clean up next time + var newName = GetNewDeleteName(f); + + try + { + File.Move(f.FullName, newName); + } + catch (UnauthorizedAccessException) + { + throw new UnauthorizedAccessException(string.Format("Access to the path '{0}' is denied, ensure that read, write and modify permissions are allowed.", Path.GetDirectoryName(newName))); + } + catch (IOException) + { + Debug.WriteLine(f.FullName + " rename failed, cannot remove stale plugin"); + throw; + } + + // Make it an empty file + try + { + (new StreamWriter(newName)).Close(); + } + catch { } + + Debug.WriteLine("Stale plugin " + f.FullName + " failed to cleanup successfully. A .delete file has been created for it"); + + return false; + } + + /// + /// Returns the .delete file name for a particular file. If just the file name + .delete already exists and is locked, + /// then this returns a Guid + .delete format. + /// + /// + /// + private static string GetNewDeleteName(FileInfo pluginFile) + { + var deleteName = pluginFile.FullName + ".delete"; + + try + { + if (File.Exists(deleteName)) + File.Delete(deleteName); + } + catch + { + Debug.WriteLine("Cannot remove file " + deleteName + ". It is locked, renaming to .delete file with GUID"); + // Cannot delete, so we will need to use a GUID + deleteName = pluginFile.FullName + Guid.NewGuid().ToString("N") + ".delete"; + } + + return deleteName; + } + + /// + /// When given a .delete file, this will attempt to delete the base file that the .delete was created from + /// and then the .delete file itself. if both are removed, then it will return true. + /// + /// + /// + private static bool CleanupDeletePluginFiles(FileInfo f) + { + if (f.Extension != ".delete") + return false; + + var baseFileName = Path.GetDirectoryName(f.FullName) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(f.FullName); + + // NOTE: this is to remove our old GUID named files... we shouldn't have any of these but sometimes things are just locked + var base64Files = f.Directory.GetFiles(Path.GetFileName(baseFileName) + "???????????????????????????????????" + ".delete").ToList(); + Func delFile = x => + { + if (x.IsEmpty()) + return true; + + if (File.Exists(x)) + { + try + { + File.Delete(x); + } + catch + { + return false; + } + } + return true; + }; + + // Always try deleting old guid named files + foreach (var x in base64Files) + { + delFile(x.FullName); + } + + // Try to delete the .dll file + if (!delFile(baseFileName)) + { + return false; + } + + try + { + // Now try to remove the .delete file + f.Delete(); + } + catch { } + + return true; + } + + internal static int ConvertPluginsHash(string val) + { + int outVal; + if (Int32.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out outVal)) + { + return outVal; + } + + return 0; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs index 7fd04123aa..c7c4d6fe4e 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs @@ -1,27 +1,25 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Configuration; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Threading; -using System.Threading.Tasks; using System.Web; using System.Web.Compilation; -using System.Web.Hosting; -using Microsoft.Web.Infrastructure; +using System.Runtime.InteropServices; using Microsoft.Web.Infrastructure.DynamicModuleHelper; -using SmartStore.ComponentModel; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Plugins; 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 +// Contributor: Umbraco (http://www.umbraco.com). Thanks a lot! +// SEE THIS POST for full details of what this does //http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx [assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")] @@ -29,24 +27,26 @@ namespace SmartStore.Core.Plugins { /// - /// Sets the application up for the plugin referencing + /// Sets the application up for plugin referencing /// - public class PluginManager + public partial class PluginManager { - #region Fields + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetDllDirectory(string lpPathName); - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - private static DirectoryInfo _shadowCopyFolder; - //private static readonly string _installedPluginsFilePath = CommonHelper.MapPath("~/App_Data/InstalledPlugins.txt"); + private static readonly object _lock = new object(); private static readonly string _pluginsPath = "~/Plugins"; - private static readonly string _shadowCopyPath = "~/Plugins/bin"; - private static bool _clearShadowDirectoryOnStartup; + private static DirectoryInfo _shadowCopyDir; private static readonly ConcurrentDictionary _referencedPlugins = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private static HashSet _inactiveAssemblies = new HashSet(); - #endregion - - #region Methods + /// + /// Returns the virtual path of the plugins folder relative to the application + /// + public static string PluginsLocation + { + get { return _pluginsPath; } + } /// /// Returns a collection of all referenced plugin assemblies that have been shadow copied @@ -85,6 +85,12 @@ public static IEnumerable IncompatiblePlugins /// public static void Initialize() { + var isFullTrust = WebHelper.GetTrustLevel() == AspNetHostingPermissionLevel.Unrestricted; + if (!isFullTrust) + { + throw new ApplicationException("SmartStore.NET requires Full Trust mode. Please enable Full Trust for your web site or contact your hosting provider."); + } + using (var updater = new AppUpdater()) { // update from NuGet package, if it exists and is valid @@ -97,223 +103,292 @@ public static void Initialize() updater.ExecuteMigrations(); } - // adding a process-specific environment path (either bin/x86 or bin/amd64) + // adding a process-specific environment path (either bin/x86 or bin/x64) // ensures that unmanaged native dependencies can be resolved successfully. - SetPrivateEnvPath(); - + SetNativeDllPath(); + DynamicModuleUtility.RegisterModule(typeof(AutofacRequestLifetimeHttpModule)); - - using (Locker.GetWriteLock()) - { - // TODO: Add verbose exception handling / raising here since this is happening on app startup and could - // prevent app from starting altogether - var pluginFolderPath = CommonHelper.MapPath(_pluginsPath); - _shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(_shadowCopyPath)); - var incompatiblePlugins = new List(); - _clearShadowDirectoryOnStartup = CommonHelper.GetAppSetting("sm:ClearPluginsShadowDirectoryOnStartup", true); - try - { - Debug.WriteLine("Creating shadow copy folder and querying for dlls"); - //ensure folders are created - Directory.CreateDirectory(pluginFolderPath); - Directory.CreateDirectory(_shadowCopyFolder.FullName); - - // get list of all files in bin - var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories); - if (_clearShadowDirectoryOnStartup) - { - // clear out shadow copied plugins - foreach (var f in binFiles) - { - Debug.WriteLine("Deleting " + f.Name); - try - { - File.Delete(f.FullName); - } - catch (Exception exc) - { - Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc); - } - } - } - - // determine all plugin folders - var pluginPaths = from x in Directory.EnumerateDirectories(pluginFolderPath) - where !x.IsMatch("bin") && !x.IsMatch("_Backup") - select Path.Combine(pluginFolderPath, x); - - var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(); - - // now activate all plugins - foreach (var pluginPath in pluginPaths) + var incompatiblePlugins = (new HashSet(StringComparer.OrdinalIgnoreCase)).AsSynchronized(); + var inactiveAssemblies = _inactiveAssemblies.AsSynchronized(); + var dirty = false; + + var watch = Stopwatch.StartNew(); + + _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); + + // Save names of all deployed assemblies to disk (so we can nuke them later) + SavePluginsAssemblies(_referencedPlugins.Values); + } + + IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); + + ms = watch.ElapsedMilliseconds; + Debug.WriteLine("INIT PLUGINS (Deployment complete). Time elapsed: {0} ms.".FormatCurrentUI(ms)); + + void DeployPlugin(PluginDescriptor p, bool shadowCopy) + { + if (p.Incompatible) + { + // 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) { - var result = LoadPluginFromFolder(pluginPath, installedPluginSystemNames); - if (result != null) - { - if (result.IsIncompatible) - { - incompatiblePlugins.Add(result.Descriptor.SystemName); - } - else if (result.Success) - { - _referencedPlugins[result.Descriptor.SystemName] = result.Descriptor; - } - } + Probe(refAr, p, shadowCopy); } + } - } - catch (Exception ex) - { - var msg = string.Empty; - for (var e = ex; e != null; e = e.InnerException) + // Then copy main plugin assembly + var ar = p.Assembly; + if (ar.Assembly == null) + { + Probe(ar, p, shadowCopy); + if (ar.Assembly != null) { - msg += e.Message + Environment.NewLine; + // Activate (even if uninstalled): Find IPlugin, IPreApplicationStart, IConfigurable etc. + ActivatePlugin(p); } + } + } - var fail = new Exception(msg, ex); - Debug.WriteLine(fail.Message, fail); + void FinalizePlugin(PluginDescriptor p) + { + _referencedPlugins[p.SystemName] = p; - throw fail; - } + if (p.Incompatible) + { + incompatiblePlugins.Add(p.SystemName); + return; + } - IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); - } - } + var firstFailedAssembly = p.ReferencedLocalAssemblies.FirstOrDefault(x => x.ActivationException != null); + if (firstFailedAssembly == null && p.Assembly.ActivationException != null) + { + firstFailedAssembly = p.Assembly; + } + if (firstFailedAssembly != null) + { + Debug.WriteLine(firstFailedAssembly.ActivationException.Message); + p.Incompatible = true; + incompatiblePlugins.Add(p.SystemName); + } + + if ((!p.Installed || firstFailedAssembly != null) && p.Assembly.Assembly != null) + { + inactiveAssemblies.Add(p.Assembly.Assembly); + } + } + } - private static LoadPluginResult LoadPluginFromFolder(string pluginFolderPath, ICollection installedPluginSystemNames) + /// + /// Loads and parses the descriptors of all installed plugins + /// + /// All descriptors + private static IEnumerable LoadPluginDescriptors() { - Guard.ArgumentNotEmpty(() => pluginFolderPath); + // TODO: Add verbose exception handling / raising here since this is happening on app startup and could + // prevent app from starting altogether + + var pluginsDir = new DirectoryInfo(CommonHelper.MapPath(_pluginsPath)); - var folder = new DirectoryInfo(pluginFolderPath); - if (!folder.Exists) + if (!pluginsDir.Exists) { - return null; + pluginsDir.Create(); + return Enumerable.Empty(); } - var descriptionFile = new FileInfo(Path.Combine(pluginFolderPath, "Description.txt")); + // 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 + 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")); if (!descriptionFile.Exists) { return null; } - // load descriptor file (Description.txt) + // Load descriptor file (Description.txt) var descriptor = PluginFileParser.ParsePluginDescriptionFile(descriptionFile.FullName); - // some validation + // Some validation if (descriptor.SystemName.IsEmpty()) { - throw new Exception("The plugin descriptor '{0}' does not define a plugin system name. Try assigning the plugin a unique name and recompile.".FormatInvariant(descriptionFile.FullName)); + throw new SmartException("The plugin descriptor '{0}' does not define a plugin system name. Try assigning the plugin a unique name and recompile.".FormatInvariant(descriptionFile.FullName)); } + if (descriptor.PluginFileName.IsEmpty()) { - throw new Exception("The plugin descriptor '{0}' does not define a plugin assembly file name. Try assigning the plugin a file name and recompile.".FormatInvariant(descriptionFile.FullName)); + 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)); } - var result = new LoadPluginResult - { - DescriptionFile = descriptionFile, - Descriptor = descriptor - }; + descriptor.VirtualPath = _pluginsPath + "/" + descriptor.FolderName; + + // Set 'Installed' property + descriptor.Installed = installedPluginSystemNames.Contains(descriptor.SystemName); - //ensure that version of plugin is valid + // Ensure that version of plugin is valid if (!IsAssumedCompatible(descriptor)) { - result.IsIncompatible = true; - return result; + // Set 'Incompatible' property and return + descriptor.Incompatible = true; + return descriptor; } - if (_referencedPlugins.ContainsKey(descriptor.SystemName)) - { - throw new Exception(string.Format("A plugin with system name '{0}' is already defined", descriptor.SystemName)); - } + var skipDlls = new HashSet(new[] { "log4net.dll" }, StringComparer.OrdinalIgnoreCase); - if (installedPluginSystemNames == null) - { - installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(); - } + // Get list of all DLLs in plugin folders (not in 'bin' or '_Backup'!) + var pluginBinaries = descriptionFile.Directory.EnumerateFiles("*.dll", SearchOption.AllDirectories).ToArray() + .Where(x => IsPackagePluginFolder(x.Directory) && !skipDlls.Contains(x.Name)) + .OrderBy(x => x.Name) + .ToDictionarySafe(x => x.Name, StringComparer.OrdinalIgnoreCase); - // set 'Installed' property - descriptor.Installed = installedPluginSystemNames.Contains(descriptor.SystemName); + // Set 'OriginalAssemblyFile' property + descriptor.Assembly.OriginalFile = pluginBinaries.Get(descriptor.PluginFileName); - try + if (descriptor.Assembly.OriginalFile == null) { - // get list of all DLLs in plugin folders (not in 'bin' or '_Backup'!) - var pluginBinaries = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories) - // just make sure we're not registering shadow copied plugins - .Where(x => IsPackagePluginFolder(x.Directory)) - .ToList(); + throw new SmartException("The main assembly '{0}' for plugin '{1}' could not be found.".FormatInvariant(descriptor.PluginFileName, descriptor.SystemName)); + } - // other plugin description info - var mainPluginFile = pluginBinaries.Where(x => x.Name.IsCaseInsensitiveEqual(descriptor.PluginFileName)).FirstOrDefault(); - descriptor.OriginalAssemblyFile = mainPluginFile; + // Load all other referenced local assemblies now + var otherAssemblyFiles = pluginBinaries + .Where(x => !x.Key.IsCaseInsensitiveEqual(descriptor.PluginFileName)) + .Select(x => x.Value); - // shadow copy main plugin file - descriptor.ReferencedAssembly = Probe(mainPluginFile); + descriptor.ReferencedLocalAssemblies = otherAssemblyFiles.Select(x => new AssemblyReference { OriginalFile = x }).ToArray(); - if (!descriptor.Installed) - { - _inactiveAssemblies.Add(descriptor.ReferencedAssembly); - } + return descriptor; + } - // load all other referenced assemblies now - var otherAssemblies = from x in pluginBinaries - where !x.Name.IsCaseInsensitiveEqual(mainPluginFile.Name) - select x; + private static void ActivatePlugin(PluginDescriptor plugin) + { + // Init plugin type (only one plugin per assembly is allowed) + bool pluginFound = false; + bool preStarterFound = !plugin.Installed; + var exportedTypes = plugin.Assembly.Assembly.GetExportedTypes(); - foreach (var assemblyFile in otherAssemblies) + foreach (var t in exportedTypes) + { + if (typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract) { - if (!IsAlreadyLoaded(assemblyFile)) - { - Probe(assemblyFile); - } + plugin.PluginClrType = t; + plugin.IsConfigurable = typeof(IConfigurable).IsAssignableFrom(t); + pluginFound = true; } - - // init plugin type (only one plugin per assembly is allowed) - var exportedTypes = descriptor.ReferencedAssembly.ExportedTypes; - bool pluginFound = false; - bool preStarterFound = !descriptor.Installed; - foreach (var t in exportedTypes) + else if (plugin.Installed && typeof(IPreApplicationStart).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract && t.HasDefaultConstructor()) { - if (typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract) - { - descriptor.PluginType = t; - descriptor.IsConfigurable = typeof(IConfigurable).IsAssignableFrom(t); - pluginFound = true; - } - else if (descriptor.Installed && typeof(IPreApplicationStart).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract && t.HasDefaultConstructor()) - { - try - { - var preStarter = Activator.CreateInstance(t) as IPreApplicationStart; - preStarter.Start(); - } - catch { } - preStarterFound = true; - } - if (pluginFound && preStarterFound) + try { - break; + var preStarter = Activator.CreateInstance(t) as IPreApplicationStart; + preStarter.Start(); } + catch { } + preStarterFound = true; } - result.Success = true; - } - catch (ReflectionTypeLoadException ex) - { - var msg = string.Empty; - foreach (var e in ex.LoaderExceptions) + if (pluginFound && preStarterFound) { - msg += e.Message + Environment.NewLine; + break; } - - var fail = new Exception(msg, ex); - Debug.WriteLine(fail.Message, fail); - - throw fail; } - - return result; } /// @@ -322,15 +397,16 @@ private static LoadPluginResult LoadPluginFromFolder(string pluginFolderPath, IC /// Plugin system name public static void MarkPluginAsInstalled(string systemName) { - if (String.IsNullOrEmpty(systemName)) - throw new ArgumentNullException("systemName"); + Guard.NotEmpty(systemName, nameof(systemName)); var installedPluginSystemNames = GetInstalledPluginNames(); - bool alreadyMarkedAsInstalled = installedPluginSystemNames.Contains(systemName); - if (!alreadyMarkedAsInstalled) + + bool installed = installedPluginSystemNames.Contains(systemName); + if (!installed) { installedPluginSystemNames.Add(systemName); } + PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames); } @@ -340,14 +416,15 @@ public static void MarkPluginAsInstalled(string systemName) /// Plugin system name public static void MarkPluginAsUninstalled(string systemName) { - Guard.ArgumentNotEmpty(() => systemName); + Guard.NotEmpty(systemName, nameof(systemName)); var installedPluginSystemNames = GetInstalledPluginNames(); - bool alreadyMarkedAsInstalled = installedPluginSystemNames.Contains(systemName); - if (alreadyMarkedAsInstalled) + bool installed = installedPluginSystemNames.Contains(systemName); + if (installed) { installedPluginSystemNames.Remove(systemName); } + PluginFileParser.SaveInstalledPluginsFile(installedPluginSystemNames); } @@ -368,7 +445,7 @@ private static ICollection GetInstalledPluginNames() { using (File.Create(filePath)) { - //we use 'using' to close the file after it's created + // We use 'using' to close the file after it's created } } @@ -389,7 +466,7 @@ private static ICollection GetInstalledPluginNames() /// true when the plugin is assumed to be compatible public static bool IsAssumedCompatible(PluginDescriptor descriptor) { - Guard.ArgumentNotNull(() => descriptor); + Guard.NotNull(descriptor, nameof(descriptor)); return IsAssumedCompatible(descriptor.MinAppVersion); } @@ -407,7 +484,7 @@ public static bool IsAssumedCompatible(PluginDescriptor descriptor) /// true when the extension's version is assumed to be compatible public static bool IsAssumedCompatible(Version minAppVersion) { - Guard.ArgumentNotNull(() => minAppVersion); + Guard.NotNull(minAppVersion, nameof(minAppVersion)); if (SmartStoreVersion.Version == minAppVersion) { @@ -448,7 +525,7 @@ public static bool IsAssumedCompatible(Version minAppVersion) /// true if the plugin exists, false otherwise public static bool PluginExists(string systemName) { - Guard.ArgumentNotEmpty(() => systemName); + Guard.NotEmpty(systemName, nameof(systemName)); return _referencedPlugins.ContainsKey(systemName); } @@ -463,178 +540,157 @@ public static bool IsActivePluginAssembly(Assembly assembly) return !_inactiveAssemblies.Contains(assembly); } - #endregion - - #region Utilities - private static void SetPrivateEnvPath() { - string dir = Environment.Is64BitProcess ? "amd64" : "x86"; - string envPath = String.Concat(Environment.GetEnvironmentVariable("PATH"), ";", Path.Combine(AppDomain.CurrentDomain.RelativeSearchPath, dir)); + string envPath = Environment.GetEnvironmentVariable("PATH"); + + if (Environment.Is64BitProcess) + { + envPath = envPath.EnsureEndsWith(";") + Path.Combine(AppDomain.CurrentDomain.RelativeSearchPath, "amd64"); + envPath = envPath.EnsureEndsWith(";") + Path.Combine(AppDomain.CurrentDomain.RelativeSearchPath, "x64"); + } + else + { + envPath = envPath.EnsureEndsWith(";") + Path.Combine(AppDomain.CurrentDomain.RelativeSearchPath, "x86"); + } + + Environment.SetEnvironmentVariable("PATH", envPath, EnvironmentVariableTarget.Process); } - /// - /// Indicates whether assembly file is already loaded - /// - /// File info - /// Result - private static bool IsAlreadyLoaded(FileInfo fileInfo) - { - //do not compare the full assembly name, just filename - try - { - string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.FullName); - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var a in assemblies) - { - string assemblyName = a.FullName.Split(new[] { ',' }).FirstOrDefault(); - if (fileNameWithoutExt.Equals(assemblyName, StringComparison.InvariantCultureIgnoreCase)) - return true; - } - } - catch (Exception exc) - { - Debug.WriteLine("Cannot validate whether an assembly is already loaded. " + exc); - } - return false; - } + private static void SetNativeDllPath() + { + var currentDomain = AppDomain.CurrentDomain; + var privateBinPath = currentDomain.SetupInformation.PrivateBinPath.NullEmpty() ?? currentDomain.BaseDirectory; + var dir = Path.Combine(privateBinPath, Environment.Is64BitProcess ? "x64" : "x86"); + + SetDllDirectory(dir); + } /// - /// Perform file deply + /// Perform file deploy /// - /// Plugin file info + /// Assembly reference to probe /// Reference to the shadow copied Assembly - private static Assembly Probe(FileInfo plug) + private static Assembly Probe(AssemblyReference ar, PluginDescriptor d, bool shadowCopy) { - if (plug.Directory == null || plug.Directory.Parent == null) - throw new InvalidOperationException("The plugin directory for the " + plug.Name + - " file exists in a folder outside of the allowed SmartStore folder hierarchy"); - - FileInfo shadowCopiedPlug; + var file = ar.OriginalFile; - if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted) + try { - // TODO: (mc) SMNET does not support Medium Trust, so this code is actually obsolete! + 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); - // all plugins will need to be copied to ~/Plugins/bin/ - // this is aboslutely required because all of this relies on probingPaths being set statically in the web.config + // 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)); - // were running in med trust, so copy to custom bin folder - var shadowCopyPlugFolder = Directory.CreateDirectory(_shadowCopyFolder.FullName); - shadowCopiedPlug = InitializeMediumTrust(plug, shadowCopyPlugFolder); + // 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; } - else + 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 directory = AppDomain.CurrentDomain.DynamicDirectory; - //Debug.WriteLine(plug.FullName + " to " + directory); // codehint: sm-edit - // we're running in full trust so copy to standard dynamic folder - shadowCopiedPlug = InitializeFullTrust(plug, new DirectoryInfo(directory)); + var msg = string.Empty; + foreach (var e in ex.LoaderExceptions) + { + msg += e.Message + Environment.NewLine; + } + + 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 shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName)); + ar.ActivationException = CreateException(msg, ex); + } - // add the reference to the build manager - //Debug.WriteLine("Adding to BuildManager: '{0}'", shadowCopiedAssembly.FullName); // codehint: sm-edit - BuildManager.AddReferencedAssembly(shadowCopiedAssembly); + return ar.Assembly; - return shadowCopiedAssembly; - } + 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 /// - /// - /// - /// - private static FileInfo InitializeFullTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder) + /// Plugin dll file + /// Shadow copied file + private static FileInfo InitializeFullTrust(FileInfo dll, bool shadowCopy) { - var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name)); - try - { - File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); - } - catch (IOException) - { - Debug.WriteLine(shadowCopiedPlug.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 - //which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy - try - { - var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old"; - File.Move(shadowCopiedPlug.FullName, oldFile); - } - catch (IOException exc) - { - throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc); - } - //ok, we've made it this far, now retry the shadow copy - File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); - } - return shadowCopiedPlug; - } + var probedDll = new FileInfo(Path.Combine(_shadowCopyDir.FullName, dll.Name)); - /// - /// Used to initialize plugins when running in Medium Trust - /// - /// - /// - /// - private static FileInfo InitializeMediumTrust(FileInfo plug, DirectoryInfo shadowCopyPlugFolder) - { - var shouldCopy = true; - var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name)); + // If instructed to not perform the copy, just return the path to where it is supposed to be + if (!shadowCopy && probedDll.Exists) + return probedDll; - //check if a shadow copied file already exists and if it does, check if it's updated, if not don't copy - if (shadowCopiedPlug.Exists) + try { - //it's better to use LastWriteTimeUTC, but not all file systems have this property - //maybe it is better to compare file hash? - var areFilesIdentical = shadowCopiedPlug.CreationTimeUtc.Ticks >= plug.CreationTimeUtc.Ticks; - if (areFilesIdentical) - { - Debug.WriteLine("Not copying; files appear identical: '{0}'", shadowCopiedPlug.Name); - shouldCopy = false; - } - else - { - //delete an existing file - Debug.WriteLine("New plugin found; Deleting the old file: '{0}'", shadowCopiedPlug.Name); - File.Delete(shadowCopiedPlug.FullName); - } + File.Copy(dll.FullName, probedDll.FullName, true); } - - if (shouldCopy) + catch (UnauthorizedAccessException) + { + 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(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 + // Which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy try { - File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); + // If all else fails during the cleanup and we cannot copy over so we need to rename with a GUID + var deleteName = GetNewDeleteName(probedDll); + File.Move(probedDll.FullName, deleteName); } - catch (IOException) + catch (UnauthorizedAccessException) + { + 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) { - Debug.WriteLine(shadowCopiedPlug.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 - //which releases the lock, so that it what we are doing here, once it's renamed, we can re-shadow copy - try - { - var oldFile = shadowCopiedPlug.FullName + Guid.NewGuid().ToString("N") + ".old"; - File.Move(shadowCopiedPlug.FullName, oldFile); - } - catch (IOException exc) - { - throw new IOException(shadowCopiedPlug.FullName + " rename failed, cannot initialize plugin", exc); - } - //ok, we've made it this far, now retry the shadow copy - File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); + 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(dll.FullName, probedDll.FullName, true); } - return shadowCopiedPlug; + return probedDll; } - + /// /// Determines if the folder is a bin plugin folder for a package /// @@ -647,7 +703,5 @@ private static bool IsPackagePluginFolder(DirectoryInfo folder) if (!folder.Parent.Name.Equals("Plugins", StringComparison.InvariantCultureIgnoreCase)) return false; return true; } - - #endregion - } + } } diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/FriendlyNameAttribute.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/FriendlyNameAttribute.cs index e2be7dc7ce..ec406c905b 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/FriendlyNameAttribute.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/FriendlyNameAttribute.cs @@ -7,7 +7,7 @@ public sealed class FriendlyNameAttribute : Attribute { public FriendlyNameAttribute(string name) { - Guard.ArgumentNotNull(() => name); + Guard.NotNull(name, nameof(name)); Name = name; } 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/Plugins/Providers/SystemNameAttribute.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/SystemNameAttribute.cs index 60e290daa1..4bbb2e7a4e 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/SystemNameAttribute.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/SystemNameAttribute.cs @@ -7,7 +7,7 @@ public sealed class SystemNameAttribute : Attribute { public SystemNameAttribute(string name) { - Guard.ArgumentNotEmpty(() => name); + Guard.NotEmpty(name, nameof(name)); Name = name; } diff --git a/src/Libraries/SmartStore.Core/Properties/AssemblyInfo.cs b/src/Libraries/SmartStore.Core/Properties/AssemblyInfo.cs index b09830bcb1..ddde4c0b4a 100644 --- a/src/Libraries/SmartStore.Core/Properties/AssemblyInfo.cs +++ b/src/Libraries/SmartStore.Core/Properties/AssemblyInfo.cs @@ -2,5 +2,7 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("SmartStore.Core")] +[assembly: InternalsVisibleTo("SmartStore.Core.Tests")] [assembly: InternalsVisibleTo("SmartStore.Services.Tests")] [assembly: InternalsVisibleTo("SmartStore.Web.Mvc.Tests")] +[assembly: InternalsVisibleTo("SmartStore.Data.Tests")] diff --git a/src/Libraries/SmartStore.Core/RouteInfo.cs b/src/Libraries/SmartStore.Core/RouteInfo.cs index b6856d1a0f..84827ddd44 100644 --- a/src/Libraries/SmartStore.Core/RouteInfo.cs +++ b/src/Libraries/SmartStore.Core/RouteInfo.cs @@ -2,15 +2,17 @@ using System.Collections.Generic; using System.Linq; using System.Web.Routing; +using Newtonsoft.Json; namespace SmartStore { + [JsonConverter(typeof(RouteInfoConverter))] public class RouteInfo { public RouteInfo(RouteInfo cloneFrom) : this(cloneFrom.Action, cloneFrom.Controller, new RouteValueDictionary(cloneFrom.RouteValues)) { - Guard.ArgumentNotNull(() => cloneFrom); + Guard.NotNull(cloneFrom, nameof(cloneFrom)); } public RouteInfo(string action, object routeValues) @@ -31,7 +33,7 @@ public RouteInfo(string action, IDictionary routeValues) public RouteInfo(string action, string controller, IDictionary routeValues) : this(action, controller, new RouteValueDictionary(routeValues)) { - Guard.ArgumentNotNull(() => routeValues); + Guard.NotNull(routeValues, nameof(routeValues)); } public RouteInfo(string action, RouteValueDictionary routeValues) @@ -39,10 +41,11 @@ public RouteInfo(string action, RouteValueDictionary routeValues) { } + [JsonConstructor] public RouteInfo(string action, string controller, RouteValueDictionary routeValues) { - Guard.ArgumentNotEmpty(() => action); - Guard.ArgumentNotNull(() => routeValues); + Guard.NotEmpty(action, nameof(action)); + Guard.NotNull(routeValues, nameof(routeValues)); this.Action = action; this.Controller = controller; @@ -66,6 +69,65 @@ public RouteValueDictionary RouteValues get; private set; } + } + + #region JsonConverter + + public class RouteInfoConverter : JsonConverter + { + public override bool CanWrite + { + get { return false; } + } + public override bool CanConvert(Type objectType) + { + return objectType == typeof(RouteInfo); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + string action = null; + string controller = null; + RouteValueDictionary routeValues = null; + + reader.Read(); + while (reader.TokenType == JsonToken.PropertyName) + { + string a = reader.Value.ToString(); + if (string.Equals(a, "Action", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + action = serializer.Deserialize(reader); + } + else if (string.Equals(a, "Controller", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + controller = serializer.Deserialize(reader); + } + else if (string.Equals(a, "RouteValues", StringComparison.OrdinalIgnoreCase)) + { + reader.Read(); + routeValues = serializer.Deserialize(reader); + } + else + { + reader.Skip(); + } + + reader.Read(); + } + + var routeInfo = Activator.CreateInstance(objectType, new object[] { action, controller, routeValues }); + + return routeInfo; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } } + + #endregion } diff --git a/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs b/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs new file mode 100644 index 0000000000..e0df140361 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs @@ -0,0 +1,107 @@ +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, + Deleting + } + + public class AcquireWriterContext + { + public AcquireWriterContext(AcquirementReason reason) + { + Reason = reason; + Languages = new List(); + Currencies = new List(); + 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(); + } + + /// + /// Reason for writer acquirement + /// + public AcquirementReason Reason { get; private set; } + + /// + /// All languages + /// + public IList Languages { get; set; } + + /// + /// Currency codes used for indexing + /// + public IList Currencies { 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; } + + /// + /// Array of all customer role identifiers + /// + public int[] CustomerRoleIds { get; set; } + + /// + /// Map of product to customer role identifiers if the product is limited to certain customer roles + /// + public Multimap CustomerRoleMappings { get; set; } + + /// + /// All manufacturers + /// + public Dictionary Manufacturers { get; set; } + + /// + /// All categories + /// + public Dictionary Categories { get; set; } + + /// + /// All delivery times + /// + 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 new file mode 100644 index 0000000000..0fe4f85550 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Core.Search +{ + public class DefaultIndexManager : IIndexManager + { + private readonly IEnumerable> _providers; + + public DefaultIndexManager(IEnumerable> providers) + { + _providers = providers; + } + + public bool HasAnyProvider(string scope, bool activeOnly = true) + { + return _providers.Any(x => !activeOnly || x.Value.IsActive(scope)); + } + + public IIndexProvider GetIndexProvider(string scope, bool activeOnly = true) + { + 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/Facet.cs b/src/Libraries/SmartStore.Core/Search/Facets/Facet.cs new file mode 100644 index 0000000000..84a88b2109 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Facets/Facet.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace SmartStore.Core.Search.Facets +{ + [DebuggerDisplay("Key: {Key}, IsChoice: {IsChoice}, HitCount: {HitCount}, Value: {Value}")] + public class Facet + { + public Facet(FacetValue value) + : this(value.ToString(), value) + { + } + + public Facet(string key, FacetValue value) + { + Guard.NotEmpty(key, nameof(key)); + Guard.NotNull(value, nameof(value)); + + Key = key; + Value = value; + Children = new List(); + IsChoice = true; + Published = true; + } + + public string Key + { + get; + private set; + } + + public FacetValue Value + { + get; + private set; + } + + /// + /// Gets or sets whether the facet can be selected + /// + public bool IsChoice + { + get; + set; + } + + public bool Published + { + get; + set; + } + + public long HitCount + { + get; + set; + } + + public FacetGroup FacetGroup + { + get; + internal set; + } + + public IList Children + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs new file mode 100644 index 0000000000..d0e8b5b8e7 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SmartStore.Core.Search.Facets +{ + public enum FacetSorting + { + HitsDesc, + ValueAsc, + LabelAsc, + DisplayOrder + } + + /// + /// A filter and its selection to be applied, e.g. Color=Red. + /// + [Serializable] + public class FacetDescriptor + { + private readonly List _values; + + public FacetDescriptor(string key) + { + Guard.NotEmpty(key, nameof(key)); + + _values = new List(); + Key = key; + } + + /// + /// Gets the key / field name. + /// + public string Key + { + get; + private set; + } + + /// + /// Gets or sets the label. + /// + public string Label + { + get; + set; + } + + /// + /// Gets the values for this facet. + /// + public ICollection Values + { + get + { + return _values; + } + } + + /// + /// Adds a facet value. + /// + /// Facet value + public FacetDescriptor AddValue(params FacetValue[] values) + { + _values.AddRange(values); + return this; + } + + /// + /// Gets or sets the boolean value operator. + /// + //public ValueOperator Operator + //{ + // get; + // set; + //} + + /// + /// Gets or sets whether selection of multiple values is allowed. + /// + public bool IsMultiSelect + { + get; + set; + } + + /// + /// Gets or sets the minimum number of hits a choice would need to have to be returned. + /// + public int MinHitCount + { + get; + set; + } + + /// + /// Gets or sets the maximum number of choices to return. Default = 0 which means all. + /// + public int MaxChoicesCount + { + get; + set; + } + + /// + /// Gets or sets the result choices sort order. + /// + public FacetSorting OrderBy + { + get; + set; + } + + /// + /// Gets or sets the display order. + /// + public int DisplayOrder + { + get; + set; + } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.Append("FieldName: ").Append(Key).Append(" "); + sb.Append("Values: " + string.Join(",", _values.Select(x => x.Value.ToString()))).Append(" "); + + return sb.ToString(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetEnums.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetEnums.cs new file mode 100644 index 0000000000..2ca081d194 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetEnums.cs @@ -0,0 +1,20 @@ +namespace SmartStore.Core.Search.Facets +{ + public enum FacetTemplateHint + { + /// + /// Render facets as checkboxes + /// + Checkboxes = 0, + + /// + /// Custom facet rendering like color or picture boxes + /// + Custom, + + /// + /// Render facets as a numeric range filter + /// + NumericRange + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetExtensions.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetExtensions.cs new file mode 100644 index 0000000000..3951bb1a7e --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetExtensions.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Core.Search.Facets +{ + public static class FacetExtensions + { + /// + /// Orders a facet sequence + /// + /// Facets + /// Facet descriptor + /// Ordered facets + public static IOrderedEnumerable OrderBy(this IEnumerable source, FacetDescriptor descriptor) + { + Guard.NotNull(descriptor, nameof(descriptor)); + + return source.OrderBy(descriptor.OrderBy, descriptor.IsMultiSelect); + } + + /// + /// Orders a facet sequence + /// + /// Facets + /// Type of sorting + /// Whether to display selected facets first + /// Ordered facets + public static IOrderedEnumerable OrderBy(this IEnumerable source, FacetSorting sorting, bool selectedFirst = true) + { + Guard.NotNull(source, nameof(source)); + + switch (sorting) + { + case FacetSorting.ValueAsc: + if (selectedFirst) + return source.OrderByDescending(x => x.Value.IsSelected).ThenBy(x => x.Value.Value); + else + return source.OrderBy(x => x.Value.Value); + + case FacetSorting.LabelAsc: + if (selectedFirst) + return source.OrderByDescending(x => x.Value.IsSelected).ThenBy(x => x.Value.Label); + else + return source.OrderBy(x => x.Value.Label); + + case FacetSorting.DisplayOrder: + if (selectedFirst) + return source.OrderByDescending(x => x.Value.IsSelected).ThenBy(x => x.Value.DisplayOrder); + else + return source.OrderBy(x => x.Value.DisplayOrder); + + default: + if (selectedFirst) + return source.OrderByDescending(x => x.Value.IsSelected).ThenByDescending(x => x.HitCount); + else + return source.OrderByDescending(x => x.HitCount); + } + } + + /// + /// Removes a facet + /// + /// List of facets + /// Facet value + /// Whether to compare the upper value + public static void RemoveFacet(this IList facets, object value, bool upperValue) + { + Facet facet = null; + + if (upperValue) + { + facet = facets.FirstOrDefault(x => x.Value.UpperValue != null && x.Value.UpperValue.Equals(value)); + } + else + { + facet = facets.FirstOrDefault(x => x.Value.Value != null && x.Value.Value.Equals(value)); + } + + if (facet != null) + { + facets.Remove(facet); + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs new file mode 100644 index 0000000000..e6d9ffc48e --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace SmartStore.Core.Search.Facets +{ + public enum FacetGroupKind + { + Unknown = -1, + Category, + Brand, + Price, + Rating, + DeliveryTime, + Availability, + NewArrivals, + Attribute, + Variant, + Forum, + Customer, + Date + } + + [DebuggerDisplay("Key: {Key}, Label: {Label}, Kind: {Kind}")] + public class FacetGroup + { + private readonly Dictionary _facets; + private FacetGroupKind? _kind; + + public FacetGroup() + : this (string.Empty, string.Empty, string.Empty, false, false, 0, Enumerable.Empty()) + { + } + + public FacetGroup( + string scope, + string key, + string label, + bool isMultiSelect, + bool hasChildren, + int displayOrder, + IEnumerable facets) + { + 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); + + facets.Each(x => + { + x.FacetGroup = this; + x.Children.Each(y => y.FacetGroup = this); + + try + { + _facets.Add(x.Key, x); + } + catch (Exception ex) + { + ex.Dump(); + } + }); + } + + public static FacetGroupKind GetKindByKey(string scope, string key) + { + if (key.StartsWith("attrid")) + { + return FacetGroupKind.Attribute; + } + else if (key.StartsWith("variantid")) + { + return FacetGroupKind.Variant; + } + + 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; + } + + public string Label + { + get; + private set; + } + + public bool IsMultiSelect + { + get; + private set; + } + + public bool HasChildren + { + get; + private set; + } + + public int DisplayOrder + { + get; + private set; + } + + public bool IsScrollable + { + get; + set; + } + + public IEnumerable Facets + { + get + { + return _facets.Values; + } + } + + public IEnumerable SelectedFacets + { + get + { + var parents = _facets.Values.Where(x => x.Value.IsSelected); + var children = _facets.Values.SelectMany(x => x.Children).Where(x => x.Value.IsSelected); + + return parents.Concat(children); + } + } + + public Facet GetFacet(string key) + { + Guard.NotEmpty(key, nameof(key)); + + return _facets.Get(key); + } + + public FacetGroupKind Kind + { + get + { + if (_kind == null) + { + _kind = GetKindByKey(Scope, Key); + } + + return _kind.Value; + } + } + + public FacetTemplateHint TemplateHint + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs new file mode 100644 index 0000000000..80e54b44af --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs @@ -0,0 +1,217 @@ +using System; +using System.Globalization; +using SmartStore.Utilities; + +namespace SmartStore.Core.Search.Facets +{ + [Serializable] + public class FacetValue : IEquatable, ICloneable + { + public FacetValue() + { + } + + public FacetValue(object value, IndexTypeCode typeCode) + { + Value = value; + TypeCode = typeCode; + IsRange = false; + } + + public FacetValue(object value, object upperValue, IndexTypeCode typeCode, bool includesLower, bool includesUpper) + { + Value = value; + UpperValue = upperValue; + TypeCode = typeCode; + IncludesLower = includesLower; + IncludesUpper = includesUpper; + IsRange = true; + } + + public object Value + { + get; + set; + } + + public object UpperValue + { + get; + set; + } + + public IndexTypeCode TypeCode + { + get; + set; + } + + public bool IncludesLower + { + get; + set; + } + + public bool IncludesUpper + { + get; + set; + } + + public bool IsRange + { + get; + set; + } + + public bool IsSelected + { + get; + set; + } + + public bool IsEmpty + { + get + { + return TypeCode == IndexTypeCode.Empty && Value == null; + } + } + + #region Metadata + + public string Label { get; set; } + + public int ParentId { get; set; } + + public int DisplayOrder { get; set; } + + public FacetSorting? Sorting { get; set; } + + public string PictureUrl { get; set; } + + public string Color { get; set; } + + #endregion + + public override int GetHashCode() + { + if (Value != null && UpperValue != null) + { + var combiner = HashCodeCombiner + .Start() + .Add(Value) + .Add(UpperValue); + + return combiner.CombinedHash; + } + else if (UpperValue != null) + { + return UpperValue.GetHashCode(); + } + else if (Value != null) + { + return Value.GetHashCode(); + } + + return 0; + } + + public bool Equals(FacetValue other) + { + if (other == null || other.TypeCode != TypeCode || other.IsRange != IsRange) + { + return false; + } + + if (other.IsRange) + { + if (other.IncludesLower != IncludesLower || other.IncludesUpper != IncludesUpper) + { + return false; + } + + if (other.Value == null && Value == null && other.UpperValue == null && UpperValue == null) + { + return true; + } + + if (other.UpperValue != null && !other.UpperValue.Equals(UpperValue)) + { + return false; + } + } + + if (other.Value == null && Value == null) + { + return true; + } + + return other.Value != null && other.Value.Equals(Value); + } + + 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 = ConvertToString(Value); + + if (IsRange) + { + var upperValueStr = ConvertToString(UpperValue); + if (upperValueStr.HasValue()) + { + result = string.Concat(valueStr, "~", upperValueStr); + } + else + { + result = valueStr; + } + } + else + { + result = valueStr; + } + + return result; + } + + public FacetValue Clone() + { + return (FacetValue)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Filter/CombinedSearchFilter.cs b/src/Libraries/SmartStore.Core/Search/Filter/CombinedSearchFilter.cs new file mode 100644 index 0000000000..46f00c7d3c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Filter/CombinedSearchFilter.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Core.Search +{ + public class CombinedSearchFilter : SearchFilterBase, ICombinedSearchFilter + { + private readonly IList _filters; + + public CombinedSearchFilter() + { + _filters = new List(); + } + + public CombinedSearchFilter(IEnumerable filters) + { + Guard.NotNull(filters, nameof(filters)); + + _filters = new List(filters); + } + + public ICollection Filters + { + get + { + return _filters; + } + } + + #region Fluent builder + + public CombinedSearchFilter Add(ISearchFilter filter) + { + Guard.NotNull(filter, nameof(filter)); + + _filters.Add(filter); + + return this; + } + + #endregion + + public override string ToString() + { + if (_filters.Any()) + { + return string.Concat("(", string.Join(", ", _filters.Select(x => x.ToString())), ")"); + } + + return ""; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Filter/ISearchFilter.cs b/src/Libraries/SmartStore.Core/Search/Filter/ISearchFilter.cs new file mode 100644 index 0000000000..d0adf644f1 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Filter/ISearchFilter.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + public interface ISearchFilter + { + SearchFilterOccurence Occurence { get; } + float Boost { get; } + } + + public interface ICombinedSearchFilter : ISearchFilter + { + ICollection Filters { get; } + } + + public interface IAttributeSearchFilter : ISearchFilter + { + string FieldName { get; } + IndexTypeCode TypeCode { get; } + object Term { get; } + bool IsExactMatch { get; } + bool IsNotAnalyzed { get; } + int ParentId { get; } + } + + public interface IRangeSearchFilter : IAttributeSearchFilter + { + object UpperTerm { get; } + bool IncludesLower { get; } + bool IncludesUpper { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Filter/RangeSearchFilter.cs b/src/Libraries/SmartStore.Core/Search/Filter/RangeSearchFilter.cs new file mode 100644 index 0000000000..fda82e8457 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Filter/RangeSearchFilter.cs @@ -0,0 +1,31 @@ +namespace SmartStore.Core.Search +{ + public class RangeSearchFilter : SearchFilter, IRangeSearchFilter + { + public object UpperTerm + { + get; + protected internal set; + } + + public bool IncludesLower + { + get; + protected internal set; + } + + public bool IncludesUpper + { + get; + protected internal set; + } + + public override string ToString() + { + return "{0}: {1} - {2}".FormatInvariant( + FieldName, + Term != null ? Term.ToString() : "*", + UpperTerm != null ? UpperTerm.ToString() : "*"); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Filter/SearchFilter.cs b/src/Libraries/SmartStore.Core/Search/Filter/SearchFilter.cs new file mode 100644 index 0000000000..332b617af3 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Filter/SearchFilter.cs @@ -0,0 +1,211 @@ +using System; + +namespace SmartStore.Core.Search +{ + public class SearchFilter : SearchFilterBase, IAttributeSearchFilter + { + protected SearchFilter() + : base() + { + } + + public string FieldName + { + get; + protected set; + } + + public IndexTypeCode TypeCode + { + get; + protected set; + } + + public object Term + { + get; + protected set; + } + + public bool IsExactMatch + { + get; + protected set; + } + + public bool IsNotAnalyzed + { + get; + protected set; + } + + public int ParentId + { + get; + protected set; + } + + #region Fluent builder + + /// + /// Mark a clause as a mandatory match. By default all clauses are optional. + /// + public SearchFilter Mandatory() + { + Occurence = SearchFilterOccurence.Must; + return this; + } + + /// + /// Mark a clause as a forbidden match (MustNot). + /// + public SearchFilter Forbidden() + { + Occurence = SearchFilterOccurence.MustNot; + return this; + } + + /// + /// Specifies whether the clause should be matched exactly, like 'app' won't match 'apple' (applied on string clauses only). + /// + public SearchFilter ExactMatch() + { + IsExactMatch = true; + return this; + } + + /// + /// Specifies that the searched value will not be tokenized (applied on string clauses only) + /// + public SearchFilter NotAnalyzed() + { + IsNotAnalyzed = true; + return this; + } + + /// + /// Applies a parent identifier. + /// + /// Parent identifier + public SearchFilter HasParent(int parentId) + { + ParentId = parentId; + return this; + } + + /// + /// Applies a specific boost to a clause. + /// + /// A value greater than zero, by which the score will be multiplied. + /// If greater than 1, it will improve the weight of a clause + public SearchFilter Weighted(float weight) + { + Boost = weight; + return this; + } + + #endregion + + #region Static factories + + public static CombinedSearchFilter Combined(params ISearchFilter[] filters) + { + var filter = new CombinedSearchFilter(filters); + filter.Occurence = SearchFilterOccurence.Must; + return filter; + } + + public static SearchFilter ByField(string fieldName, string term) + { + return ByField(fieldName, term, IndexTypeCode.String); + } + + public static SearchFilter ByField(string fieldName, int term) + { + return ByField(fieldName, term, IndexTypeCode.Int32); + } + + public static SearchFilter ByField(string fieldName, bool term) + { + return ByField(fieldName, term, IndexTypeCode.Boolean); + } + + public static SearchFilter ByField(string fieldName, double term) + { + return ByField(fieldName, term, IndexTypeCode.Double); + } + + public static SearchFilter ByField(string fieldName, DateTime term) + { + return ByField(fieldName, term, IndexTypeCode.DateTime); + } + + private static SearchFilter ByField(string fieldName, object term, IndexTypeCode typeCode) + { + Guard.NotEmpty(fieldName, nameof(fieldName)); + + return new SearchFilter + { + FieldName = fieldName, + Term = term, + TypeCode = typeCode + }; + } + + + public static RangeSearchFilter ByRange(string fieldName, string lower, string upper, bool includeLower = true, bool includeUpper = true) + { + return ByRange(fieldName, lower, upper, IndexTypeCode.String, includeLower, includeUpper); + } + + public static RangeSearchFilter ByRange(string fieldName, int? lower, int? upper, bool includeLower = true, bool includeUpper = true) + { + return ByRange(fieldName, lower, upper, IndexTypeCode.Int32, includeLower, includeUpper); + } + + public static RangeSearchFilter ByRange(string fieldName, double? lower, double? upper, bool includeLower = true, bool includeUpper = true) + { + return ByRange(fieldName, lower, upper, IndexTypeCode.Double, includeLower, includeUpper); + } + + public static RangeSearchFilter ByRange(string fieldName, DateTime? lower, DateTime? upper, bool includeLower = true, bool includeUpper = true) + { + return ByRange(fieldName, lower, upper, IndexTypeCode.DateTime, includeLower, includeUpper); + } + + private static RangeSearchFilter ByRange( + string fieldName, + object lowerTerm, + object upperTerm, + IndexTypeCode typeCode, + bool includeLower, + bool includeUpper) + { + Guard.NotEmpty(fieldName, nameof(fieldName)); + + return new RangeSearchFilter + { + FieldName = fieldName, + Term = lowerTerm, + UpperTerm = upperTerm, + TypeCode = typeCode, + IncludesLower = includeLower, + IncludesUpper = includeUpper + }; + } + + #endregion + + public override string ToString() + { + return $"{FieldName}: {Term.ToString()}"; + } + } + + public enum SearchFilterOccurence + { + Must = 0, + Should = 1, + MustNot = 2 + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Filter/SearchFilterBase.cs b/src/Libraries/SmartStore.Core/Search/Filter/SearchFilterBase.cs new file mode 100644 index 0000000000..29bb1f590b --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Filter/SearchFilterBase.cs @@ -0,0 +1,30 @@ +using System; + +namespace SmartStore.Core.Search +{ + public abstract class SearchFilterBase : ISearchFilter + { + protected SearchFilterBase() + { + Occurence = SearchFilterOccurence.Should; + } + + public float Boost + { + get; + protected internal set; + } + + public SearchFilterOccurence Occurence + { + get; + protected internal set; + } + + #region Fluent builder + + // [...] + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IIndexDocument.cs b/src/Libraries/SmartStore.Core/Search/IIndexDocument.cs new file mode 100644 index 0000000000..72ad6f3ccb --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IIndexDocument.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + /// + /// Abstraction of document to be stored in the underlying index providers + /// + public interface IIndexDocument : IEnumerable + { + /// + /// The primary key of the indexed entity + /// + /// Implementors: the id must be persisted internally as an + int Id { get; } + + /// + /// Identifies the type of a document, can be null + /// + SearchDocumentType? DocumentType { get; } + + /// + /// The number of fields in this document + /// + int Count { get; } + + /// + /// Adds a field to this document + /// + /// The field to add + void Add(IndexField field); + + /// + /// Removes a field from this document + /// + /// The name of the field + /// The number of removed fields + /// A document can contain fields with the same name. This method removes ALL fields with the passed name. + int Remove(string name); + + /// + /// Whether at least one field with the passed name exists in the document + /// + /// The name of the field(s) + /// true if field exists, false otherwise + bool Contains(string name); + + /// + /// Enumerates all fields with the passed name + /// + /// The name of the field(s) + /// The sequence + IEnumerable this[string name] { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IIndexManager.cs b/src/Libraries/SmartStore.Core/Search/IIndexManager.cs new file mode 100644 index 0000000000..72868d85b5 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IIndexManager.cs @@ -0,0 +1,25 @@ +namespace SmartStore.Core.Search +{ + /// + /// A simple factory for registered search index providers + /// + public interface IIndexManager + { + /// + /// 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(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 new file mode 100644 index 0000000000..b63ac0261c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + public interface IIndexProvider + { + /// + /// 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(); + + /// + /// Creates an empty document + /// + /// The primary key of the indexed entity + /// Identifies the type of a document, can be null + /// The document instance + IIndexDocument CreateDocument(int id, SearchDocumentType? documentType); + + /// + /// Returns a provider specific implementation of the interface + /// which allows interaction with the underlying index store for managing the index and containing documents. + /// + /// The index name + /// The index store + /// + /// This methods always returns an object instance, even if the index does not exist yet. + /// + IIndexStore GetIndexStore(string scope); + + /// + /// Returns a provider specific implementation of the interface + /// which allows executing queries against an index store. + /// + /// The index store + /// The query to execute against the store + /// The search engine instance + ISearchEngine GetSearchEngine(IIndexStore store, ISearchQuery query); + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IIndexStore.cs b/src/Libraries/SmartStore.Core/Search/IIndexStore.cs new file mode 100644 index 0000000000..36e7fe6a23 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IIndexStore.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + public interface IIndexStore + { + /// + /// Creates a new index if it doesn't exist already + /// + void CreateIfNotExists(); + + /// + /// Deletes the index + /// + void Delete(); + + /// + /// Returns the name of the index + /// + string Scope { get; } + + /// + /// Checks whether the index is already existing or not + /// + bool Exists { get; } + + /// + /// 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 + /// + IEnumerable GetAllFields(); + + /// + /// Removes all documents from the index + /// + void Clear(); + + /// + /// Acquires an index writer + /// + /// Provides information about the indexing operation + /// + /// This method creates a transient writer instance which automatically gets released on dispose. + /// + IDisposable AcquireWriter(AcquireWriterContext writerContext); + + /// + /// Adds a set of new documents to the index + /// + /// + /// This method will delete already existing documents before saving them + /// + void SaveDocuments(IEnumerable documents); + + /// + /// Removes a set of existing documents from the index + /// + void DeleteDocuments(IEnumerable documents); + + /// + /// Removes a set of existing documents from the index + /// + /// Type of document + void DeleteDocuments(SearchDocumentType documentType); + } + + public static class IIndexStoreExtensions + { + /// + /// Adds a new document to the index + /// + /// + /// This method will delete a document with the same entity id - if it exists - before saving it. + /// + public static void SaveDocument(this IIndexStore store, IIndexDocument document) + { + store.SaveDocuments(new[] { document }); + } + + /// + /// Removes an existing document from the index + /// + public static void DeleteDocument(this IIndexStore store, IIndexDocument document) + { + store.DeleteDocuments(new[] { document }); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/ISearchAlias.cs b/src/Libraries/SmartStore.Core/Search/ISearchAlias.cs new file mode 100644 index 0000000000..a822492057 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/ISearchAlias.cs @@ -0,0 +1,12 @@ +namespace SmartStore.Core.Search +{ + /// + /// Represents an entity which supports SEO friendly search alias + /// + public interface ISearchAlias + { + int Id { get; set; } + + string Alias { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs new file mode 100644 index 0000000000..12235f2d44 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Core.Search +{ + public interface ISearchEngine + { + /// + /// Search query + /// + ISearchQuery Query { get; } + + /// + /// Get search hit by entity identifier + /// + /// Entity identifier + /// Search hit + ISearchHit Get(int id); + + /// + /// Get total number of search hits + /// + /// Total number of search hits + int Count(); + + /// + /// Search + /// + /// Search hits + IEnumerable Search(); + + /// + /// Gets the facet map for drilldown navigation + /// + /// The facet groups + IDictionary GetFacetMap(); + + /// + /// Performs spell checking ("Diy you mean?") + /// + /// Suggestions/corrections or an empty array + string[] CheckSpelling(); + + /// + /// 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/ISearchHit.cs b/src/Libraries/SmartStore.Core/Search/ISearchHit.cs new file mode 100644 index 0000000000..af39c9a495 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/ISearchHit.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + public interface ISearchHit + { + int EntityId { get; } + float Score { get; } + + int GetInt(string name); + double GetDouble(string name); + bool GetBoolean(string name); + string GetString(string name); + string GetString(string name, string languageSeoCode); + DateTime GetDateTime(string name); + + IEnumerable GetStoredFieldNames(); + } +} diff --git a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs new file mode 100644 index 0000000000..28ba070494 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Core.Search +{ + public interface ISearchQuery + { + // Language, Currency & Store + int? LanguageId { get; } + string LanguageCulture { get; } + string CurrencyCode { get; } + int? StoreId { get; } + + // Search term + string[] Fields { get; } + string Term { get; } + SearchMode Mode { get; } + bool EscapeTerm { get; } + bool IsFuzzySearch { get; } + + // Filtering + ICollection Filters { get; } + + // Facets + IReadOnlyDictionary FacetDescriptors { get; } + + // Paging + int Skip { get; } + int Take { get; } + + // Sorting + ICollection Sorting { get; } + + /// + /// Maximum number of suggestions returned from spell checker + /// + int SpellCheckerMaxSuggestions { get; } + + /// + /// Defines how many characters must be in the query before suggestions are provided + /// + int SpellCheckerMinQueryLength { get; } + + /// + /// The maximum number of product hits up to which suggestions are provided + /// + int SpellCheckerMaxHitCount { get; } + + // Misc + string Origin { get; } + IDictionary CustomData { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IndexDocument.cs b/src/Libraries/SmartStore.Core/Search/IndexDocument.cs new file mode 100644 index 0000000000..0fce13d090 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IndexDocument.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; + +namespace SmartStore.Core.Search +{ + public class IndexDocument : IIndexDocument + { + protected readonly Multimap _fields; + + public IndexDocument(int id) + : this(id, null) + { + } + + public IndexDocument(int id, SearchDocumentType? documentType) + { + _fields = new Multimap(StringComparer.OrdinalIgnoreCase); + + Add(new IndexField("id", id).Store()); + + if (documentType.HasValue) + { + Add(new IndexField("doctype", (int)documentType.Value).Store()); + } + } + + public int Id + { + get + { + return (int)_fields["id"].FirstOrDefault().Value; + } + } + + public virtual SearchDocumentType? DocumentType + { + get + { + if (_fields.ContainsKey("doctype")) + { + return (SearchDocumentType)_fields["doctype"].FirstOrDefault().Value; + } + return null; + } + } + + public int Count => _fields.TotalValueCount; + + public virtual void Add(IndexField field) + { + if (field.Name.IsCaseInsensitiveEqual("id") && _fields.ContainsKey("id")) + { + // special treatment for id field: allow only one! + _fields.RemoveAll("id"); + } + + if (field.Name.IsCaseInsensitiveEqual("doctype") && _fields.ContainsKey("doctype")) + { + _fields.RemoveAll("doctype"); + } + + _fields.Add(field.Name, field); + } + + public int Remove(string name) + { + if (_fields.ContainsKey(name)) + { + var num = _fields[name].Count; + _fields.RemoveAll(name); + return num; + } + + return 0; + } + + public bool Contains(string name) + { + return _fields.ContainsKey(name); + } + + public IEnumerable this[string name] + { + get + { + if (_fields.ContainsKey(name)) + { + return _fields[name]; + } + + return Enumerable.Empty(); + } + } + + public IEnumerator GetEnumerator() + { + return _fields.SelectMany(x => x.Value).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IndexField.cs b/src/Libraries/SmartStore.Core/Search/IndexField.cs new file mode 100644 index 0000000000..396b9abc8f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IndexField.cs @@ -0,0 +1,122 @@ +using System; + +namespace SmartStore.Core.Search +{ + public class IndexField + { + public IndexField(string name, bool value) + : this(name, value, IndexTypeCode.Boolean) + { + } + + public IndexField(string name, int value) + : this(name, value, IndexTypeCode.Int32) + { + } + + public IndexField(string name, double value) + : this(name, value, IndexTypeCode.Double) + { + } + + public IndexField(string name, DateTime value) + : this(name, value, IndexTypeCode.DateTime) + { + } + + public IndexField(string name, string value) + : this(name, value, IndexTypeCode.String) + { + } + + private IndexField(string name, object value, IndexTypeCode typeCode) + { + Guard.NotEmpty(name, nameof(name)); + + Name = name; + Value = value; + TypeCode = typeCode; + } + + public IndexField Store(bool store = true) + { + Stored = store; + return this; + } + + public IndexField Analyze(bool analyze = true) + { + Analyzed = analyze; + return this; + } + + public IndexField RemoveTags(bool removeTags = true) + { + ShouldRemoveTags = removeTags; + return this; + } + + public IndexField Boost(float factor) + { + Boosted = factor; + return this; + } + + public string Name + { + get; + private set; + } + + public object Value + { + get; + private set; + } + + public IndexTypeCode TypeCode + { + get; + private set; + } + + public bool Stored + { + get; + private set; + } + + public bool Analyzed + { + get; + private set; + } + + public bool ShouldRemoveTags + { + get; + private set; + } + + public float Boosted + { + get; + private set; + } + + public override string ToString() + { + return Name + ": " + Value.ToString().NaIfEmpty(); + } + } + + public enum IndexTypeCode + { + Empty = 0, + Boolean = 3, + Int32 = 9, + Double = 14, + DateTime = 16, + String = 18 + } +} 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 new file mode 100644 index 0000000000..15662c3768 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + public abstract class IndexProviderBase : IIndexProvider + { + public virtual bool IsActive(string scope) + { + return true; + } + + public abstract IEnumerable EnumerateIndexes(); + + public virtual IIndexDocument CreateDocument(int id, SearchDocumentType? documentType) + { + Guard.IsPositive(id, nameof(id)); + + return new IndexDocument(id, documentType); + } + + public abstract IIndexStore GetIndexStore(string scope); + + public abstract ISearchEngine GetSearchEngine(IIndexStore store, ISearchQuery query); + } +} diff --git a/src/Libraries/SmartStore.Core/Search/NullIndexStore.cs b/src/Libraries/SmartStore.Core/Search/NullIndexStore.cs new file mode 100644 index 0000000000..eb5295c17f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/NullIndexStore.cs @@ -0,0 +1,54 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using SmartStore.Utilities; + +//namespace SmartStore.Core.Search +//{ +// public class NullIndexStore : IIndexStore +// { +// public void CreateIfNotExists() { } + +// public void Delete() { } + +// public string Scope +// { +// get { return ""; } +// } + +// public bool Exists +// { +// get { return false; } +// } + +// public DateTime? GetLastIndexedUtc() => null; + +// public void SetLastIndexedUtc(DateTime date) { } + +// public bool IsLocked +// { +// get { return false; } +// } + +// public bool TryAcquireLock(out IDisposable lockObj) +// { +// lockObj = null; +// return false; +// } + +// public int DocumentCount +// { +// get { return 0; } +// } + +// public IEnumerable GetAllFields() => Enumerable.Empty(); + +// public void Clear() { } + +// public void SaveDocuments(IEnumerable documents) { } + +// public void DeleteDocuments(IEnumerable ids) { } +// } +//} diff --git a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs new file mode 100644 index 0000000000..c2597f571d --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Core.Search +{ + public enum SearchDocumentType + { + Product = 0, + Category, + Manufacturer, + DeliveryTime, + Attribute, + AttributeValue, + Variant, + VariantValue, + Customer, + Forum, + ForumPost + } +} diff --git a/src/Libraries/SmartStore.Core/Search/SearchEnums.cs b/src/Libraries/SmartStore.Core/Search/SearchEnums.cs new file mode 100644 index 0000000000..a280714d51 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/SearchEnums.cs @@ -0,0 +1,20 @@ +namespace SmartStore.Core.Search +{ + public enum SearchMode + { + /// + /// Term search + /// + ExactMatch = 0, + + /// + /// Prefix term search + /// + StartsWith, + + /// + /// Wildcard search + /// + Contains + } +} diff --git a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs new file mode 100644 index 0000000000..18cfcd3f83 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Text; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Core.Search +{ + public enum SearchResultFlags + { + WithHits = 1 << 0, + WithFacets = 1 << 1, + WithSuggestions = 1 << 2, + Full = WithHits | WithFacets | WithSuggestions + } + + public class SearchQuery : SearchQuery + { + /// + /// Initializes a new instance of the class without a search term being set + /// + public SearchQuery() + : base((string[])null, null) + { + } + + public SearchQuery(string field, string term, SearchMode mode = SearchMode.Contains, bool escape = false, bool isFuzzySearch = false) + : base(field.HasValue() ? new[] { field } : null, term, mode, escape, isFuzzySearch) + { + } + + public SearchQuery(string[] fields, string term, SearchMode mode = SearchMode.Contains, bool escape = false, bool isFuzzySearch = false) + : base(fields, term, mode, escape, isFuzzySearch) + { + } + } + + public class SearchQuery : ISearchQuery where TQuery : class, ISearchQuery + { + private readonly Dictionary _facetDescriptors; + private Dictionary _customData; + + protected SearchQuery(string[] fields, string term, SearchMode mode = SearchMode.Contains, bool escape = false, bool isFuzzySearch = false) + { + Fields = fields; + Term = term; + Mode = mode; + EscapeTerm = escape; + IsFuzzySearch = isFuzzySearch; + + Filters = new List(); + Sorting = new List(); + _facetDescriptors = new Dictionary(StringComparer.OrdinalIgnoreCase); + + Take = int.MaxValue; + + SpellCheckerMinQueryLength = 4; + SpellCheckerMaxHitCount = 3; + + ResultFlags = SearchResultFlags.WithHits; + } + + // Language, Currency & Store + public int? LanguageId { get; protected set; } + public string LanguageCulture { get; protected set; } + public string CurrencyCode { get; protected set; } + public int? StoreId { get; protected 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; } + public bool IsFuzzySearch { get; protected set; } + + // Filtering + public ICollection Filters { get; } + + // Facets + public IReadOnlyDictionary FacetDescriptors + { + get { return _facetDescriptors; } + } + + // Paging + public int Skip { get; protected set; } + public int Take { get; protected set; } + public int PageIndex + { + get + { + if (Take == 0) + return 0; + + return Math.Max(Skip / Take, 0); + } + } + + // Sorting + public ICollection Sorting { get; } + + // Spell checker + public int SpellCheckerMaxSuggestions { get; protected set; } + public int SpellCheckerMinQueryLength { get; protected set; } + public int SpellCheckerMaxHitCount { get; protected set; } + + // Result control + public SearchResultFlags ResultFlags { get; protected set; } + + // Misc + public string Origin { get; protected set; } + + public IDictionary CustomData + { + get + { + return _customData ?? (_customData = new Dictionary(StringComparer.OrdinalIgnoreCase)); + } + } + + #region Fluent builder + + public virtual TQuery HasStoreId(int id) + { + Guard.NotNegative(id, nameof(id)); + + StoreId = id; + + return (this as TQuery); + } + + public TQuery WithLanguage(Language language) + { + Guard.NotNull(language, nameof(language)); + Guard.NotEmpty(language.LanguageCulture, nameof(language.LanguageCulture)); + + LanguageId = language.Id; + LanguageCulture = language.LanguageCulture; + + return (this as TQuery); + } + + public TQuery WithCurrency(Currency currency) + { + Guard.NotNull(currency, nameof(currency)); + Guard.NotEmpty(currency.CurrencyCode, nameof(currency.CurrencyCode)); + + CurrencyCode = currency.CurrencyCode; + + return (this as TQuery); + } + + public TQuery Slice(int skip, int take) + { + Guard.NotNegative(skip, nameof(skip)); + Guard.NotNegative(take, nameof(take)); + + Skip = skip; + Take = take; + + return (this as TQuery); + } + + public TQuery CheckSpelling(int maxSuggestions, int minQueryLength = 4, int maxHitCount = 3) + { + Guard.NotNegative(maxSuggestions, nameof(maxSuggestions)); + Guard.IsPositive(minQueryLength, nameof(minQueryLength)); + Guard.IsPositive(maxHitCount, nameof(maxHitCount)); + + ResultFlags = ResultFlags | SearchResultFlags.WithSuggestions; + + SpellCheckerMaxSuggestions = maxSuggestions; + SpellCheckerMinQueryLength = minQueryLength; + SpellCheckerMaxHitCount = maxHitCount; + + return (this as TQuery); + } + + public TQuery WithFilter(ISearchFilter filter) + { + Guard.NotNull(filter, nameof(filter)); + + Filters.Add(filter); + + return (this as TQuery); + } + + public TQuery SortBy(SearchSort sort) + { + Guard.NotNull(sort, nameof(sort)); + + Sorting.Add(sort); + + return (this as TQuery); + } + + public TQuery BuildHits(bool build) + { + if (build) + { + ResultFlags = ResultFlags | SearchResultFlags.WithHits; + } + else + { + ResultFlags &= ~SearchResultFlags.WithHits; + } + + + return (this as TQuery); + } + + public TQuery BuildFacetMap(bool build) + { + if (build) + { + ResultFlags = ResultFlags | SearchResultFlags.WithFacets; + } + else + { + ResultFlags &= ~SearchResultFlags.WithFacets; + } + + + return (this as TQuery); + } + + public TQuery WithFacet(FacetDescriptor facetDescription) + { + Guard.NotNull(facetDescription, nameof(facetDescription)); + + if (_facetDescriptors.ContainsKey(facetDescription.Key)) + { + throw new InvalidOperationException("A facet description object with the same key has already been added. Key: {0}".FormatInvariant(facetDescription.Key)); + } + + _facetDescriptors.Add(facetDescription.Key, facetDescription); + + return (this as TQuery); + } + + public TQuery OriginatesFrom(string origin) + { + Guard.NotEmpty(origin, nameof(origin)); + + Origin = origin; + + return (this as TQuery); + } + + #endregion + + public override string ToString() + { + var sb = new StringBuilder(); + + if (Term.HasValue()) + { + var fields = (Fields != null && Fields.Length > 0 ? string.Join(", ", Fields) : "".NaIfEmpty()); + + sb.AppendFormat("'{0}' in {1}", Term, fields); + + var parameters = string.Join(" ", EscapeTerm ? "escape" : "", IsFuzzySearch ? "fuzzy" : Mode.ToString()).TrimSafe(); + + if (parameters.HasValue()) + { + sb.AppendFormat(" ({0})", parameters); + } + } + + foreach (var filter in Filters) + { + if (sb.Length > 0) + { + sb.Append(" "); + } + + sb.Append(filter.ToString()); + } + + return sb.ToString(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/SearchSort.cs b/src/Libraries/SmartStore.Core/Search/SearchSort.cs new file mode 100644 index 0000000000..423ad608db --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/SearchSort.cs @@ -0,0 +1,82 @@ +namespace SmartStore.Core.Search +{ + public class SearchSort + { + private SearchSort(string name, IndexTypeCode typeCode, bool descending) + { + FieldName = name; + TypeCode = typeCode; + Descending = descending; + } + + public string FieldName + { + get; + private set; + } + + /// + /// In this context, actually means Score + /// + public IndexTypeCode TypeCode + { + get; + private set; + } + + public bool Descending + { + get; + private set; + } + + public override string ToString() + { + if (FieldName.IsEmpty()) + { + return "RELEVANCE"; + } + else + { + return "{0} {1}".FormatInvariant(FieldName, Descending ? "DESC" : "ASC"); + } + } + + public static SearchSort ByRelevance(bool descending = false) + { + return new SearchSort(null, IndexTypeCode.Empty, descending); + } + + public static SearchSort ByStringField(string fieldName, bool descending = false) + { + return ByField(fieldName, IndexTypeCode.String, descending); + } + + public static SearchSort ByIntField(string fieldName, bool descending = false) + { + return ByField(fieldName, IndexTypeCode.Int32, descending); + } + + public static SearchSort ByBooleanField(string fieldName, bool descending = false) + { + return ByField(fieldName, IndexTypeCode.Boolean, descending); + } + + public static SearchSort ByDoubleField(string fieldName, bool descending = false) + { + return ByField(fieldName, IndexTypeCode.Double, descending); + } + + public static SearchSort ByDateTimeField(string fieldName, bool descending = false) + { + return ByField(fieldName, IndexTypeCode.DateTime, descending); + } + + private static SearchSort ByField(string fieldName, IndexTypeCode typeCode, bool descending = false) + { + Guard.NotEmpty(fieldName, nameof(fieldName)); + + return new SearchSort(fieldName, typeCode, descending); + } + } +} 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/Settings/SearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/SearchSettings.cs new file mode 100644 index 0000000000..0fee2c0bb3 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Settings/SearchSettings.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Catalog; + +namespace SmartStore.Core.Search +{ + public class SearchSettings : ISettings + { + public SearchSettings() + { + SearchMode = SearchMode.Contains; + SearchFields = new List { "sku", "shortdescription", "tagname", "manufacturer", "category" }; + InstantSearchEnabled = true; + ShowProductImagesInInstantSearch = true; + InstantSearchNumberOfProducts = 10; + InstantSearchTermMinLength = 2; + FilterMinHitCount = 1; + FilterMaxChoicesCount = 20; + DefaultSortOrder = ProductSortingEnum.Relevance; + + BrandDisplayOrder = 1; + PriceDisplayOrder = 2; + RatingDisplayOrder = 3; + DeliveryTimeDisplayOrder = 4; + AvailabilityDisplayOrder = 5; + NewArrivalsDisplayOrder = 6; + } + + /// + /// 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 a value indicating whether instant-search is enabled + /// + public bool InstantSearchEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether to show product images in instant-search result + /// + public bool ShowProductImagesInInstantSearch { get; set; } + + /// + /// Gets or sets the number of products to return when using "instant-search" feature + /// + public int InstantSearchNumberOfProducts { 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; } + + /// + /// Gets or sets the default sort order in search results + /// + public ProductSortingEnum DefaultSortOrder { get; set; } + + // TBD: what about area specific searchin setting (product, blog, etc.) + + #region Common facet settings + + /// + /// Gets or sets the a value indicating whether to include or exclude not available products by default. + /// + public bool IncludeNotAvailable { get; set; } + + public bool BrandDisabled { get; set; } + public bool PriceDisabled { get; set; } + public bool RatingDisabled { get; set; } + public bool DeliveryTimeDisabled { get; set; } + public bool AvailabilityDisabled { get; set; } + public bool NewArrivalsDisabled { get; set; } + + public int BrandDisplayOrder { get; set; } + public int PriceDisplayOrder { get; set; } + public int RatingDisplayOrder { get; set; } + public int DeliveryTimeDisplayOrder { get; set; } + public int AvailabilityDisplayOrder { get; set; } + public int NewArrivalsDisplayOrder { get; set; } + + #endregion + } +} 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 7547bc4fc8..e89e76c15d 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -10,7 +11,7 @@ Properties SmartStore.Core SmartStore.Core - v4.5.1 + v4.6.1 512 @@ -62,73 +63,84 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - True + + ..\..\packages\AngleSharp.0.9.11\lib\net45\AngleSharp.dll + + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll True - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + True - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\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 True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Microsoft.Web.Xdt.1.0.0\lib\net40\Microsoft.Web.XmlTransform.dll + + ..\..\packages\Microsoft.Web.Xdt.3.0.0\lib\net40\Microsoft.Web.XmlTransform.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll - - False - ..\..\packages\NuGet.Core.2.8.0\lib\net40-Client\NuGet.Core.dll + + ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + True - - ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll + + ..\..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll + True + + + + ..\..\packages\System.Linq.Dynamic.1.0.7\lib\net40\System.Linq.Dynamic.dll + True + + ..\..\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 @@ -143,19 +155,36 @@ Properties\AssemblyVersionInfo.cs - + + + + + + + - + - - - - + + + + + + + + + + + + + + + @@ -166,11 +195,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -178,29 +231,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + @@ -215,20 +321,6 @@ - - - - - - - - - - - - - - @@ -237,7 +329,6 @@ - @@ -258,25 +349,17 @@ - - - - - - - - + + + + - - - - @@ -290,7 +373,6 @@ - @@ -312,16 +394,14 @@ - + - - @@ -351,9 +431,6 @@ - - - @@ -377,10 +454,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -415,7 +536,6 @@ - @@ -426,7 +546,6 @@ - @@ -443,7 +562,7 @@ - + @@ -556,7 +675,6 @@ - @@ -574,17 +692,21 @@ + + + + @@ -592,7 +714,7 @@ - + @@ -613,6 +735,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq8+sXZH95yQZMlWbF+0JNnee70oKBYkcZlFVvOiS0/Ml83DfNL8wgDgDQQSd5CsUteLXSISCSCRSCQSmYn/7//5f3/7X4/r9MU9Ksokz35/+ebVTy9foCzOV0l2+/vLurr5H39/+b/+z//9f/vteLV+fPGtg/uZwOGaWfn7y7uq2vz6+nUZ36F1VL5aJ3GRl/lN9SrO16+jVf767U8//fvrN29eI4ziJcb14sVv53VWJWtE/8B/HuVZjDZVHaWf8hVKy/Y7LrmgWF98jtao3EQx+v3lxToqqosqL9Crd1EVvXxxkCYR7sYFSm9evoiyLK+iCnfy168luqiKPLu92OAPUXr5tEEY7iZKS9R2/tcB3HQcP70l43g9VOxQxXVZ5WtLhG9+bgnzmq/uRN6XPeEw6Y4xiasnMmpKvt9fXuabJH75gm/p16O0IFAj0h5R+mKwJHtF65XNf//2ggP6t54p3r76n69+evXTv704qtOqLtDvGaqrIkr/7cVZfZ0m8X+gp8v8B8p+z+o0ZXuK+4rLRh/wp7Mi36CiejpHN23/T1cvX7we13vNV+yrMXWawZ1m1c9vX774jBuPrlPUMwJDCDqq9yhDRVSh1VlUVajICA5ESSm0zrV18VRWaE1+d21i/sPr6OWLT9HjR5TdVne/v8Q/X744SR7RqvvS9uNrluBlhytVRY10TZ1mcVqv0Gl2keAmo03X4GGepyjKgGFq8JVnUVk+5MUKF1QoxsP3RdkhnJwWl0mVTk/xw3z1NHkjn1AVYY4mZCtnaewdKuMi2TQCZ4b25pmrj8kaL4vVZU4XdOnLyecoW6HioPyerG5R5YutwfLPPJueDk1T34togzfYCgsxoe8m9S/u8ofRvPmN/BAzNyoCyJciyQsqldXy3UB4XEa3gefit9fD7qvek6PHI7zZ3ObFk8vOHD2+YjDsN2d5W5pt+W8//WQ0yZbs9S4pN2n09IWwvA2jGvPPBaoqOhZr3sEi4Sa5rQsK/arFs+cgZw56Ow0HfYvSOsROYdkspZWeuk48+zGPozT5E626Nh24t8XRMK+AcM/G8raa6bCbWkDFirLbOrq1ZBEAD5k6hOnzvsjrzfwCum9/qabnWt+fo/vkljKQZCZfvjhHKQUo75JNY08RV9bVAH5S5OvzPIUWdA91dZHXRUzGl2tBL6OCqtduMqXvlqcoafHsJYi8Lc1G+Gai5dLOTEt15U48Rfu49h81ukD5Ecahaj3Awe0kjW5P13iwJ0mKNOT+xWy0miNulfqexwIfuulaKu/8z4muKrhSZlLR3UzGOSqpjCvlApSDlMtQGWAvG0diVArdCV137YxDHURB43DuJaxe1vlqVx2tlzm6dK0vdIQ5LcnqOkvr2yQThIiu6mVex5DwCadV+QsFULfSihAnoXCGinVSksV5jmJq1LcWCBcorom97hWPay8I5G0FukyyPfybXGS9/eWXKdoerKGTtyxdvEeUt1FBlhW8rfM8fMVVGZawGlJYwxpwr0XMonIxGLbVy1csov3qdV69E62gkwKhC8yqG9qen/J8GT0eP6L1xvvWCyNqFXGCh5sCddWDuEruvS+fTstGqjXM74crqHw0FUq8ZJhYMPEnDkM55qZd5Hj92wskUq2k/+6FkLytUGeJJVWR1idi8gvzYEYHcmf+JfuA18cZVen9sB2kaf7wvkZlhc8l3/LKG6GHTUS4J8Lr7h1mzK9V3GEif14ma23d42zlWNPX1uR2bCOSBjymjQpElW5UCmlwSrGPax9k5QMWZ9JONeVXjRgdd4spEkU6V+4twxtkXpK8QbGX5woZham0m7L8c72+RsWXGyLBSr8BTGHUbZaP3xKD1j60BG36hMlFDToKpY+DumIX47izEjBQNshgveUEi9hLWrCIgsmMF4dRidoOEOp2im7nQ6ddnQ2ZrNeowRYw1exDbKvjFC8TRBDzw36XkLfV0eh9nfStNr9trz1L3K7uBjLEFeQxnuV08lYMvNLDNnSSF+uo8t2wO2wXUVpN3vWD1TrJjvL1mvEYni6SoQxmYzq4uUnSBC8XX2qHsTi9QykKEEfRGa4O4jivARfuKWxXYfjoY1RWp5uD1Qof0VThDKYOIxqJVyAiKb9k4HnS2tmkrD7mt0nmekDF9SkXYVEtReGsELQkldxNdKL/igEb1ACxVNj9ARBbvfUwSVM8zf3cq7rJwwJ9HYPIO8zB2faa1/RU3W5hrgaNRuw3DyNo2VJASMX2u7HqUUsNwjCEnNj6mylVj48fiUITpQd1dUdUmpgCqU45qhrgNBhVEObErJbtBGE1oF6f5WUFj60vBgcilgq9BkCcutjEesr7SMvlnRwXw73kYGy7Sc/8cA9pEdi5cYnQL67YtkvnCJ8xMIv8QU20YNdGIGAXYQihqxIw+y4/RMXqLE+yqvyQYCTkxh3stwAn6b0cDhiDAth2JN1Vp9FeIwAD4o+DkQtAHtBWBF7c5bT+ET7EnmKlDO47DwWSXwok0F4O6WXW6QnqEIe0XufZqxbB/kwvbwsf/uouOcCziKk+SYqyCmSL1uvjszSks2KEaQWvmU2UTR+PfkROnIUYLKS9E6wQ/nafZLF4Fte0yET0TjasVta8mauht5M39M9kQ5S/KNUEJwS6Jr/LM9Tc5kwvI6LHmVryMyDIj2bNGgL39k5z6GGGHZ0rErQQvtxa+WAXqbJzHKTYxRGAtKNjKL9bhI5c1srGu6RAMdE3X7U49vqGvC3NhjlRnBd1dilbS04Qz5kymDfkQ/4RESKelnNEgV3eFQiZNvhzgAaxoEVFEnONOToG1df/wkvtMv8WedustyEWbC4PpHN8rMdzgHF3XPsJVXe5xCY2hrkaKjfiLRlb+PTQwHFRW8X6wM5uBJJhjfYKYHcEAcTOg1Be59wxPVzCYFoE3NTt9yGFWFLvQ1OlbpkxNVY4uXR6m2FaH92RlTCJVGIEyxzyiNcmLUSY2/oeK7seCuYI0355y9uSGRnC+jROpa1eXxfoPtFZ5cJccW+DHuR0znXcz4XFr9z1vRzS+ivfAH5pLa79mles+ZZUvovexL3ENLpd54ZzsNlg1vNffEEdVb5uVpMYrfo7ptAuELLbNKmvhNOyPkzz296DzXpJk9rlKwbHdvgWt525RI/Tu+WRwROLcjgv5g4jyFIMra8GwIGdoHKBlUAgbzZquuLBQwTBfi+QtxUqQDHUnaVls4ESQ7sYNltm98611O1sLb4j1s/UURnPq/BIL6Pb6bNiLxPQaJj429TUoW9s2xJ/hxlZ4BxkE90IMvsUbEVx2g1546dyywwSD9tjhWJihUJwux5D+KUoKvJVHVfn+DSOHlzOcVEV4R69GuHZDsWv7dK2bJDqVhrCzaKknkcVc6vnRpQPKN3c1Ol/ofISc0oaBNnn3AWXPP6vmX44+I/l1qsekgn7gwDEgD8QyjpalcXS0iLDh/NjhcVYVueKW8+SEUlriBGtZtX8gls7+vtKn/15QSsNSXP+YXUFbpOmnqYZiUNI2W9JmWDo02yV3CerOkrTJ189ZJkLsIu7HGvCM+qJJ7h/c7Y3a5xjx7VovUkDhCiGTTDT4Ql3D7k/0IQJxaXH/C7Qiu5QwU77jfZ0Ua+DHfUDYezQUSWKG7R3H8Mh7T2cDuJtS5598aOeftFFWX0TxUQFKfA+Wmm9dMM0+75KVAs8TCOn5fvkpjqKCu/Lng5PCG2FBH0lBfpS3WGKN9tJgLfNKM5B+dEEagcRajXRjUlkJdaNDlYrrg/eYzot3+UPWZpH/vfkLR7fmfuapc0C7xB6j/FT5xb/5UbA6ZhSqUVz/LhJmkeh3kVPPE4zFDTynaIIwfYfovIiwloTCjWrY2yWASq4NyQlysFtgRCrOLp2ZoRsFqvJaXlOUnEXARyie0RHT3GKmk75yjgW4xkqktx79fU46d5PEXuulVPqUn6ckSoBEnyEzNiL5WlCVl6UdhgbZ8Defo3iZE1sU2cF/tW++vz3ly8uSO54vH86dD9YApfT8rj0JifzHqIv42AVh9xLZvd4aWJ0jc+h98mtyuMf/6ij1tbhJbKb4xrFeHAfJbhukjJYPd3DwJ46b1hJFnDkH/OHZtRtjhVv58G8Sm6eqEHgJC+6Ph4ifPryQ3wYxT/og6fkYXPvvETkNEgwnja0xCeQ/tDrrVHQQz+epWRdr8NMUoMxegyHkRIArVpkCZpBWcVrgXTwsH46rKtqMC54rC0C/T0p79KkrMIgbRd/ijDzYrk+st84X35i7ZyiS2Jv+9IISfAd6Eu6mraB9mByRK9hJ2rjYoPxRKnlQMxx9n4N5PLawUOBxdX6OThi6sxZx1mFijIIf7Via4QZTcwUrWCbtU18+LhMULMmvQU+3kNRWR1UWHRe1xU6ytfXSdZe6wVkQtxnLPNo7jriQpsm/hrzd5Tc3k23FMfnmODovyerCbF/mJY2/U7jK096RH7CJNyNRbjgijB5H7fITVwcYHKPiidS2dJ60qmBWAUTL1p1mnSJ2t0nyLG7x/cJRWVdINIlhToZ5JnNvs2DNesO6Stie7Tkxxi1GWnrbJUieqOlsUCFMa837Z2hgiSgCmX4GCEl1AiNk82cZY1Ynsuz2RL7LRJ0kulKr1roy3ywlQ+OMXIowRlGAWrrIshKA5WHz9UYUPDnGZXLHJTGQI7+Sc2syjOuCWBXLR9IvatkoDInJCm8LfG7k0lj8NL4WHVmMYWbGA8iG4AA59hx9hJR2XcWUNF/CEw2BhDWcRxnCcWjHEILo+g9ByHrOA/m2Of+EjycW6HS5863v/QEetPmY+2PLcr+w1UU41FXkI1PU8txvE24g0KgjuBEecoUS8UpC+MoTb9FRYKVOugcqey+op5iggxqyWbJpKrjVPGorQZuMVrbITqPi9XTlTw4BhSZkC2XcuEIyDqRGXc3rOgsDyp2dwwh7TAHZttl1oYDdLcvhjhDKBR4QYTw8mnuVEWnZBitUtv/2vs1y9vSePlO9Eh7NzMhPDW+luSsFOPhBvBJ7TomYgxu1Oqasj2VW9/8LhFa2HpXlUd5vckzNrGbs2VKwDRZxpNuZqh/JF1lvjw6YCLEs/UD0R3jL3PNUUw8eLMVVGf5AU7YlpTAtpsTYGKYwiKhH4NgvDC2SLSoWs6Xx1n2bcI1gHFAgPKhgNB+6Wrac7dHhFGHYr8Vy9vSbMUTBdyQ+JdALesM+PPFShzmFebTWcN60iSa4bmpdhmFCujZB8wEenaPRPSFcgBsLW2WN1Ukkuoi+RP5tT0K/Skvc7xjobjicTt75LR4voydxyZLn0UPpOdRdqv02QrDA4Fj48L7L2xx5NC23YKHu9/f5vv0mwgrWt8S9PBpkucbQl8+OhxZ9BeQwPnG2ArbiGk4WV1/U9UBMUnquDIxOR0P4JehpOuBte7+Ca2S6FVbf6+4K8RXQ6LDJIuKwWW9/ctkIen8DNc0iYtyC5skFh/lJ0mKMvXp4OdAwY+f0YOv3D4tL4soK5MQEVLTpLh0ubkVRQgMob255eSNmzgZ4fTPObIXL6apR6Y6VsynGzltpc6rQXKNJls0lis4rM+Frq+K1EjWV2qMXTDI7RqDb7+KleuGkivA+uPJfl6nyOiF9UDJKjdo8lur9iWmQFn6+6DUMOiG9x/wAYJ5j9rSYjOg6SX89JlGmUajJ8I8/UM/szXczYbeChS65dZeYTlPx49YUGHdL5rxjDz4P0x3gyQ9HSvvm9z2npEXrsf7Jiyi/W4jb0uzERjGFVhf0ad58QE9fovSev7WW8X0Y072nKljKly1YNtz1uCz7X/UGnDtV468rUCnrZGHvS+yYIkb4pABQHJfqYVTTmovambOMJmkmAVZN13Pu6FkhS7v6vV1xrz67YqsTRy6PTc4z/HqRX4V0jFFwyOGkUFcrSt2k1BECcmr6SOGFHVt7024sKZpQ6EkhhVt6FRAaxDTltEAzLsexCykm+qQegeHeq+GyNsaiOadv6cjejBEVL/3fkStxGpH417isGtopEsjC5eRpdp1ayCHfVZyAG9NDtN+ncrbCqThh/IdOy1PsNZTD3lNF9TH5O/89RxqEDY8AMvjhvsfkrUnAk6wz7sEOuv6G3KHZyOP/WUDi20vHyaXDyy5/xIyYsythiH640rqOP3RH5J1KK8wgfzwSTZg0v8gsiSQENlLj20OCZnVSZ/hhH1Uw3ZFNezjEHYwDmHvmv+M7MMqx7jx5i66xUHlgqIAAoU4bcDZafzPHTDevQ6hEj5BTiAw4RsJGiTFeGMNC5A/r4nEaocdIhhrEpdX/YEgdB4pySHBMPuU6cBUfOIwWjU6UxKosFjSRYnKS3Kqh+osOFVo93LTVuB5v/kQxkN3eyRYuAR5Myx1u1R6RlLDTy6CZFK3K8Ez0MehuiADXXBMIPyCi729wPsriCYV98KSaYElx8sin2Xrc1K7jG79j2UYyX5pOS+tUHZc3V4dLLOrZB8Fs7/68KYie6o/zyqQ73lZ3tYcz/zO8t7uQm8JB33nxvbVGDU2y0diNG7Klm/CaPzhtvMRi4OyTG4zvIq6u5I5Hodb5CWU05I+e+h/9xLGHDmcNP9znQbQWjUyL9wjktTW+aWuvtxQpFTvtb+l8X4YQ5WXXPNmhmlVmWHNuP4EThbT5X93GKyridU0T7qqbU0KddOqLsPWJl431hLHg/AIK2UR7XU/eVuac8xEgZ1G3hUhwzq3O4YU2pvbbG52+4jvaSz8EWy/9uRtBVKcWjTBLjTI2234E/MQ+2Ra2GnZZiTw9iJh9qWsKvKUYNvKxEj2Go3Lwy6Gm7izwsLznMt4QLuraR3bEaovNQyGyiOmIVNWD/TQGldSyaulgbK+6VM+aiQhLHwBN5P9LmKwi8yTp3yhq6TJ3HB9NaU2I0podYmi3XO7vC0J3fz91XZoEWm8YEnOnos/6qhA5fnt9eSDamy4q3/hlbBGEyaQa15rn6EhvzjmyY6EVDSEUGs/JtkPJkPdIolsbLWt7dGzDPVOM2XNaUcaHh50TADeIdjvM4p129LofZ30rTa/beMIyv6Bx6+Fd3IfANVkG0nX1lRZ0MlJHTUvBk4+FoOs52EaOn7EYyrD66vQNvVs8qsbi77+TVIPzbvHsRd/8rYCaQdwclbbsC73hLxhLngt/Tvcs2gwA9W86DsGhZ71ZSEEpUUCFvJ61vL1YXkPVSZK21N884L8B5Rubuo0Q2Xpf4IXUAYTKy+IB0vbFfoETTtV7b750mQBN73zXYHfo7IdYDi3hlEHVeq9QOArrqqg0GtqyFR4XTUvBsR7dXmUrx1z9JParxgU28FjbWd0seuhzBpNY+hx+mspQmmnRG1Sbu8wgozOTOzVADiwNVQuMDEI5M2yjnmKe37d5ybW2GKi7LaObv3vbAOtQfvA3CLEcrRs9oSKrrlbDZY4YZkH3JsEHI2E8E4icbDZFPk9WrX4jgA3W9swyrwKjzRw6oygCSb2OW3cRibdYztZKt1j6ZY4QI032FEhuLuOIWzPSsz+DHsNOGkB/K2/UlVw0gKIQavIovSgru7IntYErJ2jGPOty+mpywD0SoV4rzIohFBLQV+V4XjNJJuf1B5KZrkZ3U2iDMcJ22R79TZjy18IL1Pum6epgzjGx9R5GsR/3icrVEz5LJnWMgYKTpUguRpqDpLUqIKwBZjV8jpxneRFvT7LSxcTAa1bvupR7EWovK3LfJPEoczSIdxt5z/MnJ4drFYFtYBO7B+yC1nZlPKlX1KgMBFLBckBgNhqjxQFZVtNF1lAqJNDuaKbDJC/PGs74yXQKI69RJO3Ram0NRKNzFYIT6WL+vpfKFZJx79NE6D0uVkIpV/3vyX4BOZpwIjKivTE2+erxRNqijt8jdy1M0Q9jw2hEY/SHWFcDMtaDsZpT9B1sAUCe0d/qrrWAPhvAZ7Sfy/45W1RAr0v8nozcR4707zJW+PhD0p1yteeMjnM5kAkaJBzyHPcIv5aOaGHNSyX5lcsECfNmTJYmrMA/tK87YSXSKc49nJdIWSevTR+lms88P2jWmTA90hW4oK/P5LKEydx0c6ttaRoUug0/+2FhLwtSiBtkrNAFw6kLe9oiKAOwd4veidpionVGkK9jRV4CW4U6AzIe4HZog7WkTDYzqIncpscFNknVN3lqylvkiQcc1QXBcripyNcc4ZGm8bOo2pQgNXe4393XguX0WO7oYYwvH2L9LkTA4qVi/q6wiIxPc3i9JKgDeVqr2rs+HGexi5JY3huYuLONNcIR43OM9JW6swzwraxWUeGG7JYyvaNjYQj3kUSoiJE6QlCU9NU3vLUBJa3PDW1W/yq+8+AEm5yJu3W+rStULEyWRNY8a6zFVrZJoS1buYhKlZneZJV5XdUIMx2/j69R3co/pHXQ4z3nEdpofFZUqh2qkcoH/aDm5skTQK8FtmfDzaT04A6VZNDDUZ/VCAsv44wb411J2eWwpgIhuknknR5FjVcoM2Er5CWP9BKNiWTjvDo/v7tLA0dP26SghoSPuXZkOZ7pjb/C0XT05NdX01y13foOgnwvnWP6iCm++aHPF3NwB9iwzMxJtPwYZT9mOUAzLU5i4hh2zw9mrM5Gq4y5MmYo8nT62gG5aLdTakC2IewTr3ua3wgKJI/qaShKSqimPwcVIPZm55lycgaP0clkwx4Qgm/IYb0eQkuNjrTaC/q615Hn3fIZ3UR30UlmtOAfxYlrqGDnQWkOaZPPy9tc+R8jgXOpm5uS2a3Ggd64Hp7ryfZk/A5IldvzGO9RtcWHyKShai14nzOq/5FM1+ikdCWTXV5l+D+RfgzjVb6EGWrL/cOJyvpPer4Cgi8T6Vr9IoHHO5UoXLBDQMEsnX4UzolNi1A/ojjEknXnL0QO8PT1zK6RR+Skry+AHYQArxqb4iHzsqhhLtqBajtU57vkxt6StQOAgK8+lqi1fekuhMGo4cWBmVQxXZwtBYJBFXwN40TFfrPFQmd5cudeoalhiSPdl8s6dlQBPeMKbft2TlaIbRGK1ZCHjfqPdBRFkrLFFpgYTD6GtZP1+K+yyORu1KR7OMS8cHLcbFLrzZG0liA5KUdByARejyUX1pOUBA6PGHToClBgbn3hpG31d+xepqPG6nma4P2Uvq0u635DjvU0Gyy3UdhraihbfWFVqZMoSAYdXwsuZxWOqwtuHq5Qdj2y1zeVkcv3xU62kFDIGufBJjo1nIigdJR01zbHWpoFN3uo7Au1dC2AoVTtOZQ2o0GBOt0XgLHX8jsBYu8rc4ox0gFKAegmXQK4YU3sR/Eadl1lr4DGwUwdXUIj/J6M5PF/BwTY0PyV89iEuxbmyd7zgXKyDl2jpE1Tc0zrE/4sEVTcE3cDnleruMOaptc2pg7kQHKfks2MjsJG7jpaGBJqhzPlayOOCIJqHRXlsF7bclMD73CW/YJUk0O45hI3qEnQfT8s0VfzeE13yypaI70qf2D+4YmdweeYzSzjGRq9+XOttHoiFOTbNza1LRbxId2Xt/Z7m2dADluT8sOWTA1/iNeINnwbpHlEYhI6+axNnMO0QTq1dkqRVjNiqb3kmgE/BFNwheKv6WK0kFZ5jFxeV51ygp86xFQS5Jpfjqtyt++a3HRCF7XABeRxgqp4qGSAfeZ+FCJUKjo2lmIh0r6i0tPvZKg2OuV8raCaIPNPHlLJPvNmMZ55oOzx1aeaTWSwOxiH1xuwMW/02IDLta9UloD+PZrUMF3geL4G4KHwHEYpVE2vPXlfhc0sel2LgPaRJJB6cgGebtAbm0qOEFsKIHD3zgFdggyGU3A66bOjcdBFnYeNb0P0V7+ydsKooNcFlH8A1N8Ji9yGgEc9nRHeQa5+qa/Q2lyj4onx+qz6z7GznX8opf43tm6/MkdQVmIq0EGiB0cAUidAMdQXnmkWJQhhNLe6K5fkZROQTzpXB7SC2Mv1y6GkOtAtl7h1eJ4XsHsmJ2jP2rk9EZEayAYodmvA8U6CJGtLNgiCHVcCnP5dI6iMs9O8qLhpvnNIC3/Imr2DnJB4NcBdWSW2dTyz/NN5+1RRTc3beTF1Bcrq3WS6eOG//ZTkNdGRrItTNK6LYrfcz1RMzSRnKUhCODcCYJ5HjZzqpwd4U3Iz5+Cx7Tf2VTrP8DOdhYVrX5jeUPY3Oc5VGSnOISHZTALZBi3kGXyN+EliQrMQ8QtYjKLZRilYxdE8cDcktPFWEpdsfDsGUMKBpw05LBBY4yFhqC9RAqk77ezRVZ1vyu0AlzzymD0XQ5y6Svkb3PfBAVU+11Q3pbGsdr0gWJbiy16rPCn9Wb6DCfEAfqPOikCvFlObGgEvmX4UHhPy8vo8fgRMdRwRYURHWE2uM2Lp2Ab8VGeVUWehtA1wr13cFpSTy/kS7DJXicQhBANjYOtzjDsFSASB3FtWkewTRtX9DJXw60ElOkU316wy9sSKDbx41gT7RRUJT9Y/QvzDWs9Ca6cN/d5MzR0WmJMeNmjOICXqodANZdc88ssXuW0FnaOVwtxXRC+bnM5eYe1SxDupZa8LZ5kzzmRBT9WieES5KErsTJryjSrAxg3DSsGXWfhFth+ZSmY+ClOUbM1e64GgugMFUnunX+CetJQfJ6uixcVnnap+8tCZ4tAuRNPs6RKonSbJRnbRSMpdjWuIRddI0CtvBpD21rQpPv/3GJZTH5mKc+dxDJlevzpY37rIJFxrVviX8Rg2UtjeVsMmbbpEidcbuvtEEwcmcGlzMBcCfDD6lWACXJJBRv0NoJtCLqIgMqVvQ1zoS2QMYQ4IfB7kSJvq8kKjXv0kBeqBNVvpjHUaMxDE73We5wRYEsdy5iP/bbC/RaoNr3ntx/RPUr93/rMiypcNJBl6ycYfLZcPps+2av3OnPUJ3TeMLfoazG92wZmclQUqJijrRn9LTScVhCnsyz29R/Js4qcIaJS9VrS35y4yE2JkSkvSqUlnLJCX/pm38FwEPnNW+SvBFR7+S9vi6WTd2ajUAcgOoP+Zq1NEm+pqUa5EgX+BdelHEpYpQpQrzV7VtAsL/3G67pgx3j2q1WxWkN4yxIeCrVSh9wP3gpkff0vFCsd83+ZzAtqfo2VOERFAZyWWvP24VPzYFdAhH3GSl+cE8lQlo1BOToWK1dj+EGQKsAESaqCtTUpsWlT9L1noaV9H4B0PWcg/WxM3VO5DtK/r1sOP/fCX95We+z0dv8LcyHmeDsnNxIr3lrp2eMKeGdFKBRNqwKEX6BQfIdWdYouo/KHA9uTauUrFsme6eVtaQyav0xj0DzA7KKy75g2q9sY8+z4cUM4Uh03+iZMSGJzCyBt5e9bZBwGVd/Nl+y4KPyVnM9Y4zuvXaw/HyMay1lUjnWPs5Vrq3UcYz5xbZcl23QMdlp+SFZ4YftOEP7zlqyKM4TluJCW1Kyu3hwcaNDn+UMrrfthJ1lEPBaO8oy4BhBr4SeKjrY1XnNwBxwEavM+KVq5KnJxihX+3OlpjwviAYHRveqR7Hc0eVsN6X21uAbLMt7g3dGBaFe+4zjF+mwaUI1k+ya5rG959GoMyl7XQxDAhT0I5qVafi18VmH+qq+/X4DPeQFepPXt/K0Gi/aKstsab812M2D+RhVhjCT2CWElDlh59orHtF9UUy8qPKL3RV5v5mdu3PL8jY4e6pvP9uxwi2C8+i7v0Bp9i4qEoHKxjpD65asRmv26k7dFCRWAc2eJOvRbDW/DnNem5H6K20V3o3HPzX97bp+cD239hZQOTlPpeGUaynJGjucaj7AwK/hDXipTxgWyuHzMb/OzJCYrYHtyIXyo1ulhvmJUoOmc4RrnsS7h8GdUPeTFj8ln96xIsGB6okv4qLVr+eerojiPH+M7fCpA5KUqZ9SKpDzSRuBEPWSEV8paTMYeHbCYukdbwz7rkDgz+pFx4JIhjaDUYxmD+iUi6rtlvZe+SwoUk8isVx2S/Y4qb0t7vzaNAbGZGM27vL9MkizV/DG8v7tuJx9zgsCfrCZm2JO8WONJpy1M255vlh5eaJEFVt4tmn5jOS9z2zFS2XacrfDMBtew7I43nyKaeM7zlNNi2YtmeVvPwE494VH7MIp/nGa4hfiHZ9jAUVRFaX77SoJxz6JBJxi8sQ+RszZY9PUCLv8S1gMd/3Wwgo6urRAyyamssTMx16kG1HgcZyEyn36KsvomojaF4hKtsULhdnPUyhII3V6QyNtaZn/6lqAH3FV1CN4kLbvqrOYH6DYBaQBe5lHt+XjPx7PxcSvcA7Axh2nPxXsuno2LqaJEXjZolSBnJh4j2vOwvK3+VPEm0Onk7TI3TeY7fpGXJdbBU38u41Ht+Wxb+cx8H22S031C1V3u4l3a1i9fjRDtGUMxoSyhmrRQ+n11ku2NZMd5hxgTmX8PnHjwHzWq0eoY81N6UFVRfOeYd7YN7ShfgQj3PClviyGYd7w+7gmeBHLVNWIqYgTiZkQAtRWNaqUwkFfJSQJEM2j2dLxf8kE/Jt4eXtr3RDncPiVrRNqcoWV5SH8iCdoAF/pVAz4YFOVQgi1RAWprDWVQWfR9VEs3BOar4UjYGl520VE/g0jqvXxWSLoiyQvvx+kIO80fM01a1XqTBAljzucf3GU+y9DO0SZ9CjI+o3ZmGdPR0eRNHMbx5G3o8xMF0kOIE+n0LqQhPV0usHC7LBLv5wMwGqecjM3mF8d57f9UCe7EilzeRWnaz0KA5Cb9zgwnOAmsI/Cp6821CtMBsSTXjehqDAwOZASj0nHGgF6qzbhb7roNi2ev3GiWqep48cskDp+t4VV3qjJsXB8AMf8Iz/KCz5Fh66tY4pkMQyDrs31ZalKlT9RyE11zUabWYl4k3jt0E+FVjXdVuhZwLTukFtf6602U3Lq4ufXyqsOxl1XytjTSYqrAXK2OOVHDgXTOJd2tvbzTzd3EmkXk4Y7QL0MO1X41Oq/GiSyi+FTZqHZN0kA0fcL/53XGDJUvJOTxLnAMi9dpUXpGfONwFTHUfutV+2dtbWNZ+Rk9lB8RWeqeDvq9yIQx7iWnvC2YYt6J/hc6yoWRJ2FtXxOGuHxCUYmZtnkU2ysSdoRpv14U60WtaUz0atfCj4adE9JNHRM7neNby9zv8PLNSrcdRlgpPbL9Ytkvlue0WE7Xm7yocKs3iVN+rlH9/eLYtsVxkqeYcxZqG3MEaTNMxHIYTGESKP1INn4duYx+ID8MTTaBL5n/ORMvkpMEpSvy1wzJmjquOMqzm+S2LsbeglPZHo4fsbBh3fMmTMaU1uusT4owcWvnqMTy9DS7UfmIhGmqfaECIydvVITLxjR+PgO4Oh5tMVdj8OHyWA4lXB8rQP0eAnnKYvdsGIQ7u+xPrxhU+01VsayDpMRo+EOd8fXNNJmPjLJxTLQ/07Rxj9VCWjul+STvnfLDpAvx1PLZiKZW16ep9wzaGNnAhc1cd/bJYjfLlblXzCMRlu/QJs2fHOMzeBR7iSZvq92VfEXaMos6kH49o1IzMGWI84WB20yg1Jt6L5kwDWmzxAZJE1tVm8siysp1Ql++CjEVEM5xTBIVSjCYw0G3sUIFClfX3go3x/rJWzK+7g7ECLQ9g4dfAw4uzDUyWY/JPfrE5K/08B5z8UFTJWe6kYVR8RvzVQ88nKtkMMKpSgro55T76GefHNXfKx7ytrbYPjlRlg8itsmvllTTy23DmO8gqbnz+wQTdtEw89Oy3RS7xevp9hPIeOtnxhJ5CPPOHGdE/CfZFucwlnbc05hoZ7Gazqjyf7m5KZGnfzx1G/NDcRhV8d1F8qfnPcAZXuTNIyPb41RHnp6kb0rb6I/Bgtj+mWwOivguhGMQqTU8PuWviw3KERz25aWP8UFeWsUtmIF+pGNJDfRyKIkqGcBAX69ZVVKTheK0PEmj27KfVd8IMFlLwbRQvDqwlE6fyF7GLNfxtH5C62tUdEInza9fvqAPK/3+8ieBBUawRD3C6isimflRX+mNulJzAG5hAUfRZkJUk6Q4lntP0OiKRd7SghN0kRDReUbDxsym6RPuSLLB3SWJ/8kAR5XfmEzBQVnmcUJJ2O1v9MGCRlDjrZkuz6vunUau/8fZ6kWzbJW1hkU+2GCgCnhfoCPCxMRL+veX/4cwfNMGe4HINNgNgWvkzXhMuJEv2TtE9rAXB1TnIpFWZRytRLGJKboaf2kXDQn7whpUidkjySrxgJhkMZ631GYoHBLDcybpZN8cX4L3B5SRI6LNHJr0g33VU+xP3yxHTB3tfnvNMKsBD2P9atX1zZCBwSpS7mWhrVkXbmr3+FY5jrmYVjlvO8GxWJdvdyHgIelSyrXqahDn8jVsGFfTGsC843e1lS3ZECtPU/2KHkGBpMjJPm0x/BHCnVqqYNdnWJ3gHOzGgsQ9P8jKB1RcUT5RMQUDJ+OzBsSW21jEAL9BDLwdvAZ0fCZuA+bCpGUCvxivXeDzNHH3avKDXmHdC+tgcYVWR8QkQ7PpyLhEXxXiyHEtG640aA/aBmihTkmyoRg+9SFqq8QH1KsOvbTTEDRIFxbQiixgC+aUWH7VKkcww9pVzpFJ+22VxRZxa+vUMiMHB7FhC2LDgDxWc9b76dUr0bDjxEKSPszAPBKa7hLbjEWPbprHqyUsC41xA4yklJLh2Qnsz4xMBdLapP1RxcUYrH/aa7iMkXGACAqx1vAQmTlvAZgBxjJjWpexHyYpufbtGtB2cwwfnAocenNSiKvLgRrtm+HDi3K6/vIVVPRoYV3IIjSjMChsnwKlG8UMEks3X0b7IfN04iLy6jDNb8k9ht7AI0BCfNkB2TCkiHinjD3S7s/AgtI52QmjD+n9Ub6mV+Y946i4hAeWcWALZ8uEAnqAD2UMvh18KBvBTKwomx+T5rs6y5kgm2eAzhL6duhV+7/cEgmCgwbJEaSVURJuA7JNwsiX50r1EOawUirnychY2VTZGsZsHxkzZRr+Td8pGJN7B1hsY/sZczyEBRhzPE9GjDk8373MWbd9nlYrK3lA8ETTwlgdZXi85pIxnOFE1ok5TiASuu6CVHuXlE0m+YMNnhSSGLIdjer+RVUJYqoO3oaplG1AZ2QzxvUiTbvSbQjTVZmOLH0L5mI/AE26H+fojzopUOPiq+00VMuGMkH3K9P+AXQF4PST6CTWjEg3g4wzIpFJP7r6S+txV5hvkntUPNHcARoNawSs0OEclLcxaojX2H5Otn2qejOfFgbS2YirmHpLc9Zhna1SdFqh9UFVFcl1XaEmaupqKNExnAkOBR9KqzswqFFX5HsPM+ZtPXzYjHC+tWDDAkaWnb7W9iyQdiiGZ2lZPaOF4MX5XHs7eMDWjWUJvoZn0ZyXlz54iwOyZ+T5WNiFeUOqFvKuLMJ7O2joadvvzQ39kVfDBEIFBbe52H+kzVgc07dGUEpHMR+XSufLpAtdna3hUkOZyMNPzKM7vJXLxrAAg+6gECWv1dxE1OhaaC3mEDDEmCycDVeC+Jewnqs6MgNfqei8C1b0lqlHwxjxhEYsSSsqpKArz2mbBPhP1dbWyEXtiOYTkNr5NOkKW2+rONtwR4fqzMTPO7y7q8axEAPv4C7ftn+xQXFykzRpSHozmikDq2srWBmu6MDUmh7sIHubjWg+Rjeb411geXgkX+h7d1cSjpSxnwMuMLJQgcYq0NChO1AIj9GyXH6peAx3hoXjwRsmvYMxbOlGomRwN9muJu9iW46yW8ZrDV76y684/7EvvmOZ8I37+mvwLL0Ku235MrpVZIcRYQN7bLCY5SoYLg6Y/KXB+S0qkiir+mk5ytfXSUYBrfxZTPEoCKdA4UBT4w4t7SBj29H55ILtnJr0bJvcalTjMzzQGaDYCo7f4fOdxbC2Y2ns4EnPYFT/qCOaIfZrlnitChbPViyNUYeA9TEa+JKbAdTR7eB4aE5NesbW2zbed90BHMR+AIZ+TgJ+e6T6MxLl3VD0ljpTBMswuNIcJ4xx11h+Ceub7XxbLIKtMbbxg6Opva9k3GrJmkpkFquE4gmwVNT9kS8b3ard2tVjNODlVpIRf1isKh7F0ovL6mhgqv+7WNG2SpNfWF1/Fjr51UVE3nJ4lz9kaR6ttLw1Bg/MXRxyyGzY9XNy3oL7MiN3wZQ2ug0Y1VyMwy4TVOBRkxypbG51GRPA4BCH9ZA2PCZBb5kJnvLZ0nu0eigzsKh6qkw6wNbbAgbVGSIEyAnYchfNC9Lez8qEu2tCOEf3CXowtYONoRV7bwPosANzLewSKypHMN+2Dc/RzrHkB5Rubuo0Izlfx0xlxEHS6lqmZWo686+8dTlDw0tmy9haO7C5+Vw7zxaM31RcjP0/o4eSxphqk7kKkBBTd0A2TCwi3qlkrtLuz8CV0jkxaXvxZK6k913+z55xVFzCA8s40CGZK4ge4EMZg28HH8pGMBMryubHpPmuzvJp+M0edoPBgyeiX+gBt+PHChVZlB7U1R0hb+NiyT0qJ6WNUW2IVKqKNuQz68BOZa+3GtIM691qjm1sI4sJgJO8qNc0B7WWwUVQiJt7KBvWBVBb8WkQa7C8EzNwlpy4u8NGl/kmiQ35aAwrZSQKZs1JHPKFWAnuxVy8BBN4d5jpiv77vsjrjZqTGEApG1lzEIsUYB+mb1u3Z8r6PxfjAfNh0vRQaxuEWMM1BkKmGfIU4qvBLGO+LeU7oOvzCrzRfBjz3RaoXwy/6LUkZsDhVTAGuYz7QL7eEhaUjGFWHU6cH5PmaYXFWPFLsTJ/kg4ChliRwtmwIYjY/CW6QPqbqhczMJKKuibNj2suzFHa88AYLCAX7abNA+77bFy3kyeGLmf91zK6RR8S3JviqU+EL2U8ZS3VqwdsBZe3IeAGFc8YbB+XGg1lBqY1mkOTfiz+7AE4kkbyWfFTs4zn4t6mNYB1QZm9pXw7GsRSTDuaN5NO0ArLbu70vkvNoxycdHu3vUDn8e4OC0p6PtcOL87FLjGbzn1OgJyA4XbRZU7a+1nZbgdd5d4nN9VRVKyuznCX76ISrb4n1d3AQTJ20dSD2LKrYsOVumZkYhHi/nCBFYa9moH3DKfBiBNBDIsz5kiJ6FlIxy9gLRVTumqN6gYB9pStguVlqNFQZuRp5Rya9KOrs108/JVdYnaMPKo6GzePW90dRdR8MEsxNTifJp0ZVVxWbf2cV8jkjDTASVVWAmKtsjJ4d4c1JT2fS1kV52L7z0jn6AEvn7McIyi79aO1vasqQWwIwNswpLK5nbLSm4xkBm41mb+dsOBDAzFTBLQ1jcUeeP6xachtwYjN2iQVv0s21P1cTaQxGJgKvIWwSvs9xro72wvc8RnWKzwP27+5dP1uzswdq+j4YgStYjpbWxzcAJTlWsLV28OC4BBm5ERwjkza7xEs615AurEx9ljhoAM6G/CYzX1WwpndlD2ZS3eW0diUpzZb4Llyjqq6yM7RHzUyiYyAwWF1gIG005zBJnZMZ1aNYRZtWTVPO6EnD502lHuyCsGD9kIKQCvNJKfNHkVFxbxZrbybUdSBtZQxuJ2mIm9Kfn8of1I63GZh0LNZ1BDtVJj0Yqi1oIrMjUS7bUhrTM6Eu7l/aIexBL/u5C4ijELnViGrMDmn7qKvhW4QS7DpDnpenOVp+i2v8CjaAGvy4SArH1TP9crrgOmIOHCrNESKpiBuHTq/dQxrMJQZeNZg7ozYtq+1nJJ+h+Ifec3nLBY+y5V2QwSgEg/WtVLpTVuHtAdhjFvH7bbDm4H1befbSMngKy9oTYnrApPi9ix6olbG0ywheHX3OopasG1lXMHOvKJqzPxiI8jJzKgzs5hLDGbAqB9Mva3hwu4KT2AbUx6RITDhTae7c8PmAW7VLY3lhbLt6BZgf918m3SJr7vYaiDTeo/n/mN+e8X8JjwjXQCKOhDPMyA2fK5qBbIpcp3fOs42GM8MzGwwdya94KpuBftq7WwQ8EQMu5uGNdUIZubNnTSnGXGhjvssuc6V27biVYOFGG1nGYzmE7mor8u4SJoHw82yrIFVpCljWGjr1DFwUwulXlN2ZgZG0xN/J9gOj/c+qtAnVBKn/KuTIl9r+U5RB04Iz4LbpYGXNzQ/2xn0Zg4Tqp74Jr1g620L813mtqw31JiU8ZhmFmc7sS/zM51IdpM+DLWWO1Pc3CQp/oKudD41AiR4muiArM4SAubZc19JuzDHSUBGWKOz6cJOgwdxymWCJoNSnEohcPhcmtpfT0rQWyZTX/6ooB7HLKdT1TzZ6HGk3nIuH1VekOezknVUPB0/xndRdovO8VI7qgvcRPwk9/3Q1QSdQEglK88PbSsg67Z9n0YUGvdpBjY0ngWTvijQbAeD0j/sOHNUJTxLjtEvzItgZ+ZmQpDgFtw3qr8Y2x1G8Y/TDPcl/mFnYNFVhFhQUseGKbXN7pTl2XQ0M/C26XzuhOVGNhidn6em3sw8vYvOn4ZjWZChd9AV9B81qtHqeB0l6UFVRfEdvXc/SRTHK3kViIlBaBsWVjRn+7T50hysH8oMzKufPiNDZrLgiYsZwlUzkFidj1hWQcOwjmw6bgJg0lGft07O6kYyL4+C82XSBbbeNnAqs9hYFrMTeyxh5hO2bKsAOytWzFZxs3pEi4leYE5N+sJUW4y9T9ebvKhw327o9hHfoVWdosuo/CHla3kViKFH0DaMrGgGSmbB9nwaK4O+QzMwoJ74Jp1o6yXZLam5GPMdP1ozn7wK/HyoI/MpmlmG+fQdmoH59MTfOebDDaV546rcsYmaJ8QKcsYbYO15D2gH0kNVDL781q0bymw8K581wyP+zXynpWNcp3rCdSpcAxXdqllHRfXl+l8orkgResSMEFNTWpRleUWx/Pq1REdpQfij/P1lVdQiMxPUF6hiX9UqX75ovjN81T5iJvAoVz16PIoqdJsXCQKx9OVPWlz4FwlvhNC0RVoUH/M4SpM/0aqdQbhTPJS+ax+j7LaObmFsbZlZ59BFVdAYzpIyn7R7HJwW+Rkq1klZJt17yxBiHkaLdPzsuYhwfDeu62GepmCv8Hejyk3YqgxFFz1sOCTVcLRIWl8KkCa9+4muI+RMK1k1TZnBimmTynxC1V0OTvkYQo8QSxGEV8U9Fq1gz0YAxsSm4iqrVDRvQbQoD9P8ljwiCOHqyvTc1BhsQVbqLOcaFN0jNRCO4V0qHX1UotNYbp4lcVUXII62yJQgKkwjCGPykGxSSYHWktkHwPSoUZrco+LpMlmDPWXLTQc+JMhRjJ1NO2SLtg8zPknSSiLENHVMG1Uy1RjGgLca+E9RVt9EdOpVfWfBtKh1OK2QtR242KA4uUliqgv19FP0GK6gl5NgtS/0BgkUmwp4x8bMmzEl3mUE6l5DqSmib1GRRNkQWn+Ur6+TLJIRR19L2/A/6oh++ZoloJxhy11HYdF10yZMcFsj5TtDkyVYdL5NiKGTwu0dHSiB+/s73ekhQQU+xsGqRl+oRfMZPZQy2d2VaZEcP2IZm0XpQV3dkTNbs4jkyrQKXttY/xYyhJl5oNoEjfQEx75FrUcUyTCY9YK+TS/tBS3VIqIZECAcbToJQ52DeSMD3gThpx412IEXOGDs8CMqhthVCM3oJ1sIzLtWJmjIGw9SNM1LGxo0Ym53mF5gDniDg49MpxySWhsikVFsnH5bO1omeSo8zFFuW23fxhnK4P7xmeR0xw0+5w147BBTFNmile44suRLWtpCqSZk3CRJJWLZhgluvZ1gCN0GTQVsRL05KmrcU6Nrcj1o7VFgr0x6I0RwSiX/2OlNq8GwsVKwzjKOS9NRrYuYAek1hCDpViNzzwGuxNHtj3YmU7lWwQSzaNB8LRRo+kL9xoMyhJUspUjgYfR63R0+ylO98ho2HI4ADKxSOWyUaH3WtVYo6kQtsbL0fukmnfgUUYkr7Utbrrdiwc6HoFFL5kBqcby+ROtNKlkFEJyp4epJhZiHMT6GKnByIAZSnYCtFPa+MYR+4EVelrhiqkDJw+gH3mwncivuCMDgMAx4qMCnYtDTyBy9Bqn+zMW4d4FnrJGPnZYn15souQUXUVemXzXNxqJcMGMQo9PpR1Th06JuqcOQBn2OSrx6v6Pk9g4k4wjAFN27BHNDKekpD6NFOnJagTByTkI6YfyUxSpZPBQbnPvHt8PwWZ+/yzdCqhgud2vPoWOuhdXXh1fD5SNTR3GPOFTgb7HZ3DmKer2rQz9k6cWmcFFu2kTn5cA2MVzA8n4KY2KZEpK9/tVTEYbWjA+sJKUff2utox6MfWLS8TfIV9wNsUg+TQ35INUVITICV+AKImrwA4TkxupPzDxNlaw3BlAMhYUDKdNcvauoMUIxNRcNmauby3946CyIuucMpGz4vfOAhggsKoAMICUdSDC+l7/CMhDLwhgrooxbgEgVg1ry0ekrQ7QTXAwU9DNoAVpWw4j9ycq6J1z1bhQAJUFAxdAgeJBenAeFilwgzokp1GUOUdCGB5GPgIOE6MG4yihIwSOaiQicN4ucFGNA/TjGU+tNljE6gDhqrnOgUB/Wy3RUJA8AJR+MCAwRhvHPUhAGwAVQRUpkH4IcJmnKPCymogoHajCccY0A9OEQzkSk1plryBKgoJIAqx8VX0VFp8H1zIBcAmKFFhiCXp3DmlILFIHkAxFgIdIwLnQKmoioJtYKSYNH+Zp6aA+efDA9BDj1OHhwb4YBkQL0kZLaRWseeeBd9Z54gPIMQyoUX7ACqErzjoIqdRrGCmnVEnQBqNSZYPVUgnI/KMfDJX0IRCUuqYOINYigbm3zKi4SYBTykwMF1xfjlqgSxTyqadmlcwW5Oths0gStLnO2nyJRlPDyUamqQcRivIsVtFJihbYu6RQEodzAnyZ066FtxtdVCkmzHue0S65vGHaNVpAMrGAwQqheAMKBaAHaweMMJeCvxm7eUvE+htOK4RG4QrQbyfQxMohEY1f2ULSRupCzr//KSWZUXTt4EywKAqtc5/WUN2pcvt5H4QDhZ6XthF43kVaxIMC4phHFLUnMtTCtGBVbtyKjCwGnJN2sROt1qyH8Q0ozEVY7LKGKgmKG2qAU89TaDd+wnskEUPNR6RnMkVzzcBfrQaQ6UIBw8sFA4BCJuDgiBXlAjLMcR0ctjwObpAwlr6NlAGlVBYuZk1HbCEBSJfZAdNWvURDcbqD6tepHyFk3BDhezICQmoraIavrK4grjYXTk1nT5rQEVwUAXsnC9oBrSwc0igtIe2zglac6GFJ1A+rQAej2y4wpgq8TdVSn7eJRYnPlbjWFZ1xmyo4Yz6mEqTzMCJ1HMY19lVsRWDD9uZ+B9rYhsLjkMqrpfyiqKEJxTe0uxii0FDDFpKC0OiBZPwnGXZjPxqPqkn7/NqntRRX9Tj7lnMyqRKk6Mo47d5qOEQovqrCYFpqYUReA2eHi+KeaIocV4r4sHNaCE52X5fqudaX6alzXefBKRXUKaisVUx5mQvrTSNorSaHFXKjxWJNJic5ijrqQYvuJUvdAPmlaLvFQOg23BUvZbyrgzfTOhUT11UW03qRoSCEipw4HqR/SuII3hTh0kO43JEnxpk+fDmX8fh5AHwmkfEBwBYg+bMIWBYUkCGeIqRhaVmzxIpDJUBTbuDVZZt2qz9F9gh4MdB4OULsCxvDeTnUw1hlJ9AGlm5s6zYin7KhASzN5TcPhShGEpaq8GcXalDXjQO4uG5PS4VUEko9OgIXoxeSHUhBKRDWxwytpsHMiHdJUwfQQ4NTj4MG9uQhECtBHSmofl3ttkKEEUuFlCFYI4G8/exChKg2Z0v/erKJ8yEb1IYpqEq0pqGzW5MQ++312NiV1ASj5uERgiG5szjgFkQBkc1CE5pnTk4QD0wxjDC0lSpcBT0cVDt0cZLli099JaMLCaEbAgEqoEenJwCIBaDBK5xeSOdpsgkrOaGCM5rEZSxieaHDBxBAo6iU02KSMKrHBwJmsdWYsIUQHg07GITCBHWhDMwoahNWBcPLBQOAQbbqkkwq6gKgmDqVr2lTJUw5C132VFDWmwVyyE8rQeTWkVZc7y8MV5MNS1lM5y/NpQA085+E2FJ7zk1GyzadqSMYG2nJ8Db9MSMCmAYB6MDO7LkF60JHSiwfRrJ8BUroK9edUHtM8JFBYj0Qgk84rrEbWhJjHVtTl4b06wz2+i0q0+p5Ud0xeXZE0uirywWlqQmRjcggrqKZDLGOnUKd6KD3y1ZDiWE5DuIJ+oGA9Ff0sJJO6DYCU0jkKRcmv7IQak3Ncy3K8o8pTEnbc0ORyj2S51oh+BkQjpwZIqcRrU27rJB6DaUoSAGm5leqoEl4+JFU1iFKSrOIKoilbmFiPhdrWrlB9JbvBOi4bS7yuc+WYPItaoqUk5CDk4xoDyhJggbGrKjxTLkw2Lf3VkOxeToQxoH4MI3gVSfQqGowScveVUdn56NwlI9NaFHhA3RmYg/c6T/O4JjYqjN4g0EhzEFIlIKAKsFQYv5SglDog0omF9tB5PQtJYQ3uigwYyfL2aWZ24h+g0ISXq8BVIkRaC5ZNwsMZSvkkRz5XeLjQB9W6lANbDFK1Oj3pN9caFRpW2CeksBbjUlgrPCk2k59Lnqbf8oqmW6bXpeyTqYBziwJc4Woir+XvxqLALcnTKkn56rIjgO/WXAFP5QA7hGldhXw3RAESWfY4j2o/MW0PWuTAG0MB9JbxwztXp1lCXtVWHKFUFVQKh6IerMwIzwYp9RkV+mlP8+CzSFfik0Z6Ykrrmg9chsKExKanfsMWAaprJ9Ul5ezwONOV8FCTSHMVuHzQilpgEtrR81QKYqrwQjqm8FZVUPKp9CMQzmxgKq3IiVJz6UI6mhjSQkcD7djnHLPwtpje9QiG1nhDgJWkLhbcAy86VwsY+cSUG7+gdnVS5GsV6VTgCm1NXgsOu+DefVP6M8tRz0u6y9yCcAyw8diGOoGJxiCemGT9G3tXCiOKCKQQsDwsKK6ZZ/9UwlrANbHBpH/dTxvDI4FUbTxQBXgvS02cbSUIZ3BpviAv6eFzcbKOiqfjx/guym7ROZ6m4a0+4JCvraQ4k+vqwg895LrUvnq8IDWH1wrDkpL+YUzDMbThIEeVQlBtjHBqckneWlTqFto68pHqqoLJ26UvRypIqm1o6jz3kvYVRjtdFfvBKkx4QYk6j0EPfLvx6iSBtxMFtHyA8koQDWWvTiooqGhg4mBapuWr8SOTStqNYY0GNqqioZsxtcZIAVpxD2tOxG2jpz5NmY6tZMsaLAmmZEG2HTD0XT5lDsQdPX15NX5LWqSqAlo+THkliI78Y50K+ikQQ7f843e0AwS/2VBOAS0foLwSHNtmTDkF4tkoN7xoenXWPUUqoxsAqxucWEVOs9EDrFqyAZghKaicDQXVfnvdYCE3U1GSoaIv++01mYl11H747TUGidGmqqP0U75CadkVdK/TDjXbLy8uNlFMLln+x8XLF4/rNCt/f3lXVZtfX78uKery1TqJi7zMb6pXcb5+Ha3y129/+unfX79583rd4Hgdj47Iv3G97VvCyjM+7nOl5Cm1FTpJirJ6F1XRdVTi+TharQWwC6yAV1+u/4Xiil7RPXJb3m89qbsG2xwMTWSQOIcEmhiEO3Dyuz2nkKaorv+K9OkVGPg00PAED4tMPh0hYmZcUg/XvIijNCrO2gdTO8m7wiPP03qdDX/zvCevffFUVmhNfo+xsN/NsZ1mcVqvENYoE1w72nA9E0otMJdnUVk+4KM8LqgQeZCRQw4BmOPvKo+RDl/NMV0mVcoRs/1kjuMwXz2NUTRfzDF8QlX0H+jpoTGRsJjGJXYY3yHmRW8e6ajQDi9AM+azOa6PyRqz1uoy747oLEah0BzvOcpWqDgovycrKp9ZtHyZOdamxj/zjBs6+90W2/ci2rS+CBDSUbEt7ou7/AGYKaHQFu9hTm6I+QXNl1ms5SLJCyxNubXcf7Vcy5fRLbCc6VcR02+vOfHO7yCvhS2E29D5Dclsu4oelW84WexaPSbBYGayd6lqT7ODiXuX7a71Lik3afTUOmKwmMYlWzPb+ANxIvKb6BaJwyRLa27rBFPXnzGK9pOFokSIwA+k/7g1rPExx51P/kSrtvu+4oDH5yIUDHBMwzlNH3gcw1cLxaJNmcTjYr9bYCMEQVgJa3NqjDByZQ5YJQjtcQHrZlSwPVzfZ7Ty4nVJqi4TFpdW3VaZ2PX4qE6bt24gtu4LzfF+zZI/anSBcnJAH2PlisxxnqTR7eka94eY1cWhA8UWqn2Vcvo8+bD8keOsvk6T8o7XipnPz1jBaaTMRVVQV+mS2tsC7GMcRtetTItmmjUfdg/qei8up3GJPUZg1+CKbMw+xDfqLK1vE87iMC6xwXiZ17GwrpjPW7MKzlCxTsoyGRLL+awAHpsD9+tRbOtuF9bMOTw9yOIavm4NB+lSSZpzj8qDy4Bz1NW3lWtOCoS6cERO5RiVWBiUosfjR7TecMY55rMVrnb7bhzwOYSjMnOs1Pmbw9Z9s5GyzcpqXAPHgpYtmXoFLyW58zT1lNYYg4uEBqvtgj4SSsa3VxsQk/RFy2jhxEL+JfuAxeAZTRo96iBXZrFe0zR/eE/D0C/zb3nFL12xeI5zg9yKhtkcMzj6WsW8KY0tsbHxrEB87Pf5TnMLypsuUtRX6sARsoayR1Z5GglEWuQxdN/mlDyf6/U1Kr7cfGuSH41QjYue8ZldiIT2ZUQ2UtqRHdUopmPKZhlArDmUhJ84DY3xERf//nLz32S6fTtz/91Dv+/ulmeiddcsj4X9bqG0bvr4nlGXhs82CvDBZlPk96KdYfhuMc4C4b1s9SUTtrlxiYWZdrOSYByXzM6lPHMepvlt+2iDA18qa0/Ek01zl8TVbDxVbIGFLxAeAslnzfeK/b74LCnfizER1ur6E0nqplFBTA+f5/X6akYvMg773QJbVAlmi+6bOZb2sZ3/Qvj4UEXcVYlQaI33cy5H25dtF3czzw/5MroS1aQ837Qv4fyh0MKTKyrb0XBeXMz3xeeReQDIYeqUtafdS0ThMi5Zbnfq3krix8l+37ojShhTuIeaPLd+/L5OJBpyU2KhN5bk9SL+wDx8tTDcNCFXI5tN82kJr+2uzklerCNRJRBK7TFfRGkFY21KLEx+q3WSdZJobO0blVhdisIXE6MCix52SQl4Qo4K5r6UeIdSJMQN9B/tLzf6eEvofqMvXOqS8mOEzwbwiZYrWvIcSrryMb9NMtCIK5baYe4yGUmRCwBbs1cNKTt89ipJLhKDrUpac5qdCsvAuorEwBL2+7ynMRpcJi5G5rMdL4qohq/z7pp4g9hEGe+90H20wYMFXCH41zKfrS6GKoS/3SdZDLhZc4UWfRTiQI4sY0DahfCG32m7r9aY3oKY3tpg+meyIaafKBWdLLkiCz3lLs9Qc13BqSlsgcX6iR4hbMznOfadpU4adA34Ot+3K8nloCGrOY30FmWbrVyj99Rl+zwdcIU9FNnihB14+DKLveUh/4iqChWnJeDjLJZaYL4rEFLhBsqtLilRkcQgZr7MQm7XNLb6Mv8WcYrwuGTXHJ+fmYNAx+ifUHWXezqSjnG5xI9pEGyrjJJGPztGPofnztPbjKQauyNpJPhbzHHR9nAmq9t5MiaLyoUv1fWnMvGGU6IDbMPX1wW6T4CD2Lhk18T5Qszd3dH68XWHxfH+G646DTeH9QndjlQaVFVsjaoloEUORRY4WyeQtu6RaEeEISxkQV7pG5EC2Xih33JEab48H5/QfSqVkHGNO3eUP+vyXQbwIHB3GJjZP4CMROId0BXZ3IAVeGQ0np3mAACdbiQw5q18S8rkOkWn2Sq5T1Z1lKac3AcBZg1QuKNZ3yTrXiy1sLXVaSpFLBQuedPYMRFaY31NvB8EipcOsOjqyJVPGGK/wWh4i+pOXVwgdTkCtSsewl7JarzeLuo1rGExxU7qlQQ9DGHfe+oEB9MHhHAag7wRKZCDjfAg5q6uxiXLKycXP2qug+SDxfqIsvomiknOjALvaBV0ASKDMW/lfcUHwzdfbDwYhsfqx84Lw3eL/rR1IKWBL7Pxhv2jTgr0pbpDRa+DcX6xEIR1C4O6AeMflVus3xrLLbzwY6JoHKxWHDZ+LWuhbWa3SzPOz+7w3cLo0tbhZ5b9buE/lqXN8mQyoY88yYBym/X32AVcSfDDEPbUOH7cJAU1hr2LnkqYMjyMfSvUPYVigNaWHMpCu4nKiwgrWwhmGaDYxqeDrSlc6gulVr0mLocHtwVCom4qltr5N/YVRS9ZoNhmXfaP8vELkymwkV9tpaOnOEUfUXZb3fESDIJwbeEMFUkuzKMMxqEVqmBQNIIkhiCsPPbuks1xFuHznyAUR0U2OOXpIfgyKx+RhKzkKO1qN1c2gs+IBGo5n8/T8rgUaEs/2RgT+/ShPJtxRVY6GTE8Z/d4xeLKzaUjj10KZGPGzOMf/6gjarrh7ZijIusLD1r/4D5K0ug6SQX0cii3luBBwBAW85BkCuxiqcVpIH9oxt46cwqXD0C51SkpuXmi9o6TvOj6d4jw2VQ4KckBLS4sovgHTXpMcvELQX98oeV5W/5IkHDwNn9PSN4mNYXgqU3W9RqedxjCtoXoUdcCD2FJN7Rqqyb81gQUW606su8c1k+HdVUJbgVCqTXm70l5lyZlpUDPg1hQppE1KcLsfobPYaJlDIawuCzApyFaNYn5+KdRiY39UUBljeNLugLQDF+traFH5IYWsoM2BRZ70AbFSZQCvRuXuGHsr+MukzVwWaeEdGuxvbDTtsfDWXBYa148zipUlBCjQQBWux4RPCMsCGIfJaDVCdiwPRWg1UnsMkHNOuQkI1dktZ+jsjqoqiK5rit0lK+vk4yeb4FxaIGtxoJlYvPe2MFmkyb8WQEEMMf/HSW3d/yzDO03C+oA5zz7k933ZMUjaT9Z0AsYzwfr8fR7hFq8KMAc2lIJFinQkp59Qd2oAsXBbVNWEsVYk3tUPJFJFOxnXJk51k6P/Jolwk02X2aj+Zeo3c4AQ4VY6oD5E4rKukCkbxLsIwiHFg7WopuRUOiAl/xQ4mYBLPDX2SpF9PJStPYJhbZ4z1BBguBhI5EExLENQgN1Ez2E8yjyxtSE5a9yJCzY1rjx9Bu3lx9Ph8XBkUdedRpPHvKvp79722Pw7oErs7KwY06JMYkEJw2uyL6nMsRQuT12SPzwZc/H7bG9gSyP8nqTZ2LMEFRu5fYOYnXD1s0Cvfqnyr+MX8cQlh4HzR0b1hYgjwO2cGukXqBHwTxeBNu958CIv5yIZfhqo3yGDRg6zCt8WJdiBYptzJxJJJg26Scbc10z1zLvPqh87yanxtU4xspuxcRSC8wJdUISUA6fbXp5iy6SP3nLav/V0S2wvMwv8Lk0rmD8Olj7/n+BrkOEQktz83mUCeF4o4LFXVwnNnztjgPgNppZwhuTdsNwcxPVafUtQQ+fBC1RKNwahauVnp7RIg0Sl2gRWc1plK22ucMki/jXcLgimxuTNRIfNBm+WogdlJMX4TJBlxsV2Hi4fEbcnX/7ycrrpoiyMhGc1kYFcyy8ZcOpwqyTES734Kq5V03AvMMhlbidC6/udJLWFNFE4YYw7zEIPSx9SizTsFbXtMz84YaNGch5nSJZnkIDcCs3ANjiNiqwODs3+YMk6RbEUhudvHXBhFEDxTZGuLLCJwWqzbJvqIlOuHI4l9bOpCEREIRTC9ETYZAuIY2sFQ7KpaVuAsCznQLMbZbAd8JBAAvD7mNVRERAcXbd4fP2iGTmTtVTFjOYXISwsvq22h5x3bz4gB6BN1S5IuttvXm6FtzXu6JnrCq04qq5qSTXlEE0zwGdu/KpwrH9+md79QtjFArtPSxg3woXJQbWVbbnltFfeEyYXyBJK1T0TnbcHiqWWpgekxW6vKvX15mQ15YrMsfZJnoYY+s/LmQMfNZGvG0R6j0PNiwZWMZz2EOIfC3KaXaAoQOwyJa/3CG9tukGImT8Zwsc8FF1R4q0L7WxwJ0VqLkpEmMBR0XbxueBLvPH2Fzu9LUYtl9vkV2oul2lnpYnWOTWQ7w+z1dC8fNXtdk8F0EYlkXozrRqLNvPuGz/gSjWUdmegZ0YOBznerLsErzqfwwJ7fXEUkHmWSSD2XsXqXHtfYB2zwdo7/uyPzaPy90UNBpAe5PE1HGZsd4EUNVg1O5Kmym+7Vff4JE0UkW4OdTAWgrUxsQgxAjxZbbOiJ0PhcQfcVT8jJVG1WR5PmugwOzyyoEVumlWFNwHM/5fIArqOXDiFDwYjPt26HCza9zQyl+alD/Azo7xuG/jYOWlZ3rZefkWFUmUgfkrgsyXAr/7PFohnWgH8czU659Dd55cvyHyFT23jCG7mmvgoCyT2wyteusKH28HlFsduXcm08VpCb3oPXxd5mw26Er/ueZ8EbgiCzk1QVZIemL7UldfbigKqnFAWelEkK3Z/VjW8dvnWEwOO5q6+tK6yVwm9u1yEZRLjTbuixcb/eet4W+JojSJShdOj9tFg1pbSWpEgMrNsZO0P/iTkCOZ/W7DwF1af56Dh+8O29VRnlVFnkJpWmQwz//AG3bNBVhss6+yEBkdltuqtktwUz+zSaR3484fTIRL0E0qx/k+SEQwBLZr3EyDMC7+qKMClee310CEBltoecw/WP2rLivxPQuh0OLQTg/XMsRi6TxulPOpjHQ5QBvjqMDm7j37IX3DUCicMtxhqfiu/tEcH1nYvyZjL/fkVaeRcV177+uEN6qMSiz8Bcr+1ZuvBWdn4Mvs+ymg9MQH5Xbgy2zkZ1ahJmsgLzuZAqvAFCC/g0tyh+NH3H4paCnMZxvZuE8UIZZbShomd7iPqOnROMgaRd3tPxiDgcgOwceqsHP3UPNw8X02NyQLsTJe+qV/zGmHxYGR5VWn4eOPUXZbkweVeA2J+W5hhBGdXa0dXWn0IxAQabXZ1Cm3tzZflnGx3Pb8sY1jbvNCNPiwb19kgbN9ibateyRe0cEQFkfE4blbeSNSoOWdeMO7tz5vx/iFdgSiVhZZlB7U1R1utPWHOkcxpaXPLqHC7LBz2KGbZjfptAyZ9mGZTGUtRJq3n+wOC4QqpytCk5uEN1dA5fbYW6OHrhEAzLytL2RiL/MfiFuG7HdLbAcx1tlLGc5RqZVmfJ+sUCHLPQWVb81qP8mLen2Wl55X3T0ah3WsqDvNor3MN0nMo+g/LrX4xTc+rB/xPTtYrfCmzO2FzOd9LIrt0qBsEWBtUDyui0NSeZrVQVvkUfQfF1sdhASQtXxUYHFEabI7c6eT7qOFUt5Jz7EW3n+1uA9I8EmYuwloPtkcbsuKNCwebofv9thkMwmV22OnWdBAvE3JXmbZyazIX1i5yqlZRdT7Iq83oJzqS3bBmUAhWNq9h5cs3eclhBRZmqD6NCrYCyxDntmHEE+ktlEREEBto3hcxaGk8jQycfsk2PPm7qmspwutmtYN32fBNNNpv1Yk9aZZJrQxKB5qVGCJT3TAYD4vdy0a5uTV5h9vbQhCUj6h1Obiq0lrLUENFFvOy0UVVbWAlyuy7y+MViy1MCE2ucRhxEKhNd4mR7nUPikDsue4o7ooUBY/AY8nghA2LTT1zrFQ5jGzJfZ9vowe2/0IMi/IoWw8/cAITOazLV/X11VeRelpFqe4YxB78xCOLRw/6lroIexbuCT1++cfVGOBIT1bVI4NhrRtsRUJirHxEI4tKMbCQzi2gOuKaw+GcJRPWM4nRMeM0hOEQJIZgIdoGySmAXiItkEyG4BbWFKbKpxmOny15A+Y61w4Dc6GzhXZ9o4sY6h/zXdzbOfops5WaAXFXvNlNlgfomJ1lidZVX5HBcJzyzvgSEAs9rk7FP/I6yG4QXrOU0N6tCgGLUtA7HdvmTsXVG7hzHNzk6QJkMxxVOCgkW8kGvnG2n2JnAuw0MUHXywOjjCLQCqGGtKi/7gW4Bjdf7XDJKqgw1dLTMCY3Ub4KSp/oJWamjIYuz4f3d+/FXvcfLXDdPy4SQrq9PIpz/gEGCCAK/7/QhFAZb7cjYPfJQWKq3foOuH95WRANiaovtpBTHeQD3kKmKNkUD4tQRwkh3Jq6TDKfohHLRDAGb+4WEEAN/ynR3LUpMwJa/uamRRzX+6E/fQ64s2jfKH9vkB1ktZfE94hxhAWK63GOmOR/EmXKY3OiGIooaMKzr81kUnVkP4tnqNSyMagg7WRjhuSBkdBTxjCpwVoRHIoK0+IXstTDEgBZuNkXsR3UYmkVlgQwOZclcB+4qMCe4tfc/KC7X1dmT1WcuDCS3pTV80erbTSGVeyufAJkwl5F65l2GPUOVpHSSZkm5WAmLfxISKRdO1h/XNe9ckkx+0owCzkXhyjTXV5l+AeR/gz9RP+EGWrL/fCIUANujVXUN0h/2uJz2sfkrLyf08GQOnyqIwZmmkurMI+m0vlKnghYrk9OS/5hZjrfXJDz2wBmQtA6cJcZmimYa6ubR4L+91Capdo9T2p7kAmEwrt8AKvjjKf/wKMG4ZXPfhztkDjVgVkuAV4DE4OZc/90L0fX2axMwMWYnvL8GnZ9YAmnoyATCYAgP3Y8WF4A53PoHIbbStONiQNgKjHckUOOIEoKb7MQhdHGTlpiOo2890WG9DBUYGFVRKVpfB4Qf/RhpsGslOFE0rzJgA8Y6naS4wALk+Oge+KuhO6PpEGJe5KQ9H8+uRZwOwS4fI3kMyrNGEDeBstljpgBu+axVIbSsr669pXeT9d+whecjtcZXdnoGZ7BQctAXFtAySDBMRCZdBezfpeyU6RP7pLtQRkIeCKbDaqrqpU7QEAbLxzY5QNiazENGlCsUXfsfz8DmSRZ79buF3S147JLsM5XDLfreXrEYmyhSRsU7Bd2zNWRDxP6D0a1+0Zrjvl9uy/ldI+jwMBcmuxSh0d8+7yCfTeZ4ufsabIWoXD2I1EjC5mIyMs03BqWP/yZgicVGq/2WI5xCWZkHJrXGRnd4JO8+z3+c9yO7eCyM1Vk07JZ9V0WFwe9pJW3W5JfllE8Q88MOiilC+zwEpcHyE9ZVRgeZuJ4GtXvszqbpI+CwOiFQr/AqvH31jBYvJYRXOaLPo2gbv37ruDAQRem9b2453JjHyOqrrIyEMOyDfRzwiVk9qirD8RGwUKrAvNQGEVqXB2r3MUlXl2khfNbPG2dq7QBi+ddkTP7ryJQyh0xiv3HlEC2s8bnEJSLLXh1Ojmpjn4csw6fLew+qzWSQa6FY5LbCjNLF84XFECspvuRItt+jn98wgL3xAb/xib0+avQ7HdkvssKlrVRYyuZUts7XMQxnGJjbIz0Bi6kobKFzu0b/nbht2oMAujgjx8ISTihiGW2G338g4IB/MSeAI6B4lngGMakUf+5ey3kZ0Xwja/DEctAtF1ilpRAeOWQ9n0+zJ6PH5EAhlGBVaXlkd47dzmxZOQg3Bc5CD6tvSdPDltQz2Su6PpcgTpEOAVNhhnCNE168trQuuCxgEBzCkf9y+a+UmFxaxYcV2QYNA2dCLUbRyE1e1GzhDTNOuOb148l4vl+8ABa64Ly20B2Gw2uf4Up+gjym6FSHO2wBLfGSqSXHDD4Yos77ZobT69CVtgZY4L/JBNOM0pVPTeaZZUSZSCC5wve8brnE4ALviY3/otcQaRw+pW1p5mYTNNgucPsXgpqxMcIW9vxd5l5iSYgzEozd7ixaQwhomMwTTKuX3WiRPH46I5TxPHGTFUcP3pP24NC3nLNTd5NqMcw019RPcoFZx6me9W1viiAn22xiXmGMmLfCDCUYHFxr2Bn3vZuDz3EvZ2AI9EeOy3/2hzpLlBRYEKAdeoYDct7VIuKcjNaRZzFwvMZ5v9kr5c/CEqeZWdLdgaEUWTprN5RgIkcGfRueZxV+OYaK9j2hTjtcTSpZZ6qNeCgr3JtWuq3VlBo1ha+e/H8WNcDuyuQ7Ddl/wnRb6WcTdfZsOZMpzjEqu1HeTtqQAvxpXnKALu9SLLm7bW7HD41KTYEkK2+GIn3H2EshQ9A/GMJUafg9TzGNihcTkAyutOZKOAnwhwehoglOEMMuTJzXhLeZLFd2hVp+gyKn94epExmFw8yJTVp+Ea/8P9AeZy4YVw+slGwOTZ8eOGMKqYcpkrsxD+Qjpe21S8toYLxfa9+ZIdFwUv+EcFFrOGN7HzWhTH7HcLq0BEvVCLSsA3LrHDeJytQHzdd8v+1fTlYbiHTJllH8UZYT7bbMEfktWKfzF5+GrlNXhLWP0MFTFw8c4V2uMFrStCoYX9IX/4hgpx1bLft0bSH8RpiEfaezROdmFp3WkEfNM2j2P4aotJ3DDY7/Zn7PM8lWam78psFuLpKhVuDptvW8OGX4sgbNijcWBDRd2/FhtepDWX47T5soijn+ShBvUDDUsl3UMZKpI4kDsyj80lCZ8WxbZz9n+gp+Y9zBGm4asVJgGJTX0glaR1Gklbk9VCfHx5h9boW1QkRKn3Y+IRKgcO1tSfhn1po9wpqfk05yHyL8RwraO2l52B/HQxMMD1ttWyINxpWt5lXpQpeIBnv1tgI06F4p0t89kc1wfxqfEP1u+M57f5WRKTxw2A63y2aMkwiA/VOj3MV8L+yH63vq3tklt8RtVDXvwAL24FGCvnd7zgnuhq6Z7OFMP4YBjrVo4f4zus3yH6aIG6MRno1oi2tlO+/ubd2FziOqRVt1XIqd6DdXsHVswb6ZA0kiz2jzkuFF42GhXZnvdP8mIdVVXCPywhls4XHyVdoPV1mpR3/O7BfF5SsO6Co867nDwRcpyt8Ixy88IVbY0Io2zSfgqgpLVgrrqatPq2H2UDG2l2Q7M/jOIfpxnuV/wjnIuWBKkDRxljmoa5guWpCJhXIbDv/a65SXyKsvomoueE4hKt8T7ka8eDMDqwqhmabVXpviXk7So+Bmz4OofasNQJoI3xD8NNPDaXA4EWxZ6Lto6LWlkehok4ZE4unxoMexbaOhY6R2SqVu3U+UYks7ic4pHVCKbhn14jeiPRlN44YXsrwWb1NvfO8dNRkZflBUrTIBzFY3PZ2LQoni9XLbUtNdkEmvdkPTclFpXLlqSuP9HUs43KHuGVAtlFAUqfeREKt4Y9/lGjGq3oA2YHVRXFd/65MECUDuxiiGcatmEa5xFxRXbGhOgWETO0yCJCoQ2L89qSraZ0koj+bN03i/1ReNTG9jkbf+3xU7JGogv18HUbF16w5ea3yObbjJO8SPgMm8NXu8AwMRzMFoPIdMNXm3AyPojMrrbYi+6bxSUa2qRPfEf6j9Z4xC6NCixsjEecZfHIpvZhzBkm6Yf5w/LIzT/XEfplyeuvC7ymL2muNM6/o/9shwvoGvPZ4iqF7tox+MI5X2bVwxWxskZp+iR0kinZGiHPDtVPyrOYHMS8uvpEF3Pic7nWD+W2B2tREo0K7HyYRBcmq90rL/gAF/rF4sq6REUmDGj4aqP4laWYM2f4ahszdlHy8zV8thrfO3QT1WmFpdoKc2MSpaUwWAhka9btUbTeRMmt5y1oh8XpBkBWdVvNts95l93RZMZtZFygS9IxMpf7UR2GbWVtrG82+2cTlc4zAFC8i8smXEjQNMrfVB5z7kqlDONgrXoDGVaAYhfcb9W43/rg/lmN+2c57oVE3Wf0UH5EVYWKcE5MME4HwWeKaCL5B7Yu5p5Swc2r9Nsl4JjxsL0bHn2fUFTWBWry0vtu+gwqpy1fWX9bN/wpEpKeE7O+4OGeWNnldu4Cup3/dwlusvQWyDw2d4ZUoNjz5DPnydP1Ji/Ie0k3iW/s7AiVAzdq6m8rK57kKZ5SEQ/73e7uEcpPzn639eGH8I1LbE1jAdIp/Ui4IPnmi8UlUfSDvyKiX2yjZb5k/EmH/W6ODUuckwSlK/IXdx7jiuy54SjPbpLbugBuyiUgFjP6WBWReFnNfLYJ7iQIupAXLqZzVGRz/VbWaXWa3Qg3ecN3C75rErThPpAUbYL6KpRujaC+eMriMEFNAyKXmCZV7YluToKFNF3kdREjIbMG83mp8Cga8fxYiehGBbYjFVNis99tY2xOhTcphs+2uC6qQhJA2pXYYjzM8xTC13y30SyzGDwnjwq2RiwcPxKl6R3apHmAh5t4bC53q1oU00iJVm8EYspurE1//kphKDUp7OY3zAqkEoqlu32L65t75ENVbS6LKCvXCc1bCtFMBuPXir4NWyWyORSLrpF8mdVVTXPCES5rus+2VyTwfZL7ZRKtCd4ojUuWvsohvJ3co09C9ohRgdVaFFwXum9btm8FsTuMUDnvWHu7AyUvFgMVFtqilBgV2hrqxF66hRXgP+8TPDpp5AJQbnPn24rClhW4q1+ucAGbiffZVTH7eGIFjZz5bDVHRK4Khgr2u/2MN+YN0UwBlS+lXn25uSkRt9V03yxv9oH7fCv/h6iK7y6SPzkeZj5bzABeTjTp3Jju/delt8+jfL2hrxqotAgpkO0N6j+TzUER3wk3smKpBeYURRmfM7T/OP+WfVCWeZxQQ6IYQIeKVg1tkg9fsYmeMT0V8XKamkJ8HAfPggP6wkpkW0VzV40hBNAWjPZ6ATnEsoTMfa+8O3wZFbcIOpAbdZjFZdnZ316D/GDOMhd3Cf2ziSe8wrK1KhLyjvsRWeyNX77CXGlQWzBSjuo0gEI+OWAK9G158g3XQACuMeiyJ+c01JubaTqGvWJktSrdoAgt5hVsYLp3I/W0FbF6Tn+PMMDEA53zm+gWz2ITfZikRHntn/U0mG2+imzKbeZ6jNOTphyykNPOofZjTOuOYXV7lZD5fHFafq7T9PeXN1Fa8uqmbvTezPMuKanGeHWw2aQJ8XhtkwVpNhV1PZ6NOuguEZEBO6ka8JyrHnUAblJ203PfaIk1tzwRh3TWpfuwYoihlowdzuCsHUZk7rBvNS/0nfTjhBbN3IzQNdv+fxndqo8nELgkjdQAY3IQERH7nj6cCGrcuSCzjTEtpkkYH0llNWR6hOURFEa/jTrkczlqnqOHqFid5UlWlR8S3A+8/3wt0ep7Ut21npeqHGHaymJWMKGKAV9oG/KcgTGuAHyi7/A2qp86MoQTON0h3ObsItQJcXjhkHryEY8tpMThcW8jA+nHr2ehztZK/LiiJEMFD9Ibc9sv/d9l96HNhUSugdNyqEfue9YRJUi5iWJqqlmhk6QoK8Jp11GJGpCXL7pLku7arbWs/ZEepc070h3ApyhLblBZXeY/UPb7y7c/vXn78gV9CpbE/6U3L188rtOs/DWm0xhlWV7Rof/+8q6qNr++fl3SFstX6yQu8jK/qV7F+fp1tMpfY1w/v37z5jVarV/z1Vu0Rlh++vcOS1muRll6mEvjzkU43yTxmKd++w8kMEPHJOfo5oWMn357zVf8DeBJ0vbvLxNCUrqc6YNmNG9kc3NKoBDt5csXhO3IFWDPeq+V6NkrzaaZ7D4q4ruo+G/r6PG/s/iqQnx1SehtFqf1Cp1mFwlGG206pNfkosmya6dl556DCyoU0xem3dENvj4BxnmZVGkYijURxAEQfUJV1IanlMEQjvLpBcIZjnZCQLI7d5yjDAuqg/J7siJbmwemBsM/8yzMGBt034to0z5fJOmbOa6Lu/xhNAfuozzMiRLkuS77nGSMmLPEQYdDzs32FGdvNdVSP3qELXU7K/shqf/yxafo8SPKbqu731/+7aefrJGOA9JMp9R4FrA2VAlBAs93Bt46zED7PKJmJZhpB13cdfB5pM9EJX+iXpV+JjM6xI8wjfCK6a+neLN5/P3l/0Ur/fri9D+vBHpcEd+SvM5W//aCrqVfX7x58X9bd4d9IDh4h/7m0iH6PNjwci3P+2F69jPpmacg63s6VSffBuuk84o3X64tHz2TVaqVu29c5qKl0VGdkudpNHLdGv3XLPmjRhcob179UyG31QtP0uj2dI273nnkKtH/8pMt/vMq9dEQA6r5zBN97kgmVnGaNd0ErJ2jErBg7ey689iO+pq9/PzJYfvp6DmJ4tUhD6iAnZYkTfJZWt8mmcfB77S8zOtYzvYexyLev/CZcKqRRczTwjZmuF9+sUbdn0f9EBvPtfyi8lnNs/e8nBQIdVZ+n43mMno8fkTrjZc9CiNpN6wmwg/cr0yESJcdzMei2ywGyj8eeNyWlI+Qy9P0mTD84ltwaMnap2QKYGwNoh4S++qX7ENOAttuvfj8IE3zh/c1Kiu8f3/LKy9kblorZBOKCnIViGjGggYPSWxTJWRe7eh9nK0CYXI+I1jJgIOsfOCvzndWEpAB2UuBptaWSIDP9foaFV9uyNoofZh64iNd7+nVXuE8DwZi30K2Y6KhphcjnW5GrjFmRzmz89LBZlPk934bwTgFqly+mZl/aPYwJ2TWbPrM+LNJr9s0VFMrWkJR3iRksdvOQ598RGkis2a5NoFvWKQyn4dQeE/yYh1VNhdEclwXUVqF7ufBap1kR/l6zdyb+/m3lEHOZAc3N0maYC73I53/iewdouHLIxRy+e0godsTX/cqzkSHPmWX/VjoY1RWZvvMG3vkQXcI0tOP+W2ShVKpMT7KYVj2GqA06iMQu2WnOQgIZAqEyQghT0y77ogYzPtjvC2DoaU7uytjOV93/plb5YlHfVyNziamqycYMlA5cMJEbqGjLIzHYRvD7aD+dxW9Fi9Gj86Ih3EWO1izuOo+PTlivNe8CNou9Dchkb0NguyfyeYsL6sohS6f3Uxtd3mGmvN7mNUbPQbE5rExm5+2oAQSOyvWJ3GpoAbQst3zvS2pZZA7mYe8ecDltJzAE+PyrkDIHP/PtvjxEkFFEnO4nczATZ7Dy/xb5KXWL+iRsYQVeZxY5a+y9p0ciQOHHoTikdPbDFPv6I7EwE3CIaxi8kwYZDpNccG96fq6QPcJeJLxtQvsgpvaYZrfEp3wmbDo4vfiZgdZMyuLSUyZud7UGlC9HC+7+5QW1xFrDXSRwp/zKjTKIZWH506znTfkqvhA1Wa9daGCvp0N6E48xzHxDMoHtLNith0NacH32qjAXaBRJDTIgmD1w/gtKRMMjQV8cp+s2gdp3XljEqX44o6+ghF2pZ1g6NA4g9+8dYzTPmztN9XhXLQ6HKFOF3spPdI8OodjrGijhyAKCHl7Mbu9qNeBtI8g+Dpkl1jLTLnBevYvFMre3nMQb0swz8WPOvgSibL6JopJ/FaBd5gKNimbXzg7dOF9lQDnyQkbPC3fJzfVUVR4nTY7HP47+zn6o04K9KW6Q8XZKMmia2oHim9QErTX+PbCqiaTUyUxURoOViuuSa/un5bv8ocszSM/Y0CLw29qvmZps3w7dF4j+9Rd2Xy5EfA5OXG2SI4fN0nzYOW76EmG0chk2CKkXhAUoT93f4jKi4i8GhBiVseYHC5Bufo+t6B4YMQ37uC2QIjV+lzGNUJ0iR5DOW+do7guCs8boB7J0VOcokZs+Mk7Ft8ZKpLcc5n2GOnmT9F6LaxTenPWPy3jI8tChUZhIUsTrUVph62xyPencBQn6yglOcjwr5ImE3vzd3yeJbG1eJt06HoQz7/T8rj0IiGThsaPSbCqQ4yW2T1eYhhZcyHleayq8vjHP+qoNQ14SPLmNEXxHdxHCa6bpAxOD3M42Een3SvJgo33Y/7QjLV1t/ObBqz9JzdP9AR+khdd/w4RPlD5oD2M4h803xNJGejpuEoOdwTfaUNDfLjoj6VeKgXdvvDMJOt6HWJiGnzRYyh8dOBo1aJKUHhVFLM5AT6snw7rqsplUeOmS4ZAf0/KuzQpK3+E7VpOEeZLLJhH1hIn+yvWtymqJPay4owQBN86vqSraRtojxlH5DZuqjYuNhhPlDoPxOjihGmjv0S5TNYhrj9Y3O2lSiDMnZHqOKtQUXrzYiu9RljRxAzUSrhZ28TnhcsENWvXS+bjzROV1UFVFcl1XaGjfH2dZPTEMymz4v53bxuU7dsGPqP4jpLbu+mW7/iYEhz992Q1IfYP09Km35VCy5wecViBE+quIYyXycQRRNsSRAmOPLlHxROZVXvTy7i2j+Gl00u/Zgl/A2rQj3Ftn34cRiVqt1dvQ0GP6xOKyrpApHdKVdk+KVnfxMGa9RwJvU30zZAf46YcLJKHdbZKUZPuFjCL+V4FNOjPUHFaoXUIM80IISFDSHwXd3lj+cFSewJHzG53fyaeF1p/BJeUZx2N/O3iX0syrTEenOeNfP+OloAtuOLQNeUuQXY440p7x1Ue5fUmz9hwAqeTvIAllC9tN0n04pnugn6MOuAhW7eFXd08MGifZlyLlPgs2SI24pbA/jGHeYU1sODeVeTtlNALpWO7MC5We0emkaNimDubs4S6pjhkbeoq+ij4xM/tIvkT+Yxg5NZVXuYX+PgYVzxmt/ddGhxfxrcIoVICUR3+PMpuNTZ7Bw4J6I0Y1ia1hT5fO2GzCGWg2Wbbx01Up9W3BD18cosIN/d/b0TXM1GG2tEcJlk05O3FVLumH9x2LjyBBI9GLNmvfpSTfPCZTsv62cXl8DN68FnBp+VlEWVlwvsZGWyF/Tq4YpB4PbKhXlneXdIHk9nGkjyzJQUGf5goRV1Fr7i96XSybYjy7NSJ9rS/fj7ZK7uROZju+5q+M8eT9rxOUeAXGS82aHIDVZugA47ytt11One+EMjOUVlhvZ3qnmzyeilWM8NXh/RM6lHuNFUM4uiJsEPjkxcaeUdhyUHJE3ur5HtR+PixKiLymOyUeiV7AfdMRJrWIPaLvRZ4lKd58QE9go+a+CJvN6/mxZvA93sTb4zt2m8upMht1DPhoYW1qfaCz7UTXHWvrgRyZI6nvF2WX3bNEbJsaNyaIGI5STGb9y5fXhalD8kKXd7V6+uMybHogqiNH1/euvXMrE/uIrlnkIZfnomEHsbnKh+bul7CsSetZyBAh4aqF364TkssVJv7BJsFZMtez+x2duH9XnIvZpIarK/p94ZEeYLFXD0E684vem05kA1S33NhCC5kKWrfh3Htvww3PkM2nMRVJbinAkP3va9GWF+NvX+Ftne751+xdz/4ax0AaajfTRJTQvYnjGeyRS2sKcG0bWSCQ/4RJTa/AyoRhM253zPmoHHP6q7O/b3Tp9TJVAR9JgsAHmIo3tuapy0XYZRnwiI7MwutULmMbp8x5S0PULbE+xYVSZSB8d/PhKgT5DmEkwzOkzvR84g2ZUy/e5S8CXbnIHmjdATOMfLPNlj2oCyT2wyvwO5wPkG2nX0MOGe/9H+TdOETznCr/J/rMG/uBUsTRs80X+rqyw1FSQc5xRM0LD88kz10CtcohVkzjO/SFvlFQUu9DQKZ7qqVV+2eCS/6Crju/54uV58i6tE5SMArdpa9ghv4tpx7HeqITfJe4E9MHk7vpLBNsmafPYs5fWRVkacEk9+djHSZzsAnb4NHnDy3JTxJAHZAs4XXhZuvmG5chZ/HREuG6CwFQVrJCkPK8eU41uwp6TQvLv6oowKV57fXofvZHF1X/8LTxKYmD58Gjx7CZ2jI0ddtkm0mCEvrtxyx77Qh/532Y5L9CPSElP0hzzxcpXsu4HnI1W447+ukb6jOkj9qlFCUNwnSvXpiewFd9g8dfC28HL0BNH4v07b4QgaFEz0UNXm5Qnncg5HgTsiOH3HfylDeQPtg8mWCyfvsvc9EJC3tWaEMmzRz2JGHuJo4VI9qh7BG+4wlZPJpY5bGgqR8RrF8z+lNYxpHFgYVyR0XBlMQd8DtTO/4V3r5eTteBAZu1P+aftnG4prokUUWpQd1dUdEZOPNdI5iTLBnIsK7Tdl9O/cU4cdrJizWW+sns3XKHPACom3NCoGxfyG8dZn/QGFWCEV3EMeoLMMhxX/e41Nz4ZU5xnjRneRFvT4jT808jxV2mW+S2H55tdX8gh+XXtxGTyuanaHPDlYrvHeGz0q7a0EIdH1Q7ngmC4QOyJ5F22o7vkDIPPqbmNtEqmq1yMED5XMjib0e9/zm+zL4x6isSC88rfAtFsmUO2KjyZ08T0K7KYGek/B5X+T1xlECtXWDR3X4P50R+PD1ud11vNZyCIFC1h6kVO3FyiR+eLspnuiyfCYyaifEw3Njt4BWPGPObYj3PJiWjgUO0XGzbBB86tv85S7EFjs/tJlx27OxQ74urr7fDWOb+NW5NwICn0tCWvMCt1XzPXEcVghcbeLdgKiaHL6Bkzp33HlUFwXK4ifo3SxHxA3CcyxkDC9f/+68Ki+jx3ZX8j9hf4sk8Ybuwgwf2iu8FtLTLE5xVyfz5Rs1dvw4T2OXpLE+kflMIxw1Os9IW9kwzwjbxmYdGW7IYrHaNzYSY1jwJ2RviNIThKamqbzlqQksb3lqarf4wyRlp3wyOSP6pDg2b4WKjsmawJp6jbWd1cTvJJyjh6hYneV4gyu/owJh1vJzHTm6Q/GPvB7cq0OfGoUGggXFdkqAxEfJ1snk5iZJE+9EeL3WvwkyRupDQ44x5D2LAmE5coTnf6ynOE07xkJqh5kI0qVgiqkwPs9Uh+UPtJKRzrunR/f3b4MhO37cJAU9OX3KsyHPQ0C8/4WiMGNn+fJdgoVb9Q5dDw+su8WR9GgOYroffMjTVaC5EpEHZAQG+WGU/Qh2lOLwBltiLN7To9Ao22d3QqM9vY4CbUithKZKQev/F2ZN1FjZK5I/m5edid98FI+T2k2CPhi7yRo4RyUTo+8pjTYkj0p44oiIA/Yanzt7nSh8189qUrdEoc2pZ1ESypO3Oyk2x5kwNG1RkrMKXoSbujEZTmLrmjpb6vZeaLDHhXO0jpKMyWTpkA/pQ0QipdrD7ee86vP/ebl3xzHaVJd3Ce5phD9Tf9EPUbb6cm+j5Fo/SPi1xIeGDwnmg2fzFMPiLxLSqvatt9X8PL3cV6Ex67xPbugp4hmyTjc0+8kbanrN39cSrb4n1Z0jC3HVvbsyeq0uuB1nTmZ9Jgza6UnMLDs9YCTD43MT2ZHa/xZqYvvhadl1lWbZizxzP3TI8JlvE/Akco4HuyFBx8FUwB5juGCUC5QRhT1UDxt04br3CZUlk2ncO8NVNyNU6fO0Oc8g/PqF/UykXz+eoA4yC6prZ8vGyocIMCfJLWmQ+dT3mH1Dk19bzjGaWUYy9TVrd15q9uupSTZubWraTXYXGP4OsEt84x3AfVp2qIIoRh8xi2dDuiCvt8yJ1P8+bW5u+qan43PpUtF+RCI8w/Co3baPdZTnklFmwQ2aUlHHDqbSi7ro5d3Vy7Yrj6zZ9HlZeRZ3Z26I6uUVSzEcRmmUDel9nNS3cnJ3nZCHsLC3DKz948rT2nZlYzwxf1/oLtnQu9nnsfIWlOSXRRT/SLLbgJeL1JluWqWE3hCiUFeY3UMZgdDNsQt1C+AZWTH6ITkFjjQ1A9xPiXbjya0HFrpHVRcZySiPnk2ClABxWIEmbnH9J4Td6RxFZZ6d5EXDK2GU9JbjED3/Gpz3XZCa+jZYBTBxCeY839mJbm7IwScMuoPVOskkLmZ8QjPrtDYjQREi3Oz/b+/aeuPGlfRfGczjYnGymcUBFoucBWzHmRhIJh7byWD3RVC66bYQtdQrqZP4APvfl6JuvBSvInVLXmbiZrFYVfzEa7FqKW4sFhNkTnyIruJiU5Pk+NHyNi7ahcOok6fmXMjtmpWuO+Zqle7k8ders4//M197hHuFgb9OVNRB8P0F3Q0ze65umBNe0GxknIOuzi0zvQIb0yXmdiJb2Zq+/Yh98LwpH+Lv198RpakLG8zkCnfnIS+eRzquTJe/ytpS4zNKho8MInzlW8qBJCjnMP+KLMJG5HIZfX5mCfL7MVucmuzORf0YrfVN39atDa+d/dcjcvDu5r3IezxW643A4ep5lyI20bhTD9RsblGR5HL/BLM1RH0ZQLiNutgzzWIw2aQPvRuyFSNLqiROHS9/2NqLfx1BLI5/epcfNvKlURoB61mDHhQYrDuYselz2SVCsv51I7Bs3ke2eT2Ui9iXDotY7crYJRjldVYTW4ylxl28ndEGa/IOfUXp2GB2eVGZOp4YcayzHXl9TnAyifT/m0Ok/4Djo9mT6wPyldIRIxUVBSp88Zvl2NMoL2RRX2Flu3GHuk2yyrdxKYRN4Tb1nryTSAhiOjDARsYgWiWvT01mX7tASSisNzqjY5JPsei5LYgDezdkbwSY429F3xT50R2GbO2ROUHcxaDrjovlGyijiMeMQOUdikdekrR7+MvnJrqNJ2b9c8ile7L3Ue02Mgo4B572E//aw4mQ7bGUuTfM7gntzyl6iMsvG+lt7U707w470Qv8Pah3Hn93yTGWZ9ffTzXEAOc5fktuzZ6cXQhMTWaLGh//x7T/H5527wat/4GngrtzFrXVR2U1x9g6fciui2LcIN6K5JoaXaaRw2axzgRDjrxdZekZkH9dZ6OlwSz8y/LS0TJnkuLSw/6SCETDxttXeVO+TfZ7NCr4Ff7zUI8at6jYUesJB2e9jpPkpGa0tnf5t0+ooIe3Iv/2tftFMvQ3od3qkcPXeW66qeS8jYLjtjUNjyD+DN1W4y5PXVw2mNqjFl43eOWWhlg1fSx+AmpCQN2n54N3pl4cvByCiJtH4qr7N9ltzYHTB9awCk0iN9+gwIy982Riwo0727A+zjIG28MTOqJPcZHUrDaCNKKTTXcapbu0HOBMeIII4a+1rLmGAwvhvBGQBJmwgOu38ZNgmVpfhwNcauc26LpxLIbf0ulbvW0f3uWH/DbZ1VG6l+EW/rY6ppf5npq/xt2vNjeQ3TvzP1D1LS+++O6b2yI5xsUz+XS6TGsub4kgLiPfNRGW19+xltkBkbDeY+WDmVmIae5833L/UUZCh2sdVaJAlre9e3PIVIHtcPEurxlYGMXc3+RNXhxxl1GR1T2xd350Anye589pUj7N4cPv9eILmKB9xst/ndcx8q+zPe7MsVOg3RrsfUxeuW5kAFr6KUSw9fRlvPtyk2H+uy9bdA7y8DJ93vfPs3shTXHp/z7Ozo8xWWoXD+iIx+zNHDkFGQ4+JXVOEzHh2mjGgR+9dQ93f/bxdvu4HfZ+dvF2u/gOpcSqTU9vpIf76fqll9XCb2O4hB6Gi7ws71Ga/uxBPz1oPjjSWba3YnZaJ3kiPGX4MONXPYoA75bxyYz77M8zOqM9yXNyUVXx7mlDD7Mp3ew3GEzlcZ7dWK34gOojL7pn3QKJaeZTl6uAN4mLN0lTa4yPGx3gHuvyOcni4tnpsF/7Rbo8kXyPt4Cwo6cVY5cvcSPf322R5MXI8GD1Mw/vPsc1U4NDeXu/4Ny7qA95CEHv0Cl9tpPWgm0Iia+ExNhjOV7udr5ZmrzhcRmh63tRP7eiPo/67/Gw8FAkI6OMYCZe3q02S5idY75Vtva46R5l+/rIL07TvtM8ekjSkm5kngCz7LHT7N/trzHbbaZ+bcDyNnXJ8C7vbV7I/M3Nbt5K3AuWyhouOstSG9nChXHjdXNfpuafCaj2a/QYn9MKD24EgbhWgO/uKj6e4uSwlbsj6LNwdJ2EJz03ZkYz3fS32uHjTbbPRH60g2WXjRleuDWTYPOGVAwOPzZuyeKXcT487BXrpak+Dl9rJ5HPcJT1EjjdsDP2wOs3j7z+3ZKX8UDyB/pWvkP1h7tBjwdYOa+BUSSLUaPHqN/KVBCu2VpEra8G+w5z5Ppp/CDgdz8IuKP4Nttv/pxM36O4PBeozZq1ke9DN9+6BKALG97ursZFaHfTUHeNLYZe488lKzc0zP6E0aQwujme8OYft/mYbOaNWBAMvcnTOqFwENbY9DVLH16yPviMf5D0JTmNEeEh/oLG1G88wT9k41byGMVvEpTu67/8v4LqOv0qzx6Tw7mIoat3py3a9feqiL2l8L7K0/Mx65zSfXC8Q+U5rW6yR+ESyC1TWxPtCEtXxzuyXwTy9UO8Krp/znY/gF+/ibUHS0SXzw2XweL/+stN+ZFso/7zlwfc9y53IPm52CHwzbe1eA0vtXj0MODwmEr1tmGsOV+K8pqqClQdPc2Rd5HfqzC6/uauK1B1/IqTNDE6CC1gRGKnm1HxgRoe91Xha0RvGF7muew6xWgcx30U+DnE9fd6sfsandJ8S1lA2tW700uaR6Uv1XzLbQ+rUM/rjAE249fYRhemLo/CTe5LHfgaxBawDy5QVaeHIs7KY0JCJY63KsRxlAMY/jaaowPbxwSGVz3NntI3Y4sbKpd+I+yNQga7iu7jZqj+EpKv6D31Rt3xit/KScByKvp57uLr3MXlNU/9fdf/ajX3/oEbu+xbD/NF/jXBVgn5KOCmbIe+FqJj7ns9nCjNt8kHcYPx4mvxjP+sh1Jfpz8dNppzJW/HQJ4XUx8eH0s0yueMXP2PYXAZV7un++Sfo5YIt/gjJIGzFuEOUcdYJWHJ7ZYAbq68/5OcLjC7sdfAKYqzIeifxxkWd027DmzCetIxSF0mXJ5fJM7AYD26XaCOftYGG7bc6IksvARP6HWyE4erHuTEtU60jYfo5vXcVf1tuSWBZRkZd3rToFN/Ay1aTnY8g5GZ/gZVLLu5rxkmUlcDoi6DmEPfdixMe7VtyqlXmbbcPpexPUmLbyfBUDNET75OSjL5dVEsXLqy42H8gbZtOfUl05idJamq475KWn5LOA1VQ/bmrfsbeNvObJtaa1/S4lsf4LY1Q/Rky73930PsdHcp7xsVOW7Mba00rSmlAnTyO8nQVA45Z45dC9tOnKPXwAuYPZe/5r3N0/RTXoefnDWRgUk685EfGVb0Iiu/uURvo+uG6ITap7hLjr1V+7f6PSSVGEjV0cmoYWiSMNAoKgPuhJsK8Uk+DV3Cm5ohwHGZ5ocfBRy++rK22W1eOrysHmoGXB3doa8J+vYWpafHc5o5bkRX0bGMws6Lm676KFH+isvW4iEODmlBt96bc4Vf9Td3NP3kbbipvfmHmx0nN4QGmf+NygdsxdQDqz9yS04yrF+UZb5LSM921ygkGnrjC3aHSuK2FnUpkDjwX2f7X+rl65AjqZPoHqWPfxt+fH9Oq+SUJjsswj9+ffkr/8l8yJr0qb9ckHuv+jSj3MV70RxYjb1UBkByVh6QgJXtX4Qm8WeMiuYt/1WelVURY3OL33yS7ZJTnPL24AgNh4da054lX/IanVBWX8mr9DZpl05sJbbfN8P1gM4er15QoDLAWvJPEuCSyLYioNFiiyhjS7cBMUanVeBLuOWi99xldA98KlQ387WZXhYLJ4Ge8g5TJR9LGASQgkkmAKb5na6kfbNL3AWA9SEuDojfJA7AkAJB1fE/IEitATI3QDUnpVOBM0/TdUzOtaQsyMgPq5+CiRrrmHX7A9VIFNq2pwKipJFRkKD7OcwcadqLHtDSKmI0C2Ly2fDCeafgLQzeyuwq1DrpJPqVGsuB6VG+aBJ0wb5GcrF6iiCY40wwAfoMfK0kDZs4Vy0Ek7oFGdDrkn7+IVBog4LZ4Cf30psKd1VcIeKxne1QBHspLgpjtLwsttiS1WOKUccCS7NBqXPR8wiif/vb314KPTdw6hwvaU79b2sHAOhVuvCuV4B2/De8ODDYf6ITQoIRbjZg9K5P/VMQ3dK6qwGeKU00yfCO3ZAogceZTuEJUKV0Y5c0qXa/mwlZmgWyxQCxWVzZ9PEMsJI/b5gaVZdJWr/VhJ92OGFKM31ZjXvrRYNRW6zx50cDiRuUVRGsxEInr1ZoUJS+bDOTV6eRzeQ1G646t8B1XHB00jIyDD+u/qKjV8WkrfkvO9onMQnJqR21/5ffebTlzKVD99s0Nx+MwKwgXFGYGxDIQmGQxOlj0mJLuxg4gU/mhM6EevFHgRNkoeXAafCpnWfF1L3s9Dgu6RbO3ZtdZqHT/7j2MQV+kizr/plHk/496sUJm7xOi9KKb3Cn2tVl+nH4cZLBRXgHDskSGFu9yhOAS/3uXdKm+m307DDTXZPaDBcbhplVl88BM0V8hNlg1k6vqxrLoCWbULaZkcxmebbEgaxHmGYYm38hPj++JlyKu8BLGXdiUnR1/7hD/3tOClQ/jpUfaS9p7KIEBsVhyjczhtFa2Yxjcx8iRLjnk6+oeH6oI6ZL+5YmYjqVKbDaA1qMhr5wIVU1DDas5jVKtrkxcXnO9imq4xlcVFWRfD5XqAleGg0lukmOogQ6mC6d8gRKqplaSIE45NQos3FQjMp1NZFhqL0c6LZYXc3hqOsHs751mRvOme5cEMy8Akw7W/5EibrRheCjPweRBKdc1tEWJzSErA0dbPEa2ZxrLQZXq5nWZgTV9IOV3b3PzENVnbb+MSZ3T8WEV390sww7tmDtV4CMNkZwWIZTAYMKuRJjOzTsoCMVCSwPAjXr/vczAlk3S1dYFPZWM8MtAGzTz3T2w9syFub3J7RLHpMdKeqPOtYDNlh+SC4Z5UYAKFFvDVCERf9wqv+MTPSin+Xp8WAGhFBPfxW6GkjZEYZ5CugOobHvhFXKmrQPM1jo4GqubWjg/JBj82iwzT5gqzSYG/NU9gV9rMClrCIGmUFfbKp0I6sFSiWLFcKMkdQAcJk5F0n6FOjMHwFiph0+J8rgpDXTAu1TXCRxVvVj61V+/JxkhHB2BxGFbBC0lORbcitRKWoixZI8TlT4W83efPFAnX7eHYvRubfqBvD88xyToPkfs0SOUYaIxgJbsMnhUW6gRUOPFntp+FvvmGgCyc2Ofmse8jrI6Y8kBUKgy6c+hlwsGic9dvQAy8WcNfJKfIrTcw9StYZhcDEtcom6JmK2hCEx7ISnMFButLXAM89gbljPvpyc8DnGTAvD1a3+ovv4eErR6/xbluYxn5+VOoPpCJjzl/7HhaMBVm9+JLDGnw0LDwkqsH51vGd5FmbKrdgl0wvBRJBZrJeekYX6dRNpXAZ9TBqjpVsArJa/pZwHRBNuG+3wM/dGsct5vI6AeJ20jAzDj6sPiNerYtLW7AHx+pCtVrn2FhWpUzfBTpimbJKQVpBaNtPc/BFh15srb4mgm2pRNRp4dYX5wHf9vUJFFqcX5+qp5tg4MXHJGxc94qk0YORSE65+BFSqZwPI2bD4Ji/ORxLD2Dfw5AcJfZsMJ+rX1eNi0GU9IHjIT8luahSQRkUYtD9vAweNMusBQkT++3uRn09SFFAkQue1P08yEZEGRRECQUdmmIDgMWpokGsJQwggt32PhcTLDIOOeV9OO+IQ8gUsPmRij+m6kBCafPVi2a+Trl+IWLOB6EOxD5LnR7V2IW0yTNpf1p7ep1HDpCHW4DP3/io2zdOCZsplrjlqZl/hdjE2P5bxAb1NsDTFcwQHB11oLFVaclAelmAz0VQZtUzanT2cKog14FuxHyN+AIiZjylz4YtIOO/ER+5al4yoXkpRgoCXrJNhZ1BkTYBZvlvHPLCZ0K3DDjhzu3X8njxWV3Gxj27Pxe4pLtH+r6R6kujg3o0a/8NOCobZ8GO4kWSquMu9LkaYALtidogwax1YIdcuDTTWQJKD8kyw6LFCgCe0Wa97uorLwtpH+lNY6GpoSVCbbI3kjDOmR+ddNv2RV2j56+xaSlGC5td1Y2hQZPnr7Dv0DaP9NscMym5wWsX5JCA4Iw5YvvqzS0irVZxkQjjzOglqluPLgctkw5ArVphumc/v8P4pOdXJqBY9k3VCsgH9+h/XDaBej+VPY52o5MQIltu11wIjRzhwYAvCBFS06VhPIDI+lugrzHtJW4tx8ntHr5mifl7T/8oZfcbVSnUusjojIgrgaxxoPUyJzC1tmJINrIFpfVax+u0f8Ew/qlhhc31jixXwOOvPubbNiSBXcVFRqf5CpqXUwISXiFuS8IXbSR8p6GbS5gLSRQoQWsUstQSYTTlXOaFr9ulKwNby7+CXAKwJb+SdcDX3xfzVE9p9yc98yDPhZ/kIJlAyQ5lYOs3bZlAttWghg5pp7BkGkBINjYY7vuqM277ducAaH27jZ3L0eJMlNV9PJ5Cq02m2YW7/xheu+1hR0MeoTaonFoOP7iJDrZGvfg52cAAqpRQt7A2JG0D8gtLmwoSvOxs+axh8xT3xLj9E1L/rjpSfNHB0zIkDXzYJIKlWZdKEOrdQ2SwM7milTJrjRFwE1Fax85wPVVPuN23hNPtW0z9+wsWA5KGzDcisBirkme/9+XO5K5Im1eTE4T/otsXn1Gzp6mEh6rQKkGD9vsYVeo/K2oEzelPkx+lQwjbOnYaxRavHB6eQSYt0ZywFIA/5T3gsBB5DV8y3qn18TFL8C4omCs3QN8gyGn5d+/3soIrR7mZmv4+LXcoFNqy10I4MhGjmCJu96NzuJp0kfKFopkBw6vWxWY3Udee7TKvyog5Onhzj4vn6++4pzg7oDn8RV+cCN7F7VsCrJWCh1f1oPsoQEdgbseaXQJiA9AqDh0YPk4YUHbAMaJA/fmJiBkwwlp8NDJfx7stNhmXZfQm6yQ0y+0iEZ0SS0qx+sSvTbBU7Zhnulu/zsTzQTegBMgZzczuC/HlGZ7S/PsZJelFV8e6JXLa/SRRLbfvUUEEgB0rOJTADKVafcQrWy+jYJ5lx9U2JHTXC79TB0xgiJjg8UzDJAEcJL8NYIGTJTRUcXUbN0fItAVvUMCZVZVzH/hADmi0MZhvTqJqzwe/meMqLCsv2iAfY6H73hPbnFD3E5Rf5w0eaiNn7MQXmu0hGBoYjVxLmHaNU5zB4YXUyabCVMMkOtYwz5nCZHyqMDFzale1BhdVpdVDBDaV54wML6jC+X4Pl+mHFBwSiC8Msnqz73gvYKL0MN4GPc67Mb/M0/ZRXGO/tlUvdUQNTcnXfH37h36sajA95xNfTnou1deF0Y12ZxS0u3z5zACIUGrzCG3W+1WkwAczUlrdqcw581T9cZOU3xVEqRcL3avfzJKPYKIz5OsuSmGtB2BpEnDWH7FV+JDOl4QBGVZl67KKb5tPI9r9vaMSSmtqquYlhBCfCdc0EHDAbsSWSPI1LVnmCJ8ZPJ9t8lzlpfrAcjqgqUw9HdNPMpQ39+4aGI6mprZqbGEb1v8XkSlwvChmmhh+nuQq0R5Kn4Qg2zzLw08k2o7MtuWi8Q18T9O0tSk+P5zSrI6+Y7vUk9Sff88nkAC6/AaINDWFmPWLV9pw4ZAp0rg4tlbTPZ0OUX+8FyBgLBBNTeRnIchrWZh3LzMG8nVFrRUPVCpywHOG0PocrewxN62Z1jetUz/VHhWugonMvzPfoTVKU1eu4ij/HpXiPU9e6R1X/CIfkpm1+pjqz/b2+pDrG//h1/znHfR1/TocqwoDDMY6/X8UVOpCwESJ7uhRshCbQNIX/VR8kAs30JVATfaGG/bt8F6fJP9G+63GgIYAGahIg0zUeZ4czeSImttkXgU31pSbqofuqIGexZX4udmBrIJlUSYFSI8UtKo5JWWKMd4fcggQiCdS6SKVpmX27I7TKFkMtshQ6PfM0hXQjP4P6kBIDrt2FBci7K5S10JUb2qpfhkjN1VOoLNYTGTaraE/dkLaF/imf0EBfAvHvC3UK1H5r4EDYl4Did4W6AbCNc/oeVU859OnwBOBwyNHo2qzw8IyHsa94OoW+G64cbJEl0TQ4nDAJbQ1FUDNDqe4r6hZS4ifUlYDfT1eoYT9k5BX4D0VQA0OpDmbyCVc92xpPtbfJrjoXUH/3JaCJukKzHlC0whEo+iNqiaL3McG1cf/UkaaTAh3hAQ6kUvUaQ6gTAaXJV1Q8PyRHSHu2GGyUoTCzNh1DWGZwmkZhc5rMtvE+HOCbJK3giUxbxUg0oZaZpIqPS6BQwbKjMsZlW/F9nJ0fYwJphXFYKpUcNKWxLBoh9K2zFGaa35/QLnlMdmQLREX7lNlARq+yBlzH2C5w9Q/t2yNxclSSg3OlsoaTdMZy2Uhk2qcPMbRfowsVvUXKzdr5FBdJnA2xRq/y4+ckiyX9YlJJIZeynkbeP88x+eVjlkDTDlsMycBSuFnH3CSaqbf5v/13xFeUC2QmiTUueT3bwL2mZmnJLTDS1tAtDPqXb+KioC8CFwR9qe68JkHFbZGAa3mqDDyrGYo1jQxOK0IbQxHURF2q5X79HU/nWZxenKun+kStGZqk5wlqckgKdQ2NdCTAlmQDQ5VB7ZLiMjLaxBBa2akeXahoyOyEjxDLGlHybylM+P9e5OeTrJG2UNFSS6FpqY3fLDTS/g7xb4sMtxRsxlzpnoIlU20qWEqNFHDeXkEKmAySAqY0lELRsro1s26UDC9UmbQ7jfYtVNZauJGmTNpIU6xpBMxOKTQHUkENg4QGZ0qSLfBQJDtHMtrssonvpK3IO42l0JqUyYoFGJMph83IkGjV45NQACryJLCaPJXuGEhMjCCeB4k04MGQSGbbuGwFJSM0EsNs1SQNYw70voQSxoGE2FIcAznMBNAfb9NBj8UjbroUPOamCcybamKIq5prKDRNNkTaWyxIM6lGJpoAUV7hZQhLo1iNsITaDQkbIhLYgrAEqpUjR6rrwyEeodh7QxnYb0Oxbnxk3pWKYyNTDI6LDIUWlql0I0CVwVBMDZf4Hwt5I1QZ1AhVrFtHoQzhzZZqcBdJwHWVQKXbImIeiOxfP4NXpVw5uFVkSbR3TDl4C9D+Dt8p5Qb3DEMANXFa6ovgC8Wu1ET0/tQD1qAvlipiemwijXokNCylBG/OZMQWp8QP6HhK4aEEJtOdGg+UhrdhCglEEtXtmHHL3TGlvGGBQnXaadrsHarJ9vJrTJ4AXluwNDojF3lZYuapvFWRBDSyQKUzcrP2kd55c+WggVkS7QktGBgFOKoF6eAzW5DUXBB189pG9UdqTBAk8QiNKQaPzBgK7Rd7PMXJARq3hiL4C+1KdSNTswJSDUo8BTge8UQG557vUFWhQjM8ywhlZ6IQrdYEcYnH0b9QcniC+pQrh9VnSMwafJ1gcJew2iKJolmKStMyF5NGaJYrh9rkSHSz/nO2U0z6dCk459ME2sNuPrYFcMDNk8CH2jyVUctyq3Ll8jZNrcq79Gk89tTkJj6DUU1qfvfLOE7JXb0kdHpfrDtU4iU68dk2cDVrtZV7tAkUKs+5lgjpG+6OphV+EiKJ6ow7ujid0gTtH/KWPrGQQr4cESjMZGjJ9RJ0LkoG99yG193RQGcMyt7F0sy71dzLlYmUb36vK8SwEG0ikMicVVkqg6m3fxEKzrd9qWyS7QkMPBTlTTGlMj9F06bkL8VkWANIFZADqG0k0olh0LbQIPUMQu28Hg2u71QdhRv7UIF/tUHlAuP88LEIch97tqbCv77hYuIv/4JV39Q09FsDvV1g6mBGgd5UDBZRPpOwN4cwx9PjWBndt2YXLWNWUa6q7GUD0VT3WkHJCRrbQa7KFwreTfkQFwdUOZiyrSg3gFRhlYLLNCGexpTfI0sQ4hOkX6I0KkMvTdxUa56TRA1HWDmaZKSQQg32KUxfT/LIxV5Fdnkedetz1C7qE+V4Yl5ZriT84IMoqn7GoeDCbVwAZrLnKEEMKB9FzCsrRhJAWYl6SzEZ/Vgm6vkC5gEJA5gCegDUmED5rsde9XYPqlKaJ/GvLrefJvVkz7/cVeRMJ1eUJfTeSxOq3m8s+6MGxeApJ9avGsAVg1JxyVkKwyCoIeSDoJzYSz8uwQyXSVpHR+45K4zAkYYzgRGGRijdBQ0ZWMu1FmhDfgLci1+GgewZr70ZuqeayqWxSBRiecw/NyU1pU9JHZbJzEvGqH/RCKyWYUrFEph9Ptmsf+EXmGw98PVlU50r8q5+dxirVx+OACKqAcm/GPX7R4CKfhdo/Pc4f1bffNayx8D2aqqO8hWzvEk1uVL8O2uilOwNNVgTNItYGNY88rnfpJqnPl+YebpLGCvs8JVCIgcae4SykIaxQQ1fyf+QOpNZgKf34PpRXSEkToAYAwwTVdAA58k2YqMDSKdalk6hDBCtoNFCEYXAFVfuaktf/kd0uAKpNYyqa1UUgyzQysrjJqh5SWImwKx1cQ88GLg1iX41J60SbFnn2gFezGJlkA2aol/KDqsDqSVE2hBrGUlQD9oQHlcyvGp6PAikwdAwkQmYcCOKXQ9I53/nAwUyIZWVAUqce54LtkI3IYWAvI5ntSBUSBmB5UHMpP9IQPJgH8o8JpEEptEbR1MxmJnUwWZoboYRZBzuyhTBciSRfsCbNAc2ipsYvV3MDGLAlQ08pOAtCSDkG63qxm0hrOQ2sakW+gWInqNKFyc5dbhjVsE/ljlppUqDGEPhpCSn1iol0QZQYxkmUQSsMj0sMGYxwTmCQbAv2qI24bqCmFg/iZvUDvaJrs2cbAQ0J5syLOQGgoK1EVOogrCt1cQOMJ0emybWCm8i5XLSuK5WeaXWZuvGRZqRRHWJpM0am1TNZxKjmPBlIuOomMPRbtwXQoYD5eSjYdh7gOg+Pp5SNERElCvOUSqWLlxcxmbZIom5OKHKfUjGiHVcF1WWUOo9gtyd8IVQk6S6PIrkGPUVc5dI5H+2Cq9qFzdT6TIlEslFdneZ4qN7kprDj/48A02f7qgr6CE+wl1O95F4fk8i0dTAf3TyJzhzmUgVvFXpb2lWMQSaTALaEm5WgWntTdeHp1XaCaAKYRQhji6pSv3qR10SA1evL0cWTGEmou+gMRyp11HliA6qK9GXptGIzITuHUQGQ/KKtcWKfnu2Zans1oZmtKDz9Cb5Emm2qi+WovMo+sTfLYmva/BsAKSTC+76ZoCJKk1qwUGjXRVVjU0cRYhhKZx6UJDraHCvk/vxwRUUu7fRfnxQSG2GizJEtifTtHY3tEtDPbpfF2GOPmy33AY8iV/FhZDjQz1Pm55BfsVWViTyv5UNr2oX6D26xfuzp7hE+7+S6olqQVRcV8WbOkxdPpQ9qSoNU+9uCOa7HfjLzQBX8KQIWFP6xRvlCPBkmo90vxvbh63ld1yYyzx9igHNgEiRBBgQ6fQIQz0w84G9ikAyA+VSSEkfYmGkSN5AmJhkY/BjFu13oa/kFx3zmKbLEyH/JjgKv0rzmTTuuzARnl530GkwooGvXEuW0JPgYE1hflWm7HDdCHUhN7RbPp5wTXs+JvmIZsADKcMMdUBSlfZLVuVKGXHAbdDZUlr//R06LgCf+0XzZElFrt0ReHmiJMt60379mkw2Hgyk+jLkxCE+jtlNodgwSmn97xunNgOcKygCMhkBI4dpXQVeZMmXGuDo8impeYkeI4aZkVzmGzbHUHSTJVUSp4q1pKqC73UknEmpnXo02ZHGG6NbK4tN6e0irRtIXSUneBFult7KIcrVkNQpEhI8iZZTkSumcjgBVTOlq7NKyfjIeHg2iWrqAulCzFrTqK1TN7iavHp+1BLSgekvkGHqYPfIUP6J4SJOmUjCxfGOTjsWvSnyo8oeKvIQBoEzrLULG2XCtNGmeMgtDEERr90MfXq4SLF/E4n8b9yEFHdNTWn2OpeRPQWiHMNjO0ip72t3JywhHV47xsuS0LnEK81JtK7kGBfP1993T3F2QHfYtEMKNWBboq2kMgqb0601CJyvjd2h0Gnmmm0JmEVupBHIH8bas9TrUluSN045GWrrhBj6NHnzCCPTJHj+zKTYt+uq+N++z2siMEFa9CaBB1IFtcJ3wdmFX5kQ7r55AGGS322UWSI2w5rSKCytXDEo6xvRR5XNTWYamUGCoYNpwxQkdCWvai0EMUwes4hNnSvaSEGtmE+AhL7NtKJI1MtwAPO1ERbqNGwuzuA25lBQhzQHmGit+Q6V+dNczTHkgIt61jJjALTeFQF4iPnuKDaKHHZuaSzoRFNRfWB0lWdlVcRJPavg3Wm/OOlCMT/kkZigCtjf+eKtXxM5xG1mlw2SxF3N+kGXjMuD2enEHQaWpMjVSlnlCJnVJFQKMlOgMFnLRKuN5BgedUDStvvuMZksRdo4ww7P1NTm6unUwhs/g5tBbSrPnGnvM6npgF3JOI7h8QRk5ms2LoqUe+MMO0SKV5urp1MLbxyJfga1212kkDLQeFaT1Fc80Pbf1gQzqVRNcUeuT7/osZuYAiujszUNVZfrux7zOcFNa1o3rhMj17z7RhtZH3eGI/R/+uVf9VcvGia14XEvo6Ive/WiSYra/oD/rPIiPqD3+R6lJfn11Yu7M659RM1fr1GZHAYWrzDPDJE80QPTjuYme6wDvpN0mpxEHUlX3Odtr+J9XMUXRZXUcS1x8Q5/SyTdL/Eyqc+DPqP9TfbhXJ3OFVYZHT+nzKHzqxfq9l+9EGR+1QTaK32ogMVMsAroQ3Z5TtJ9L/ebOC257aaMxRW2/u8I/970Jf40K3R47jn9kWeGjFrz4U0qyvb4k3tAx1OKmZUfsvv4K3KR7WOJ3qFDvHu+rdN4EacZGRN9R7Bmf/U6iQ9FfCxbHkN9/CfG8P74/b/+H1GEE/HetwgA + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.Designer.cs new file mode 100644 index 0000000000..be66100823 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.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.1.3-40302")] + public sealed partial class MediaStorageData : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(MediaStorageData)); + + string IMigrationMetadata.Id + { + get { return "201608110948010_MediaStorageData"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.cs b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.cs new file mode 100644 index 0000000000..1cb4b063e3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.cs @@ -0,0 +1,341 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity; + using System.Data.Entity.Migrations; + using System.IO; + using System.Linq; + using Core; + using Core.Data; + using Core.Domain.Configuration; + using Core.Domain.Media; + using Core.Domain.Messages; + using Core.IO; + using Setup; + + public partial class MediaStorageData : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + private const int PAGE_SIZE = 200; + + private string GetFileName(int id, string extension, string mimeType) + { + if (extension.IsEmpty()) + extension = MimeTypes.MapMimeTypeToExtension(mimeType); + + var fileName = string.Format("{0}-0{1}", id.ToString("0000000"), extension.EmptyNull().EnsureStartsWith(".")); + return fileName; + } + + public override void Up() + { + CreateTable( + "dbo.MediaStorage", + c => new + { + Id = c.Int(nullable: false, identity: true), + Data = c.Binary(nullable: false), + }) + .PrimaryKey(t => t.Id); + + CreateTable( + "dbo.Discount_AppliedToManufacturers", + c => new + { + Discount_Id = c.Int(nullable: false), + Manufacturer_Id = c.Int(nullable: false), + }) + .PrimaryKey(t => new { t.Discount_Id, t.Manufacturer_Id }) + .ForeignKey("dbo.Discount", t => t.Discount_Id, cascadeDelete: true) + .ForeignKey("dbo.Manufacturer", t => t.Manufacturer_Id, cascadeDelete: true) + .Index(t => t.Discount_Id) + .Index(t => t.Manufacturer_Id); + + AddColumn("dbo.Picture", "MediaStorageId", c => c.Int()); + AddColumn("dbo.Manufacturer", "HasDiscountsApplied", c => c.Boolean(nullable: false)); + AddColumn("dbo.Download", "MediaStorageId", c => c.Int()); + AddColumn("dbo.QueuedEmailAttachment", "MediaStorageId", c => c.Int()); + CreateIndex("dbo.Picture", "MediaStorageId"); + CreateIndex("dbo.Download", "MediaStorageId"); + CreateIndex("dbo.QueuedEmailAttachment", "MediaStorageId"); + AddForeignKey("dbo.Picture", "MediaStorageId", "dbo.MediaStorage", "Id"); + AddForeignKey("dbo.Download", "MediaStorageId", "dbo.MediaStorage", "Id"); + AddForeignKey("dbo.QueuedEmailAttachment", "MediaStorageId", "dbo.MediaStorage", "Id"); + } + + public override void Down() + { + DropForeignKey("dbo.QueuedEmailAttachment", "MediaStorageId", "dbo.MediaStorage"); + DropForeignKey("dbo.Download", "MediaStorageId", "dbo.MediaStorage"); + DropForeignKey("dbo.Discount_AppliedToManufacturers", "Manufacturer_Id", "dbo.Manufacturer"); + DropForeignKey("dbo.Discount_AppliedToManufacturers", "Discount_Id", "dbo.Discount"); + DropForeignKey("dbo.Picture", "MediaStorageId", "dbo.MediaStorage"); + DropIndex("dbo.Discount_AppliedToManufacturers", new[] { "Manufacturer_Id" }); + DropIndex("dbo.Discount_AppliedToManufacturers", new[] { "Discount_Id" }); + DropIndex("dbo.QueuedEmailAttachment", new[] { "MediaStorageId" }); + DropIndex("dbo.Download", new[] { "MediaStorageId" }); + DropIndex("dbo.Picture", new[] { "MediaStorageId" }); + DropColumn("dbo.QueuedEmailAttachment", "MediaStorageId"); + DropColumn("dbo.Download", "MediaStorageId"); + DropColumn("dbo.Manufacturer", "HasDiscountsApplied"); + DropColumn("dbo.Picture", "MediaStorageId"); + DropTable("dbo.Discount_AppliedToManufacturers"); + DropTable("dbo.MediaStorage"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { +#pragma warning disable 612, 618 + context.MigrateLocaleResources(MigrateLocaleResources); + + var mediaStorages = context.Set(); + var fileSystem = new LocalFileSystem(); + var mediaBasePath = $"Media\\{DataSettings.Current.TenantName}"; + var storeMediaInDb = true; + + fileSystem.TryCreateFolder(Path.Combine(mediaBasePath, "Downloads")); + fileSystem.TryCreateFolder(Path.Combine(mediaBasePath, "QueuedEmailAttachment")); + + { + var settings = context.Set(); + + // be careful, this setting does not necessarily exist + var storeInDbSetting = settings.FirstOrDefault(x => x.Name == "Media.Images.StoreInDB"); + if (storeInDbSetting != null) + { + storeMediaInDb = storeInDbSetting.Value.ToBool(true); + + // remove old bool StoreInDB because it's not used anymore + settings.Remove(storeInDbSetting); + } + + // set current media storage provider system name + settings.AddOrUpdate(x => x.Name, new Setting + { + Name = "Media.Storage.Provider", + Value = (storeMediaInDb ? "MediaStorage.SmartStoreDatabase" : "MediaStorage.SmartStoreFileSystem") + }); + + context.SaveChanges(); + } + + using (var scope = new DbContextScope(context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) + { + #region Pictures + + if (storeMediaInDb) + { + PageEntities(context, mediaStorages, context.Set().OrderBy(x => x.Id), picture => + { + + if (picture.PictureBinary != null && picture.PictureBinary.LongLength > 0) + { + picture.MediaStorage = new MediaStorage { Data = picture.PictureBinary }; + picture.PictureBinary = null; + } + }); + } + + #endregion + + #region Downloads + + PageEntities(context, mediaStorages, context.Set().OrderBy(x => x.Id), download => + { + if (download.DownloadBinary != null && download.DownloadBinary.LongLength > 0) + { + 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; + } + }); + + #endregion + + #region Queued email attachments + + 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) + { + 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; + } + }); + + #endregion + } + +#pragma warning restore 612, 618 + } + + private void PageEntities( + SmartObjectContext context, + DbSet mediaStorages, + IOrderedQueryable query, + Action moveEntity) where TEntity : BaseEntity, IHasMedia + { + var pageIndex = 0; + IPagedList entities = null; + + do + { + if (entities != null) + { + // detach all entities from previous page to save memory + context.DetachAll(false); + entities.Clear(); + entities = null; + } + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // load max 200 entities at once + entities = new PagedList(query, pageIndex++, PAGE_SIZE); + + entities.Each(x => moveEntity(x)); + + // save the current batch to database + context.SaveChanges(); + } + while (entities.HasNextPage); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Common.Copy", "Copy", "Kopie"); + builder.AddOrUpdate("Admin.Common.Data", "Data", "Daten"); + + builder.AddOrUpdate("Admin.Media.StorageMovingNotSupported", + "The provider \"{0}\" does not support moving media from one provider to another.", + "Der Provider \"{0}\" untersttzt kein Verschieben von Medien zu anderen Providern."); + + builder.AddOrUpdate("Admin.Media.CannotMoveToSameProvider", + "Media cannot be moved to the same storage device.", + "Medien knnen nicht zum selben Speichermedium verschoben werden."); + + builder.AddOrUpdate("Providers.FriendlyName.MediaStorage.SmartStoreDatabase", + "Database", + "Datenbank"); + + builder.AddOrUpdate("Providers.FriendlyName.MediaStorage.SmartStoreFileSystem", + "File system", + "Dateisystem"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Media.CurrentStorageLocation", + "The current storage location is", + "Der aktuelle Speicherort ist"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Media.StorageProvider", + "New storage location", + "Neuer Speicherort", + "Specifies the new storage location for media file like images.", + "Legt den neuen Speicherort fr Mediendateien wie z.B. Bilder fest."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Media.MediaIsStoredIn", + "Store media in", + "Medien speichern in"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Media.MoveMediaNote", + "Do not forget to backup your database before changing this option. Please bear in mind that this operation can take several minutes depending on the amount of media files.", + "Bitte sichern Sie Ihre Datenbank, ehe Sie Mediendateien verschieben. Dieser Vorgang kann je nach Dateimenge mehrere Minuten in Anspruch nehmen."); + + builder.AddOrUpdate("Admin.Common.MoveNow", "Move now", "Jetzt verschieben"); + + builder.AddOrUpdate("Admin.Media.ProviderFailedToSave", + "The storing of data through storage provider \"{0}\" failed in \"{1}\"", + "Das Speichern von Daten durch den Storage-Provider \"{0}\" ist whrend \"{1}\" fehlgeschlagen"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Tax.AuxiliaryServicesTaxType.SpecifiedTaxCategory", + "Specified tax category", + "Festgelegte Steuerklasse"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Tax.AuxiliaryServicesTaxType.HighestCartAmount", + "Highest amount in cart", + "Hchster Wert im Warenkorb"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Tax.AuxiliaryServicesTaxType.HighestTaxRate", + "Highest tax rate in cart", + "Hchste Steuerrate im Warenkorb"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Tax.AuxiliaryServicesTaxType.ProRata", + "Pro rata in accordance with main service", + "Anteilig gem der Hauptleistung"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Tax.AuxiliaryServicesTaxingType", + "Taxing of auxiliary services", + "Besteuerung von Nebenleistungen", + "Specifies how to calculate the tax amount for auxiliary services like shipping and payment fees.", + "Legt fest, wie die Mehrwertsteuer auf Nebenleistungen (wie z.B. Versandkosten und Zahlartgebhren) berechnet werden soll."); + + + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Discounts.DiscountType.AssignedToManufacturers", + "Assigned to manufacturers", + "Bezogen auf die Hersteller"); + + builder.AddOrUpdate("Admin.Promotions.Discounts.NoObjectsAssigned", + "No objects assigned", + "Keinen Objekten zugeordnet"); + + builder.AddOrUpdate("Admin.Promotions.Discounts.Fields.AppliedToManufacturers", + "Assigned to manufacturers", + "Herstellern zugeordnet"); + + builder.AddOrUpdate("Admin.Promotions.Discounts.NoDiscountsAvailable", + "There are no discounts available. Please create at least one discount before making an assignment.", + "Es sind keine Rabatte verfgbar. Erstellen Sie bitte zunchst mindestens einen Rabatt, bevor Sie eine Zuordung vornehmen."); + + builder.AddOrUpdate("Admin.Plugins.KnownGroup.Performance", "Performance", "Performance"); + + + builder.Delete( + "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.Database", + "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.FileSystem", + "Admin.Configuration.Settings.Media.MoveToFs", + "Admin.Configuration.Settings.Media.MoveToDb", + "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase", + "Admin.Configuration.Settings.Media.MovePicturesNote", + "Admin.Catalog.Categories.Discounts.NoDiscounts", + "Admin.Catalog.Products.Discounts.NoDiscounts", + "Admin.Promotions.Discounts.Fields.AppliedToProducts.NoRecords", + "Admin.Promotions.Discounts.Fields.AppliedToCategories.NoRecords"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.resx b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.resx new file mode 100644 index 0000000000..83164d490d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.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/201609201852449_log4net.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.Designer.cs new file mode 100644 index 0000000000..52a9ee42ba --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.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.1.3-40302")] + public sealed partial class log4net : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(log4net)); + + string IMigrationMetadata.Id + { + get { return "201609201852449_log4net"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs new file mode 100644 index 0000000000..5eda541bc0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs @@ -0,0 +1,96 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Linq; + using System.Data.Entity.Migrations; + using Setup; + using Core.Data; + using System.Web.Hosting; + + public partial class log4net : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + // 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)); + AddColumn("dbo.Log", "HttpMethod", c => c.String(maxLength: 10)); + AddColumn("dbo.Log", "UserName", c => c.String(maxLength: 100)); + AlterColumn("dbo.Log", "ShortMessage", c => c.String(nullable: false, maxLength: 4000)); + AlterColumn("dbo.Log", "PageUrl", c => c.String(maxLength: 1500)); + AlterColumn("dbo.Log", "ReferrerUrl", c => c.String(maxLength: 1500)); + CreateIndex("dbo.Log", "LogLevelId", name: "IX_Log_Level"); + CreateIndex("dbo.Log", "Logger", name: "IX_Log_Logger"); + DropColumn("dbo.Log", "UpdatedOnUtc"); + DropColumn("dbo.Log", "Frequency"); + DropColumn("dbo.Log", "ContentHash"); + } + + public override void Down() + { + AddColumn("dbo.Log", "ContentHash", c => c.String(maxLength: 40)); + AddColumn("dbo.Log", "Frequency", c => c.Int(nullable: false)); + AddColumn("dbo.Log", "UpdatedOnUtc", c => c.DateTime()); + DropIndex("dbo.Log", "IX_Log_Logger"); + DropIndex("dbo.Log", "IX_Log_Level"); + AlterColumn("dbo.Log", "ReferrerUrl", c => c.String()); + AlterColumn("dbo.Log", "PageUrl", c => c.String()); + AlterColumn("dbo.Log", "ShortMessage", c => c.String(nullable: false)); + DropColumn("dbo.Log", "UserName"); + DropColumn("dbo.Log", "HttpMethod"); + DropColumn("dbo.Log", "Logger"); + + // Custom START + if (DataSettings.DatabaseIsInstalled()) + { + CreateIndex("dbo.Log", "ContentHash", name: "IX_Log_ContentHash"); + } + // Custom END + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + var prefix = "Admin.System.Log."; + var deleteKeys = (new[] { "Fields.Frequency", "Fields.ContentHash", "Fields.UpdatedOn", "List.MinFrequency" }).Select(x => prefix + x).ToArray(); + deleteKeys = deleteKeys.Concat(deleteKeys.Select(x => x + ".Hint")).ToArray(); + + builder.Delete(deleteKeys); + + builder.AddOrUpdate("Admin.System.Log.Fields.Logger", + "Logger", + "Logger"); + builder.AddOrUpdate("Admin.System.Log.Fields.HttpMethod", + "HTTP method", + "HTTP Methode"); + builder.AddOrUpdate("Admin.System.Log.Fields.UserName", + "User", + "Benutzer"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.resx b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.resx new file mode 100644 index 0000000000..b7e10dc3e8 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.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 + + + H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq8+sXZH95yQZMlWbF+0JNnee70oKBYkYZlFVvOiS0/Ml83DfNL8wgDgpUAgcQdZVep6sUtEIgEkEolEIpH5//0//+9v/+txmb24R2WFi/z3l29e/fTyBcrTYoHz299fNvXN//j7y//1f/7v/9tvx4vl44tvPdzPFI7UzKvfX97V9erX16+r9A4tk+rVEqdlURU39au0WL5OFsXrtz/99O+v37x5jQiKlwTXixe/nTd5jZeI/UH+PCryFK3qJsk+FQuUVd13UnLBsL74nCxRtUpS9PvLi2VS1hd1UaJX75I6efniIMMJ6cYFym5evkjyvKiTmnTy168VuqjLIr+9WJEPSXb5tEIE7ibJKtR1/tc1uO04fnpLx/F6XbFHlTZVXSwdEb75uSPMa7G6F3lfDoQjpDsmJK6f6KgZ+X5/eVmscPryhdjSr0dZSaFGpD1i9CVgOH/F6lXtf//2QgD6t4Ep3r76n69+evXTv704arK6KdHvOWrqMsn+7cVZc53h9D/Q02XxA+W/502W8T0lfSVlow/k01lZrFBZP52jm67/p4uXL16P670WKw7VuDrt4E7z+ue3L198Jo0n1xkaGIEjBBvVe5SjMqnR4iypa1TmFAdipJRaF9q6eKpqtKS/+zYJ/5F19PLFp+TxI8pv67vfX5KfL1+c4Ee06L90/fiaY7LsSKW6bJCpqdM8zZoFOs0vMGkyWfUNHhZFhpIcGKYBX3WWVNVDUS5IQY1SMvxQlD3CyWlxietseoofFounyRv5hOqEcDQlWzVLY+9QlZZ41QqcGdqbZ64+4iVZFovLgi3oKpSTz1G+QOVB9R0vblEdiq3F8s8in54ObVPfy2RFNtiaCDGp7zb1L+6Kh9G8hY38kDA3KiPIlxIXJZPKevluITwuk9vIc/Hb6/Xuq9+Tk8cjstncFuWTz86cPL7iMOw3Z3Vbhm35bz/9ZDXJjuz1DlerLHn6QlnehVGt+ecC1TUbizPvEJFwg2+bkkG/6vDsOcibg95Ow0HfkqyJsVM4NstoZaauF89+LNIkw3+iRd+mB/d2OFrmlRDu2VjdVjsdblMLqFhJftskt44sAuChU4cIfd6XRbOaX0AP7W+q6bnW9+fkHt8yBlLM5MsX5yhjANUdXrX2FHllXa3BT8pieV5k0IIeoK4uiqZM6fgKI+hlUjL12k+mDN0KFCUdnr0EUbdl2AjfTLRcupnpqK7diadon9T+o0EXqDgiOHStRzi4nWTJ7emSDPYEZ8hA7l/sRms44tZZ6Hks8qGbraXqLvyc6KuCa2UmE93tZJyjism4Si1ABUi1DFUBDrJxJEaV0L3Q9dfOBNRRFDQB517CmmVdqHbV03ozR5e+9Q0dYU4rurrOsuYW55IQMVW9LJoUEj7xtKpwoQDqVkYR4iUUzlC5xBVdnOcoZUZ9Z4FwgdKG2uteibj2gkDdVqTLJNfDv81F1ttffpmi7bU1dPKWlYv3iPE2Kumygrd1kYevhCrrJayHlNawATxoEfOofAyGXfXqFY9ov3q9V+9EK+ikROiCsOqKtRemPF8mj8ePaLkKvvUiiDpFnOIRpkBf9SCt8X3w5dNp1Uq1lvnDcEWVj7ZCSZQMEwsm8cRhKcf8tIuCrH93gUSrVezfvRBStxXrLLFJVaTziZj8wjya0YHemX/JP5D1ccZU+jBsB1lWPLxvUFWTc8m3og5GGGATke6JyLp7Rxjza532mOifl3hprHucLzxrhtqa/I5tVNKAx7RRgazSjUohDU4r9kntg7x6IOJM2am2/KoVo+NucUWySBfKg2V4iyxIkrco9vJcI6MIlXZTln9ulteo/HJDJVgVNoApjLrt8glbYtDah5agS58IuZhBR6P0CVBX/GIcd1YBBsoGFWywnOARB0kLHlE0mfHiMKlQ1wFK3V7R7X3ojKuzJZPzGrXYAqaafYhtTZwSZIKIYn7Y7xLqtnoavW/w0Gr72/XasyLtmm4gY1xBHpNZziZvxcIrPW5DJ0W5TOrQDbvHdpFk9eRdP1gscX5ULJecx/B0LxmqaDamg5sbnGGyXEKpHcfi9A5lKMI7it5wdZCmRQO4cE9hu4rDRx+Tqj5dHSwW5Iime85g6zBikHglopLySw6eJ52dTar6Y3GLc98DKqnPuIiIaiUKb4WgI6nibqIX/Vcc2FoNkEul3R8AcdVbD3GWkWke5l7XTREW6OsYRN1hAc6116Kmp+t2B3O11mjkfoswkpatBIRU7LAbqwG10iAMQ6iJbb6Z0vX4+JEqNEl20NR3VKVJGZDulKOrAU6DVQVpTuxquU4QUQOa5VlR1fDYhmJwIHKp1GsAxKuL7VtPdR9ZubqT42K4lwKMazfZmR/uISsCOzcukfolFLt26RyRMwZhkT+YiRbs2ggE7CIMIXVVAebe5YekXJwVOK+rD5ggoTfuYL8lOEXv1XDAGDTAriPprzqt9hoJGBB/AoxaAIqAriLw4q5g9Y/IIfaUKGVw30UokPxKIIn2asggs85AUI93SMtlkb/qEOzP9Oq2yOGv6YMDPIs31Se4rOpItmizPj5LQyYrRpxWyJpZJfn079GP6ImzlB8LGe8Ea0S+3eM8lc/ihha5F72TDauTNW/maujt5A39E6+o8pdkhscJka7J74octbc508uI5HGmlsIMCOqjWbuGwL291xwGmPWOLhRJWohY7qx88ItU2zkBUu7iCEDZ0TFU2C1CTy5nZeMdLlFK9c1XHY69vqFuy7BhTvTOizm7VJ0lJ4rnTBXNG/Kh+IgoEU+rOV6BXd6VCNk2+HOEBomgRSVOhcY8HYOa63+RpXZZfEuCbdbb8BZsLg+kc3KsJ3NAcPdc+wnVd4XCJjaGuVpXbsUbHlv4zNDAcdFYxfnAzm8EimGN9gpgdwQB5M6DUEHn3DE9fJ7BdAiEqdvvQxqxpN+HpgrdMmNorHhy6fQ2J7Q+uqMrYRKpxAmWOeSRqE06iDC/9T1WdgMUzBGm/fJWt6UyMsT1aZxKW72+LtE9Nlnl4lxxb4Me5HXO9dzPpcWv3fWDHNKGK98Ifmkdrv2a16z5jlShi97GvcT2dbvJDedgtSKsF774ojqqfF0tJjFaDXdMsV0gVLdpSl8Jr2V9mBW3gweb85KmtatXHI7t8C3uOnOJHqd3y6ODpxbleF7MPUaQpThaX60B1+wElUusBAIFs1HblQAeogj2e4G6rVgPFGPdWTo2GykwtI9hs2P24FhL/c7W4Tvi/Uw9lfGijo/0MrmdPir2Zh40Wgb+tjV1mBvbtsDfcUYWOQbZRDeC3D4FW1G8dkPR+KndMqO8hx2wQm9ipUJwux5DhIUoKotFk9bn5DSOHnzOcUmdkB69GuHZDsWv69K2bJD6VlrCzaKknic1d6vnR5QPKFvdNNl/oeqScEoWBdnnwgeX+v1fO/3w4z+eW68GSO7ZHwQgP/gDoZxfq/JYOlrk5HB+rLEYq+pcCetZMSJlDflFq121sMetPf1Dpc/+vGCUhrS58Gd1JWmThZ5mEYljSNlvuMIE+jRf4Hu8aJIsewrVQzZzAXZxVxBNeEY98YT0b872Zn3n2HMtWq6yCE8U4waY6fHEu4fcH2jiPMVlx/z+oRXboaKd9lvt6aJZRjvqR8LYo2NKlDDo4D7GQzp4OB2k2xY8++JHM/2iS/LmJkmpClKSfbQ2eunGafZ9jXULPE4jp9V7fFMfJWXwZU+PJ4a2Qh994RJ9qe8IxdvtJEJuM4ZzrfwYHmpHEWoN1Y3py0qiGx0sFkIfgsd0Wr0rHvKsSMLvyTs8oTP3Nc/aBd4jDB7jp94t/suNhNMzpFKH5vhxhdukUO+SJxGnHQr28p2hiMH2H5LqIiFaE4o1q2Nsjg9USG9oSJSD2xIhXnH07cwI2SxWk9PqnIbiLiM4RA+Ijp7SDLWdCpVxPMYzVOIiePUNONnezxAHrpVT5lJ+nNMqEQJ8xIzYS+QppisvyXqMrTPgYL9GKV5S29RZSX51WZ///vLFBY0dT/ZPj+5HC+ByWh1XweTk8iGGMg5Rcei9ZH5PliZB1/ocBp/c6iL98Y8m6WwdQSK7Pa4xjAf3CSZ1ccZhDXQPA3vqvWHhPOLIPxYP7ai7GCvBzoNFjW+emEHgpCj7Ph4icvoKQ3yYpD9YwlOa2Dw4LhE9DVKMpy0tyQlkOPQGaxTs0E9mCS+bZZxJajEmj/EwMgKgRYcMoxmUVbIWaAcPm6fDpq7XxoWAtUWhv+PqLsNVHQdpt/gzRJiXyPWR/cb78pNo5wwdToPtSyMk0XegL9li2ga6g8kRu4adqI2LFcGTZI4Dscc5+DXQy2sPDwUeV+fn4ImpN2cd5zUqqyj81YmtEWY0MVN0gm3WNsnh4xKjdk0GC3yyh6KqPqiJ6LxuanRULK9x3l3rRWRC0mci81jsOupCm+Fwjfk7wrd30y3F8TkmOvrveDEh9g/T0mbYaULlyYAoTJjEu7GI97giTtzHLXITlweI71H5RCs7Wk96NZCoYPJFq0mTrlC3+0Q5dg/4PqGkakpEu6RRJ6Ok2RzaPFjy7pChInZAS3+MUduRtskXGWI3WgYLVBzzetveGSppAKpYho8RUkqN2Dj5yFnOiNWxPNstcdgiQSeZvvSqg74s1rbytWOMGkpyhtGAuroI8tJA5+FzNQaU/HlG5SoHpTGQp39SO6vqiGsS2FXHB0rvKhWoyglJCe9K/P5k0hq8DD5WvVlM4yYmgqgGIMF5dpy/RNT2nQfU9B8CU40BhPUcxxlmeLRD6GA0vRcgVB0XwTz7PFyCx3Mr1PrchfaXnUBvunisw7FF23+4imY8+gqq8RlqeY63fe6gEagjOFmecsVKccrDeErTb0mJiVIHnSO13dfU00yQRS3VLNlU9ZwqEbXTwB1G6zpE73HxerqWB8eAMhPy5UouHAE5BzIT7oY1nRVB5e6OIZQdFsBcu8zbcIDuDsUQZ0iFEi/IEEE+zb2q6BUMo1Nqh197v2Z1WwYv34mStPczE8NT42tFz0opGW4En9S+YzLG6EatvinXU7nzze8mnhZ23lXVUdGsipwP7OZtmZIwTRbxpJ8Z5h/JVlkoj64xUeK5+oGYjvGXheEoJh+8+Qq6s/waTtqWtMCum9OAw3w2k9sV6ujGMwK1GNIY3ntUa8PJFHYW8zAkk4y1naVD1a1n9evRoU24BjAOCFA9FBA6LAhPZ00IeDfVo9grGOq2DArGRM+I6KueSC2briXmewFyWNSET2d9rJThZIYkWt0yivVMaf8MKFIyQfpOMZZbY2c/dLx/o+/DLvCfKKzt0YOm6rIgOxZKaxG3t59Rh+fL2CVusqBg7Jh9nuS3Wk+0ODwQ+cVffK+MLX4PtW13+/G8FrbZS+AmIYrWN4wePk2SlCL2larHQcx8rQqc2qxty62YhkPwDfdvPRAXek8ok0PuiQBhcVf6Hjjr7p/QAievuvp7xV0jvloSHeI8KdeO+N1fNgvJ5D25ZKFptFvYJBEGUHGCM5TrTwc/R3rS+Rk9hMrt0+qyTPIKx3j3FVPWspVEmQwKo2crb3gk8DVKJy7GgNwlClAuX6FAQJ5Xj0637rKghCGMt+6CVPUSmmMSekpOHslefKrbIssq8ZGa2kXoGhPIf5sUQgPt90vbCEFTnZPnU/a9dENvwae47VbJR0dhHdc1ytRXTQQzeyHN3TgErFsezX7VqtvajHl6VoMhxwl7C+t2WVj3NtEdtInuzYQ7byaMYYiObQn09mIw2wNhr4cYat/YnV1W+qBySY0CgYKUKMBbIIonIYdvr1RpRQgjV4RDjEj28yZDF09VjZYGzS1SYP4VmtxDr8s6Gykj2RCAJw66da67y2IICUlWraPOskYzHJOnz6rANZo8UeYZkprO1nA/G2Y9KHbLnXriOE/Hj0RQ8aarGW7O1r7e0/mVKfdIrRea394zenEYkMuRR7TfbdRtGTYCyzfUzu7IWVF+QI/fkqyZv/VOR/9Y0D1n6vfjvgcCV2P1+n1quL16jWu/ctRtRTJZj14ThyKLFqQujRnsQP0uZMPh9Y3uWzNH08cZYUH+SWKgxxheoMu7ZnmdJzjYv6tLkrA9BpvnaGlRm0V6pmh5xDIKglDrit8kNBER1NXM0RE0dV1NJ0IIh2nDPihup4xhIiJeqXFtWQ3AvutR7tZMUx1T7xBQ79UQdVtrogXHKu2JHg0R0++DE0ZXRO1oL1hiGpp5WbgZWWpctxZyOGQlR3jDJWDar1N1W5E0/FgvSk6rE6L1NOscDhvUx9Q5zQcOtQiRtAZWx0gafijWngw4wT7vE9TJ1N+YO3wkJxoA214+TC4feHL/JWTEmFstw5GNK+ljko3+UKxDdYUJ5EdIYDWb/seUJXCcrXCpAuPdy5fJ5QtM+PaGLkqyhFbXjRAJtH192Q07xgPMSbyCzcs9dkQ8hQiwjKNnOzAdn3iMVo/OlgQ6LI500aIKkpz6oXoLTh3avdx0FXjB2Wvi+N9sjwSLF+pzhqXuFhTUSmqEyUWQTPp2FXjW9PGoLslAHxwTCL/oYm8v8P4KoknHvbBk2sCSE2VRyLINOaldJrfhxzKCZL+0vJdWrPdSpr06WoxqxT4KxrEO4U1NHOhwntUg3/Oyuq05EpbPkjl8Q1nRo2bscs1/pcfmmO7K4ITkmN3KcNu9nel4DqoK3+ZkFfVvEudIc7mRnE6nFUvgGu4HFsccuT5p/ucyi6C1GmRevHS4zNb5pam/3DCkTO91d/gLTvGjy7BgyP5jW1VlWLOuP8EVynSZLDwG62titc34oGvbkAzCtqrPsI0pJKy1xPEgAh6N8Ij2up+6rY08GrHzxI75aGO7X4hAe3MXwdFtHwk9jcU/gu3XnrqtSIpThybahQbNQkk+LVe6XJRxtLDTqntvGPyGgNuX8rosMoptK2NHuWs0PimqLDdxb4VF5Dmf8YB2V9s6riPUX2pYDFVEzByinVKNsRpXSslrpIG2vm1SMj2SGBa+iJvJfhex2EXmyU2woaukyZ7shWpK3Xvn2OoSQ7vndnVbCrqF+6vt0CKyeJF/8UeTlKg6v72efFCtDXfxL7ISlmjC8DDfmfV5hobCXilNdiRkoiGGWvsR5z+4+DMbeabuqm1tj55lqXfaKWteO9I6hapn6OoewX6f0azbjkbvGzy02v52fUVeDalqv5bBT/cBVJNtJH1bU2U+oCd11OY+nSMegynTQZyGjh/JmKr4+iq0Te1zKrCq/jkVhhzSqqQKIICcnBGECpLxQxrpgCPGgGMv59VtRVKD4Bhzrg+C/eMKxrnJdnRk8X8MzA3UkIR9DAplYuchpKWpAIt5D+2YMF7dQ50t1tVccY7uMXr4gLLVTZPlqKrCTRUSymhi5QV11em6wvJrdVPVKQgvbRZw27vQFfg9qboBxvPfGHVQd46RCHwlVJVOLoYaqrOKqVoQAxKlpDoqlp6hhmntVxyK7eCxrjOmYPix7DdtY+hx+vs3SmmveDNKbu8xgozOTezVGnDN1lC5xMQgUDDLeoZbHPh1H2LRYHRK8tsGUuZdL6cjrUH3F8hljOXo2OwJE11ztxotsN9FTZYDPQOCR0LDQTpfeNZsM3q0EiI48OLBalUW92jR4TsC/Ild34sWdXykkXNxRI2juE+S4zcy5R7by1LlHsu2xDXUeIMdFYK76xjC9azE7c+we4SXFiC6N2hVBS8tgFruyjzJDpr6ju5p7cu8c5QSvvU5PfUphV7pEO9VBo0Q6igYqjIcL7mYuZMafukst6O7wdp3R3Gb7O4YZ2z5C+Vlxn3zNHWQpuSYOk+D5M97vEDllNlVjJYxUHDqBMnVuuZaklpVkLYAu1pBJ66TomyWZ0XlYyJgdatXA4q9CFW3dVmscBrLLB3Dr3j+w8zp2cFiUTIL6MSOMLsQfFwrX4YlBQoTuVSSHACIq/bIUDC2NXSRB4Q6uS7XdJMDCpdnXWeCBBrDsZdo6rYYlbZGotHZiuGSddFc/wulOun4t2leYn1uF0IV1v1vmJzAAg0YSVXTngQ7t3V4Yk1xj6+Vu26GqOexIbTiUbkjjIthWSvAeO0Jpg52QGDv2E9d11qA8C0gUPrvBb+6LUag92XRrCYO2GebiHlrnjKAUp3xdaBMjrM5UAka5RzyHLeIv1bqo/UaVkvzKx5IkOZcGSzNeYBwad51IkikMxx7ua4RMs9eGj/LNR75/lEvMuB7JCdxId4fKeWJl7jo5tZZUrSxgtr/9kJC3RYjkDGaW6QLB9pW8LOPqA7BwYlJ28zqnSE0VmZ1FToL8l4QtmiidSQOti43elRkbaL1KW+SFBxz1JQlytOnI1Jzhkbbxs6Teq0A673H/+69Fi6Tx25DjWF4+5aYg0RGFCsXzXVNRGJ2mqfZJUUby9Ve19jx4zyNXdLG+gTBc41w1Og8I+2kzjwj7BqbdWSkIYel7N7YSDiSXQRTFSHJThCamqbqlqcmsLrlqand4dfdf0aUcJMzqXtGc59WmFiZrAmieDf5Ai1cI986N/OQlIuzAud19R2ViLBduE/v0R1KfxTN+jH7nEdpqfFZYsX2qkcsH/aDmxuc4SQ8aslwPlhNTgPmVE0PNQT9UYmI/DoivDXWnbxZimCiGKafSNrlWdRwiTbTOSAn1Q+0UE3JpCM8ur9/O0tDx48rXDJDwqciX8czn6nN/0LJ9PTk11cbxfYdusYR0nQOqA5Stm9+KLLFDPwhNzwTY3INHyb5j1kOwEKbs4gYvs3TozmbY89V1gFB5mjy9DqZQbnodlOmAA5PWKde9w05EJT4TyZpWCyOJKU/16rB7E3PsmRUjZ+jiot6PKGEX1FD+rwElxudabQXzfWgo8875LOmTO+SCs1pwD9LsO/Twd4C0h7Tp5+Xrjl6PicCZ9W0tyWzW43foQxFiHW3vdeT/En4HNGrNy4rsdW1xYeEhlvqrDifi3pI3RZKNPq0ZVVf3mHSv4R8Zq+VPiT54su9x8lKeY86vgIC71PZGr0SAdd3qlC55IYBArk6/GmdEtsWIH/EcYmia95eiL3h6WuV3KIPuKJpJuCoUgDgVXdDzIWWUkJJd9UaUNecpe/xDTslGgcBAV59rdDiO67vpMGYoaVBWVRxHRyrRR+CavibvROV+i8USZ0Vy716RqSGImD4UKzo2boI7hlX7tqzc7RAaIkWvIQ8btV7oKM8lJEpjMDSYMw1nHP0kr6rXyL3pTLZxyVyZs9xsU+vVlbSWIIUpZ0AoBB6IlRY/FFQEHrk6mnRVKDA3HvDqNsa7lgDzcetVAu1QQcpfcbd1n6HXdcwbLL9RzmOoxbaVV/oZMoUCoJVx8eSy2ulw9qCr5cbhG2/zNVt9fQKXaGjHTQGsi73wUS3lhMJlJ6a9truuoZB0e0/SutSD+0qUARFaw6l3WpAsE4XJHDChcxesKjb6o1ynFSAYgDaSacYXngT+0GcVn1nWcLbJIKpq0d4VDSrmSzm54QYKxqoexaT4NDaPNFzLlBOz7FzjKxtap5hfSKHLRaCa+J2aB69njuYbXLTxtyJDFDuW7KV2UnawG1HA0tS7XiuVHXkESlAlbuyCj5oS+Z6GPS8ZR8g1eYwTogU/PQkip5/ttH0QKLmm+OaxUif2j94aGhyd+A5RjPLSKZ2X+5tG62OODXJxq1NTbuN+NDO6zvb50aJEOP2tOqRRVPjP5IFkq8TNDkegai0brPS2XOI4aFeky8yRNSsZHoviVbAH7EgfLH4W6koHVRVkVKX50WvrMC3HhG1JJXmZ9Kqwu27DheN4HUNcBFprZBqEpWscZ/JiUqkQk3XzmIkKhkuLgP1Sopir1eq24qiDbbzFCyR3Ddj9s6zWDt7bOWZ1iAJ7C72weUGXPx7LTbgYj0opDWAb78GNXwX6R1/S/AYOA6TLMnXub7874ImNt3OZUCbSDJoHdkgbxfIrU0HJ4kNLXD8G6fIDkE2o4l43dS78XjIwt6jZvAh2ss/dVtRdJDLMkl/EIrP5EXOXgDHPd0xnkG+vunvUIbvUfnkWX123cfauU5c9ArfO1eXP7UjKA9xtZYBcgdHAEonwDFUUBwpHmUMobQ3uptXJKNTFE86n0R6cezlxsUQcx2o1iu8WjzPK4Qd83P0R4O8ckR0BoIRmv060KyDGNHKoi2CWMelOJdP5yipivykKFtumt8M0vEvYmbvKBcEYR3Qv8yym1oxPd903h51cnPTvbyY+mJlscS5+d3w336Kkm1kJNviBK3bovd7vidqjiaKszQEAZw7QbDAw2bBlLMjsgmF+VOImPY7m279R9jZzpKy028cbwjb+zyPivwUx/CwjGaBjOMWspn4TWRJopLwEHWLmMxiGUfp2AVRvGZuxeliLKWueHj+jKEEA04aatiob4ylhqC9RAlk7re3RVZ3vyu1AlzzqmDMXY5y6SvFb/PfBCVU+11Q3ZbBsdo2QbGrxRY91uTTcjV9hBPqAP1Hg8sIOcupDY3CdwwfC+9pdZk8Hj8ijhq+qAiiI8IGt0X5FG0jPiryuiyyGLpGvHwHpxXz9EKhBJssO4EkhNjTONjqDMNeASJxLa5t60i2aeuKQeZquJWIMp3h2wt2dVsSxSZOjjXRTsFU8oPFvwjf8NaT6Mp5e583Q0OnFcFElj1KI3ipBghUe8k1v8wSVU5nYed5tZA2JeXrLpZT8LN2BcK91FK3JZLsOQeyEMeqMFyCPHQlV+ZNmXZ1AOOmZcWo6yzeAtuvLA0TP6UZarfmwNVAEZ2hEhfB8SeYJw3DF+i6eFGTaVe6v2zobBEpduJpjmucZNssyfguWkmxq3ENtegaARrl1Rja1YKm3P/nFsty8DNHee4llhnTk08fi1sPiUxq3VL/Ig7LXhqr2+LItE2XOPFiW2+HYBLIDC5lDuZKgl+vXg2YJJd0sFFvI/iGoIsIqFzb2zgX2hIZY4gTCr8XKeq22qjQpEcPRakLUP1mGkONwTw0Ubbe45wCO+pY1nwcthXut0C96b24/YjuURae67Moa/NrIGvnKsfmTwj4bMF8VkO01+CF5qlQmNxhbtHXUue38UskF7cbVJaonKOtqA4XVDZon6hMZEb/UNcrY+qCNzHI9bUyRuuy3YGiqEgq1UirEsVThVgecT7LhseG0mY6fyWh2u8u6rZ4OgXHTYp1vGIzGG40W+F0Sw1B2pUo8S+4LtVQ0irVgAat2bOSxZAZdnXfBTvGs1+tmtUawxeX8lCslbqOLBGsnjbX/0Kp1u3/l8l8rOY35FB3qySCS1RnPD98atOBRUQ4xMMMxTmRDOXZGJSjY7FyNYZfC1INmCRJdbCuBis+KIu59zy0su9rIFPPOcgwC1afiNdD+g91q/XPvfBXt9WdaYOdC+Nct3ne/alN0JpMLgN7XAFZXKRC2XArQYQ9Q0rv0KLJ0GVS/fBge1qtesUj2TO9ui3DEdnWdOHK3IRddMajWBaTIj9+XFGO1L9KfRPnwWN7x6Bs5e9bZHoGVd/Vl/y4LMOVnM9E4ztvfB7ffkzYS9Gy9qx7nC98W23SlPCJb7s82aZjsNPqA16QhR06QeTPW7oqzhCR41LQU7u6ZltzpEGfFw+dtB6GjfOE+kMcFTl1PEB5+vSJoWNtjdcc3AEPgdpmP0ULX0UuzYjCX3glDrmg/hUE3asByX5HU7fVkj5Ui2uxbMbXvD86UO0qdBynRJ/NIqqRfN8UrgAdj16NQXlnAAgCcAcAwYJUy69lyCosXg319wvwOS/Ai6y5nb/VaG/Jkvy2IVuz2wzYZ8CijIHTkAey1L2ryF+JmPaLaupFRUb0viya1fzMTVqev9FRGsD5bM8etwjWq+/yDi3Rt6TEFJWPdYTWr16N0OzXnbotRqgInDvLm8aw1fA2znltSu5nuH10N/aquv1vz+2T86Fr9Bet99RUOl6VxbKc0eO5wd0szgr+UFTagHSRLC4fi9viDKd0BWxPpIUP9TI7LBacCjRdNKYir8l66cMZf0b1Q1H+mHx2z0pMBNMTW8JHnV0rPBoWw3n8mN6RUwGiebC8UWtC/igbgcMA0RFeaWtx8YBMwHJgIGMN95hG8syYRyaAK4Y0gtKPZQwaFuZo6JbzXvoOlyil775e9Uj2O6q6LeP92jQGxHZiDFl/f5kkFKt9qr2/+24nHwuKIJysNmbYk6JckklnLUzbXmgMIFFo0QVW3W00uMeULuxhMQPFMTLZdpwvyMxG17DcjjefEhbWLvCU02HZi2Z1W8/ATj3hUfswSX+c5qSF9Efgs4GjpE6y4vaVAuOeRaNOMHhjHyMibrS33Rtw+VewHuj4b4KVdHRjhZghVFWNncmRVA2g1uM4ixFX9VOSNzcJsymUl2hJFAq/m6NOlkDo9oJE3dZm9qdvGD2Qrt7N37Kvzmp/gO7Cm0bgZRHVno/3fDwbH3fCPQIbC5j2XLzn4tm4mClKNG9CpwR5M/EY0Z6H1W0Np4o3kU4nbzdz02S/45dFVREdPAvnMhHVns+2lc/s99E29F0f28KZNbr61asRoj1jaCaUJ1QbdMq8r04Weucd4kxk4T3w4sF/NKhBi2PCT9lBXSfpnWdU2+5pR/UKRLjnSXVbHMGC3+uTnpBJoFddI6aiRiBhRiRQV9GoVwojeZWcYOA1g2FPJ/ul+OjHxtsjSPueKELcJ7xEtM0NtIwWOOl4xDQB6mgAWPHeA5QRVy342haphpLMkBpQV0MqP3KHzo+rmQbBQ1sOZlTFdVAcSocxjWqZhsR9tRwRXyPITjzqZ5Sda79faSR/iYsyOBUgZaf535DTVo3eNVGedRfzD+6ymGVo52iVPUUZn1U7s4zp6GjyJg7TdPI2zPGaIull1Kl2epfamJ4/F0S4XZY4OFkDQeOVIrzd/NK0aMITw5BOLOhlZpJlwyxECPYy7MxwwJfIOoKYKMBeq7AdEE9y04iuxsDgQEYwOh1nDBik2oy75a/b8Hj2yo1hmeqOW79M4gDbGaJNp0zLxs0PQuYf4VlRijFDXH03KzKTcQjkbOuoKkNg+olabl8bXVSZs5iXifcO3SRkVZNdla0FUssNqYObw3KV4Fsft79BXvU49rJK3ZZBWkz1UNmoY07UcCSdc5Pu50He+vZuc+0iCnDPGJahgGq/Gr1X40R2WnKqbFW7Nogimj67wvM6Y8aKnxLzeBf5TU/QaVF5RnzjcTWzrv02qPbPxtrWsvIzeqg+IrrUAx8sDCITxriXnOq2YIoFJz7Y0FEujjyJa/ua8MnPJ5RUhGnbFORBL4NHmPbrRbNe9JrGRDnSNpyi7ZySbuo3wtM5AnbM/Y4s37zy22GklTIg2y+W/WJ5TovldLkqypq0eoO94pWN6u8Xx7YtjpMiW0TLs+baNuEI2macF9xxMMUJKPUDr8I6cpn8QGEY2ugKX/LwcyZZJCcYZQv61wzBq3quOCryG3zblGPvyalsD8ePRNjw7ooTBqfKmmU+BImYuLVzVBF5eprf6HxE4jTVZewgyGnOjnjRqcbpRICr49EWczUGX18eq6Gk62MNaFhilKc89Y8OQrmzj4b1ikO131Q1yzpKiJCWP/QRcN9MEwnKKjrJRPszC6P3WG9Ia2c0/5BU+ueO0cJEnTqm0Whr9X2aes9gjdENXNrMTWefPPWzXNl7xTxSYfkOrbLiyfO9iohiL9HUbXW7UqhI28yijqRfz6jUrJkyxvnCwm0mUihSs5dMnIaMUXOjhM2t69VlmeTVErNMYDGmAsI5fqPFhBIM5nHQba1QkZ7vG2+F22P95C1ZX3dHYgTWnkUi3IiDi3ONTNcjvkefuHieAd5jPj5oumBVN6q3YeLGfDUAr89VKhjpVKUEDHPKfQyzT47q7xUPdVtbbJ+cKOoJFdv0V0eq6eW25Rv4KKHKi3tMCLvRZ/enVbcp9os30O0nkvE2zIwl8xDhnTnOiORPui3OYSztuac10c5iNZ1R5f9yc1OhQP945jYWhuIwqdO7C/xn4D3AGVnkbdKV7XGqo6k4WY5tF/0x2iO2f+LVQZnexXAMorXWybjCdbG1cgQ/+wrSx8RHXkbFLZqBfqRjKQ30aiiFKhnBQN8seVXSEJXjtDrJkttqmNXQF2CqlqJpoWR1ECmdPdG9jFuu42n9hJbXqOyFTlZcv3zBEk39/vIniQVGsFQ9IuoropkK0FDpjb5SewDuYAFH0XZCdJOkOZYHT9DoikXd0gYn6AJT0XnGno3ZTdMn0hG8It2liRDoAEeV39hMwUFVFSlmJOz3N5bAoRXUZGtmy/Oqz1sp9P84X7xol6221nqRr20wUAWyL7AREWKSJf37y/9DGr5tg4NA5BrshyA08mY8JtLIl/wdonvYiwOmc9GXVlWaLGSxSSi6GH/pFg199kU0qIqwB85r+YCI85TMW+YyFAGJ5TmTdnJoTiwh+wPK6RHRZQ5t+sFnOZX7MzQrENNEu99ec8xqwcNEv1r0fbNkYLCKknt5aGfWhZvaPb7VjmMuptXO205wLNHlu10ISKxdKblWXw3iXLGGC+MaWgOYd5xnXNuSC7GKLDOv6BEUSIqC7tMOwx8h3KmlCnZ9htUJzsFuLEjS84O8ekDlFeMTHVNwcCo+a0FcuY1HDPAbxMDbwWtAx2fiNmAubFqm8BvjtQtynqbuXm281CuiexEdLK3R4oiaZFg0HRWXmKtCHDmu5cKVFu1B2wArNClJLhQjpz7EbJXkgHrVo1d2GoIG6cIDOpEFbMGeEptftdoRzLB2tXNk035XZWOLuLN1GplRgIPYsANxYUARqz3r/fTqlWzY8WIhRR9mYB4FTXeJbcaixzTN49USl4XGuAFG0krJ+OwE9mdGpgJpbdP+qOLGGGxIdba+jFFxgAwKsdY6MZs9bwGYAcayY1qfsR/ijF779g0YuzmGj04FAb09KeTV5UGNLof6OsOeqb9iBR09OlgfskjNaAwK26dAmUYxg8QyzZfVfsilktyIvDrMilt6j2E28EiQEF/2QC4MKSPeKWOPsvszsKByTnbC6EN7f1Qs2ZX5wDg6LhGBVRzYwbkyoYQe4EMVg28HH6pGMBMrqubHpvm+zuZMkJglUR3nPFCaCwFg0BjZwjlZIiHUACeOczNMdSzQ9WYOG6OGzjbNixk/NsNZbcKtfiw9TygZAAQHuWsE6cRkcBuQ1RtGvnl5px/CHLypnScrM3hbZWsYs0vnZ8s0YvbsKRhTyLgtt7H9jDkewgYYczxPVozZpwjclBWlSwRtlJUiIHhW7mCcDskiXnvJGG/vVXVijrOtgq67INXe4arNUXCwIpNCQ452o9Hd7OkqQUzVw7swlbYNyPpix7gOpKERU28SNj2lcW1BwBApeDgXcoD4N7HOdB2ZYa3p6Lyb640fkcuSG9WbbtWNm4GOVvYcHUSnbpt1IVFfZTrqDC3Y61wRaNL/OEd/NLhE7csNY6ehWi6Uiaos2vYPoCsAZ55EL1lnRboZhJ4ViWz60dff9CHqivANvkflEwsJYzjejIA1ByiPk9MYNcRrfD+nsxtpejPfEQiksxVXcfU2zVmHTb7I0GmNlgd1XeLrpkbtY9irdYmJ4WxwaPhQWd2DQa26ot57uDFv68nfZYTzrQUXFrAy2A+1tmeBdEOxNGSp6lkthCDOF9rbQeuWaSyb4Gt4Fu15edNWL3lA7ow8Hwv7MG9M1ULdlY3w3g5aWbv2B1vfYG8yMIFUQcNtPsZXZTMONrKtEZTKUczHpcr5sulCX2druNRSJorwE/PoDm/lqjFsgEF3V4iOjLgj+6GBg5QVNQzra3E3NuloC90aFjaOaD5eNs6nTVf4elvF2ZbCF6ozEz/vsCDWjWNDDLy7AvlihVJ8g9tAQIPFw5aB9bU1rAxX9GBqQw92kL3tRjQfo9vN8S6wPDySLyzj5JWCI1Xs54ELfNurQeP01NejO9AjOqtlufmlEjDcGRZOAG/Y9A7GsKUbiZbB/WS7nrwb23K03bJea/DS3/yKCx/7xncsG77xX38tnk2vwn5bvkxuNfGZZNjIl+s8ZrUKRoojhl9qcX5LSpzk9TAtR8XyGucM0Mn1wBaPhnAaFB40te7Qpn0ZXDs6n1xwnVObnm2TB4RufJYHOgsUW8HxO3y+cxjWdiyNHTzpWYzqH03CYjR/zXHQquDxbMXSGHUIWB+jgW9yM4A6uh0cD82pTc/4etvG+747gIfYj8DQz0nAb49Uf0aivB+K2VJni2AzDK41x0lj3DWW34T1zXW+HRbB1hjbxMGx4PpXKm51ZE0tModVwvBEWCr6/qiXjWnVbu3qsRrw5laSFX84rCoRxaYXl9PRwFb/97GibZUmv2F1fbd18nfFQ54VycIuag4IDb7X6wCdXjKCyDcWOEfbnRnYS0trm/a3KXTO1UVCs/UMbGESMGPwyNJLQA6ZpRXsG192wX2ZUXrBlLa6bRrV3BiHXWJUklHTKNh89gwVE8DgEIcNkC48pkDvmOuD8dmmdUD9UGZgUf1U2XSAr7cFDGoydEmQE7DlLpqvlL2flQl310R1ju4xerC1s46hNXtvC+ixAwst7BIrakcw37YNz9HOseQHlK1umiynUb3HTGXFQcrqRqblanrzr7p1NUPDS2bL2No4sLn53DjPDozfVtwY+39GDxV7bmoM1y1BQkzdA7kwsYx4p8J1K7s/A1cq58Sm7Y2H66a97yM8D4yj4xIRWMWBHuG6QfQAH6oYfDv4UDWCmVhRNT82zfd1Np9oxS51JwwePdXIhlJ0Hj/WqMyT7KCp7yh5WxdeIW2okjZWtSFS6Sq6kM+uAzuVn8RpSDOsd6c5drGNbEwAnBRls2RZBowMLoNC3DxAubAugNqJT6NYg9WdmIGz1MTdHTa6LFY4teSjMaySkRiYMycJyDfESnAv5uIlmMC7w0xX7N/3ZdGs9JzEASrZyJmDeKQA+3B927o9U9X/uRgPmA+bpte1tkGItVxjIWTaIU8hvlrMKubbUr4Duj6vwBvNhzXfbYH6xfGLWUviBhxfBeOQq7gP5OstYUHFGGbV4eT5sWmeVdgYK34pF/ZJRyFgiBUZnAsbgojtc41G0t90vZiBkXTUtWl+XHPDHGU8D4zBInLRbto84L7PxnU7eWLow9d/rZJb9AGT3pRPQ0x8tSOlrpYuAQJfwSdNBNygJqPB9nGp1VBmYFqrObTpx8YzIIAjaSWfEz+1y3gu7m1bA1gXlNlbyrejQWyKaUfzZtMJVmGzmzu779LzqACn3N5dL9BFvLvDgoqez7XDy3OxS8xmcp+TICdguF10mVP2fla220FXuff4pj5KysXVGenyXVKhxXdc3605SMUuhnoQW/ZVXLjS1IxKLELcH+9hhWWvZuA9y2mw4kQQw8YZc6REDCxk4hewlo4pfbVGfYMAe6pWweZlqNVQZuRp7Rza9KOvs108/JVfYm6MPKo6GzePW90dRdR+MJtianA+bTozqrhZtfVzUSObM9IaTqmyUhBnlZXDuzusqej5XMqqPBfbf0Y6Rw9k+ZwVBEHVrx+j7V1XCWJDAN6FIbXN7ZSV3mYkM3CrzfzthAUfGoidImCsaS32wPOPS0N+C0Zu1iVo/R1eMfdzPZHGYGCo+Q7CKaz8GOvubC9wx2dYr/A8bP/m0ve7PTP3rGLiixG0julcbXFwA1AUdQVXbw8LgkOYkRPBObJpf0CwWfcC2o2VtceKAB3R2UDEbO+zEs/spu3JXLqzisa2PLXaAs+Vc1Q3ZX6O/miQzcsIGBxWBzhIN80ZbGLHdGbdGGbRlnXztBN68rrTlnJPVSH6o72YAtBJMylYs0dJWXPpq7V3M5o6sJYyBnfTVNRNqe8P1dml420WFj2bRQ0xToVNL9a1NqgiCyMxbhvKGpMz4W7uH8ZhbIJfd3IXkUZhcqtQVZicU3fR18I0iE2w6Q56XpwVWfatqMkougfW9MNBXj3o0kGr64DhiARwpzBEmqYgbl13fusY1mIoM/CsxdxZse1Qa3NK+h1KfxSNGBNb+qxW2i0RgEo8WNdJpbdtHdIepDFuHbe7Dm8G1nedbyslQ6y8QWtK2pSEFLdnyROzMp7mmOI13etoasG2lXEFN/OKrjH7i40oJzOrzsxiLrGYAat+cPW2hgv7KzyJbWx5RIXAhje97s4tmwe41bQ0Ni+UXUe3AfY3zbdNl8S6G1sNdFrvydx/LG6vuN+UZ5QLQFMH4nkOxIXPda1ANkWh81vH2RbjmYGZLebOphdC1a1gX6OdDQKeiGF307CmG8HMvLmT5jQrLjRxnyPX+XLbVmQ12BCj7SyDsXgiF811lZa4TUhvF2UNrKIMGcNDO4eOgZvaUOg1bWdmYDQz8XeC7ch475MafUIVdcq/OimLpZHvNHXggPA8uFsYeHVD87OdRW/mMKGaiW/TC77etjDfZeHKeusakzIe18zG2U7uy/xMJ5Pdpg/rWps7U9zc4Ix8QVcmnxoJEjxN9EBOZwkJ8+yxr5RdmOMkoCKs1dl0w06DB2kmRIKmg9KcSiFw+FyauV9PKtA7BlPf/FFBP45ZTqe6eXLR42i9zbl81EVJ02fhZVI+HT+md0l+i87JUjtqStJE+qT2/TDVBJ1AaCUnzw9jKyDrdn2fRhRa92kGNrSeBZu+aNBsB4OyP9w4c1QlPkuO0W+YF8HOzM2EIMEduG9Uf2Nsd5ikP05z0pf0h5uBxVQRYkFFHRemNDa7U5Zn29HMwNu287kTlhvVYEx+noZ6M/P0Ljp/Wo5lgwy9g66g/2hQgxbHywRnB3WdpHfs3v0Ea45X6ioQE4PQLiysac41tfmmOdg8lBmY1zx9VoZMvMETFzyET2iBE6r66NJOmqvOyMSjZgFmHo1osudK9n3bGHdC82PTGb7eNnDrVTuwVB89W1XBwJme/DhuAuDCUZ+3TiswjWRengXny6YLfL1t4FRu8fEs5ibfeMLMJ1X5VgF21qyYreJm/Yg2JoqBObXpC1dtY+x9ulwVZU36dsOUnfQOLZoMXSbVDyVfq6tADD2CdmFkTTNQ6BW+59PYxMwdmoEBzcS36URXD+e3tObGmO/40Zn51FXgZLeezKdpZjPMZ+7QDMxnJv7OMR9pKCtax/qeTfQ8IVdQM94a1p33gHYgPVTH4Jvfuk1DmY1n1bNmaZC6me9sf0zq1E+kTk1qoLJfNcukrL9c/wulNS1Cj4QRUmb4TfK8qBmWX79W6CgrKX9Uv7+sy0ZmZor6AtV8Drjq5Yv2O8dXXco9iUeF6snjUVKj26LECMQylD8ZcZFf9DEuhKYrMqL4WKRJhv9Ei24G4U6JUOaufUzy24aclkFsXZld59BFXbIXxxVjPmX3BDgj8jNULnFV4T47OIRYhDEi5R0JIIRjTw5TD4ssA3tFvltVbh9Zq1D0b90th6QbjhFJ5/kD0mRwljJ1hJ5pFaumLbNYMV0IpE+ovivAKR9DmBESKYLIqrgnohXs2QjAmthMXOW1juYdiBHlYVbc0pSXEK6+zMxN7fUCyEr9PY8BRZ9SCcKxzqJmoo9OdFrLzTOc1k0J4uiKjCh4iyCEZ2xytaOurlsjCHPvkry5SRgsuG75cuuJo1HZcImWCr4EwMyoUYbvUfl0iZfgsPlyWyquA01pCMmH73JFOzzXP8FZrRCvhjq2jWrZfQxjwfUtvIk3ADBb1BcrlOIbnDLFahiyphG4glnogtW+sMtTUAZr4D0bs2/GlniXCajIrUttEX1LSpzk66gSR8XyGueJijjmWsaG/9Ek7MvXHIOigS/3HYVD122bsMHtjFTsDIsT4tD5LhaMSXB219Og0Byurk1HEYxKciaE9Zah0IjmM3qoVOK2LzMiOX4kYjFPsoOmvqMHwHYRqTVzHbyxsSENOISZy81ug0Z5HOTTsJsRJSoMdr14XxbNStkLVmpExIJ/QDi6SCqWagKXHgbet+AspwbsQPIZGDucP8gSuw6hHf1UC4FL6WaDhqY3UaJpk8wY0MhpDWB6gekPLE5RKjVwHc/dEomKYuPI88bRcnGD4WGOwjob+zYOzgf3TwyiaDq7iOGewDOMHJ3LFa1yx1HFHTPSFoqyouImRRQdxzZscJuNDuuoBaDdgQ8mYY+KWQr16NowJ0bjFtgrm95Ij5eVkn/s72nUYPhngrDOMn6SaaJa/1gMpNf69Z1pNXKXJuBKHF0lGWcyU2sV3DsuA5qvpQbNUGjeeFCOiJKlFQkijFmvuyOnb6ZXXsNWyBGAhYmrgI0S3XMNo0mLvR9QmGyGJxk2nfiUMImr7EtXbjaJwX63oIVM5TvtYIG5RMtVplgFEJytFexJh1iEsT6GanAKIBZSnYItNMbDMYR54GVRVaRipkEpwpgH3m4napPwCMDiMAy4u8CnYtBtyR69Aan5zMX5ioFnrJHDnpEnl6sE34KLqC+zsKqyjUW7YMYgVqfTj6gmp0XTUochLfqcVGT1fkf49g4k4wjAFt07TLihUvRUhDEiHXnAQBgFjyOTMH7KU50sXhdbnPvHV83wWV90DLBCqhmu4AIgoOPumPV3kVfrm0yujuZScl1BvBLnw0Zp6g1+E8OQlbek0q27bRO9ywTfxPo2V3R6GBPLlpD8XbKZijC0YXxgJSX9xCtwE/Vg7BOTTryOvhKum2XyGWqoB6mvCJERuE/XENGAHyCkMNZwYhZZpmW9MYBmKDwcSJn2Hl9HjRGKqbloHbS99SSAh86D6HvOQaqGP3giGIjAowLIAFLSgwTjS/4rIgOJLEyJIsr5GMhUsailHp25MkQ7yV9BQz+LFqBltR5xOFl5X4erwScDoCQIqBkaBA/SS3DH0JELxDkxhfqgORraiCDqEQiQED04vxsNKUREMxFBcI1Rk2IMaB7HeGqDyTJGBxBHz3UeFBpetHMdlckDQKkHIwNDhOGcvTSEAXABVFESOYQghzjLuJx6OqoIoBbDGdeIQB8B4UxE6jzD1gEyNFSSYM2jEqvo6LT2Y7Mgl4RYowXGoFfv/abVAmUg9UAkWIg0nD+ehiYyqom1QtrgUbFk7t5rt0CYHhKcfhwieDDDgEgB+ihJ7aM1t65349fhkOoMwWmUXgAcVKIH30CdBg0hA+giOC+G02bknXg1eCkC1IEhNUMCK4AUEp0odYSCsUInDgW6CFTqzdNmKkEhYbTjEWLBRKKSEOtFxhplE+vuLXRcJMFo9hYBFJQ9nJelbpsSUU3LLr2bzNXBapVhtLgs+H7KRNHCq0elqwYRi3Pj1tBKixXa1pVT4EE5/k5Nx0YgnHpMEDhEIcHBVkMlEOPcXCV014axxlVcuGBUMyZ7jRFDe55uVqIQci0EbWg4QLuMsq8Uk3IDzmnl+tAw/JxAQzKwgsUIoXoRCAeiBWgHjzOWFnE1fhqh1CHGcMa9fgSu0R+sFIcxMohE4+cfsWijfHbBZ55Xk8yqunHwNlg0BNY9NzFT3qpx9XofPaGJPytdJ8wKsLKKAwHGNa0o7khioYVpxajcuhMZfQg4JelmJdqgwK+fTClpJsMahyVV0VDM8sihxDy1Ci02bGYyCdR+VGYG8yTXrNw1UubHr+eURFPXMQ5SWVVDRvuTibGRGTRsqA9mPgTB3QZq5scwQs7KlvCjRAtCGioah6yvryGu8sGlmcyGNqcluO6V6ZXqbShwN+6BRnPL7Y4NvFfXv7jVXbN7dAC6YrVjiujrRP902HXxaLH5creewjMuM21HrOdUwVQBR+XebZ09sFaflHkw89mWgw4+J/O41DKq7X8sqmjee9vaFqxRGClgi0lDaf2rd/MkWHdhPjuGrkvm/dumdhBVzDv5lHMyqxKl68g4uIHXdIxQBFGFx7ShiRl1AZgdIVjEVFPksUL8l4XHWvCi82a5vm9dq75a1/UevFZRnYLaWsVUhJmQ/uy59pWi0GEu9HicyaRF5zBH/bt194nS90A9aUYuCVA6LbcFR9lvK+Dt9M65RXUfmcboRAUDai7nIHjwrm8dN0d31weim8+R6uoiWa4ytA7ko2YfAdI85+MKwSwkoIOUYxXJPegzBCUaJ3AF6KOAVA8IrgDRhw+bpKGQAuEML5vWLWt0IBnIZigaPceZLLPqMufoHqMHC6VQADSugDF8sGsrjHVGEn1A2eqmyXLqrz4qMNJMXdNyuEoEcamqbkazNlXNeJC7j4mmdTuXgdSjk2AhenFR2jSEklFN7HZOG+xdudfB4mB6SHD6cYjgwVwEIgXooyR1yMMX41NfBaR6OHCFCK9eZn/KqwsGqH0FY1dRPWSr+hBFDeEONVS2a3LilzNDjEQtdQEo9bhkYIhufORGDZEAZHNQhEV7NJNEADMMYwytJEofh9JEFQHdHGS54oNQKmjCwxhGwIEqqJGYycAjAWgwCqoZkzm6mJ5azmhhrOaxHUscnmhxwcSQKBokNPjQqDqxwcHZrHVuLDFEB4dOxSEwgT1ow+J6WjxuBeHUg4HAIdr0oV81dAFRTfygtW1TJ08FCFP3dVLUmgZzyU4oTu7VOlOC2mMerqAxd+nq6TzmxWC8Fu7zcBsa9/nJKNlFNbYkYwvtOL6WXyYkYNsAQD2YmX2XIDvoKOklghjWzxpSuQrN51QR0zwk0FiPZCCbzmusRs6EmMdW1EfDvjojPb5LKrT4jus7Lrq1TBpTFfXgDDUhsnGRvDVUMyFWsVOsUz0UpPxqHWhcTUO4gnmgYD0d/Rwkk74NgJTKOYpFya/8hFqTc1zLcbyjylMSdtzQ5HKPxpo3iH4OxCCn1pBKidcFvjdJPA7TlCQAguNr1VEtvHpIumoQpRSx/TVE07YwsR4LtW1coeZKboP1XDaOeH3nyjOEHbNEK0koQKjHNQZUhaEDH7Dq8Ey5MPnkEFfrlBNqIowBzWMYwetIYlbRYJSQP7SKyt5H5z4koNGiIAKazsACfNB5WsQ1sVFhlAnEIM1BSJ2AgCrAUmGcr0QrdUCkEwvtdefNLKSEtbgrsmAkx9unmdlJTANjeGOuA9eJEGUtWDZJ6Wu08kmNfK434lIfdOtSDewwSN3qDKTfXGtUalhjn1DCOoxLY60IpNhMfi5Fln0rahb0nF2X8lmQAecWDbjG1URdK9yNRYNbES1ZEXjZZ0cAs0ddAQmrgB3Ctq5GvluiAImsSpGl209s24MWOZDpK4LeMk5/dXWa4xonmeYIpaugUzg09WBlRkrepdVndOinPc2Dycmu5MRiZmIq69oPXIXChsS2p37LFgGqGyfVJ/DzOkXalZQuTaa5Dlw9aE0tMBT0KEmchpg6vJCOKWWMi0o+nX4EwtkNTKcVeVFqLl3IRBNLWphoYBz7nGOWMvyZXY9gaIM3BFhJ6WIhpFkyuVrAyCem3DiP4dVJWSx1pNOBa7Q1dS342YWQfVHrz6xGPS/pLgsHwnHA1mNb14lMNA7xxCQbMl1eaYwoMpBGwIqwoLjmkm/qhLWEa2KDyZBj0/iGRwGp23igCvBeltk42yoQzuDSTB+V0ScyeJmUT8eP6V2S36JzMk3rjJnAId9YSXMmN9WF060UpiDSZrwgNdc5Q+OSkv1hTcMxtOUgR5ViUG2McGpyKTKeanULYx31SE1VwRQKyvytGpIaG5o624SifY3RzlTFfbAaE15Uos5j0AMzqF6dYHg70UCrB6iuBNFQlftVQ0FNAxM/poVbNj3NtqjlOljTo+1oVJ37OTfXi6txMl0tXcewVoMcVTHQ0JpyY6QAvYQEwhNx5CilsS1D8pVc2YQnwZTsyLcDRl9QT5kHcUcpfq8u0ju0aDJ0mVQ/IKpqoNXDVFeC6CgmJdbQT4MY8qPgyuM8L3ShnAZaPUB1Jfj1oDXlNIhno9w6c/PVWZ9yWUU3ANY0OLmKmmajRNNGsgGYISmonQ0N1X573WKhd38JzlE5lP32ms7EMuk+/PaagKRoVTdJ9qlYoKzqC/os3Oua3ZcXF6skpddY/+Pi5YvHZZZXv7+8q+vVr69fVwx19WqJ07Koipv6VVosXyeL4vXbn37699dv3rxetjhepyMjxG9Cb4eW2g1TKKUpIxfoBJdV/S6pk+ukIvNxtFhKYBfkiFN/uf4XSmt2CfoobHm/DaTuG+yiXLRvr+Q5pNDU5N6D09/dSZA2xU5Tr2ifXoFPy9Y0PCHDopPPRoi4GVfUIzUv0iRLyrMuMXQveRdk5EXWLPP13yLvqWtfPFU1WtLfYyz8d3tsp3maNQtEdHZMaicroWdSqQPm6iypqoeipHmxa0QTzwrIIQB7/H3lMdL1V3tMl7jOBGJ2n+xxHBaLpzGK9os9hk+oTv4DPT20Rige07jEDeM7NBzCZKSjQje8AM24z/a4PuIlYa3FZdEbQXiMUqE93nOUL1B5UH3HCyafebRimT3WtsY/i1wYOv/dFdv3Mll13h4Q0lGxK+6Lu+IBmCmp0BXvYUHv4MUFLZY5rOUSFyWRpsJaHr46ruXL5BZYzuyrjOm314J4F3eQ19IWImzo4oZkt10lj9p8bA671oBJMkna7F262tPsYPLe5bprvcPVKkueOlcXHtO4ZGtmm3ygblphE90h8ZhkZc1tnWDmXDVG0X1yUJQoEcSBDB+3hjU+FqTz+E+06LofKg5EfD5CwQLHNJzT9kHEsf7qoFh0QalEXPx3B2yUIIgoYV3UkhFGocwDqwKhOy5g3YwKtofrh5hhQbyuCIZmw+LKqtsqE/seHzVZm8EUYuuh0B7v1xz/0aALVNAD+hirUGSP8yRLbk+XpD/04kIeOlDsoNrXmaDP0w+bP3KcNdcZru5ErZj7/IwVnFbKXNQlc0avmL0twj4mYPTdyoxoplnzcfegvvfychqXuGMEdg2hyMXsQ73PzrLmFgsWh3GJC8bLokmldcV93ppVcIbKJa4qvA7dF7ICRGwe3G9Gsa27XVwz5zrDI49r/XVrOMgUrNOee3Q+chaco6++rVxzUiLUP/gUVI5RiYNBKXk8fkTLlWCc4z474eq27/aJg4BwVGaPlbnXC9j6by5Stl1ZrfPlWNDyJVOv4E1J7iLLAqU1weAjocFqu6CPxJLx3dUGxCRD0Wa0cGoh/5J/IGLwjHk/jToolDms1ywrHt6zh/6XxbeiFpeuXDzHuUFtRSNsThgcfa1T0ZTGl7jYeBYgPv77fKe5Dcqb/i1uqNSB3yBbyh5V5WkkEG1RxNB/m1PyfG6W16j8cvOtDS81QjUuesZndumteSgj8m/RPdlRj2I6pmyXAcSa65L4E2egMTnikt9fbv6bSrfvZu6/B+j3/d3yTLTumxWx8N8dlNbV8IJq1KX1ZxcF+GC1Kot72c6w/u4wzhKRvWzxJZe2uXGJg5l2tVBgHJfMzqUicx5mxW2XFsODL7W1J+LJtrlL6mo2niq+wMEXiAyBRgwXe8V/3/gsaTPy2Ahrff2JJHXbqCSm15/n9fpqRy8zDv/dAVtSS2aL/ps9li6d0X8hcnyoE+GqRCp0xvu5UKMdyraLu7kET6GMrkU1Kc+37Ss4f13o4MmVVN1oBC8u7vvG55FLseQxddra0+4lsnAZl2xud+qzUYnj5L9v3REljik8QE2eWz9+32CFhtyWOOiNFc0PJR6Y118dDDftk6uRzab9tAmv7b7OSVEuE1klkErdMV8kWQ1jbUscTH6LJc57STS29o1KnC5F4YuJUYFDD/uwDyIhRwVzX0q8QxmS3g0MH90vN4b3ltD9xlC4qUvKjwk5G8AnWqFok+dQ2pWPxS3OQSOuXOqGuY8VpUQuAWzNXrUOihKyVymivVhsVcqa0+xURAY2dSI/LOG/z3saY4/L5MXIfXbjRRnV+uu8uybZIFZJLnov9B9d8BABV0r+tdxnp4uhGpFv9zhPATdrodChj9I7kCPHNyDdQngj7rT9V2dMb0FMb10w/ROvqOknyWQnS6HIQU+5K3LUXlcIagpf4LB+kkcIG/d5jn1nUycNtgZCne+7leRz0FDVnEZ6y7LNVa6xe+qqSwAIXGGvi1xxwg48YpnD3vJQfER1jcrTCvBxlksdMN+VCOlwA+VOl5SoxCmIWSxzkNsNe1t9WXxLBEV4XLJrjs/PzEGgZ/RPqL4rAh1Jx7h83o8ZEGyrjFK+fvZ8+RyfO09vcxrM7Y6GkRBvMcdF28OZvG4XyJg8Kh++1NefysQbT4mOsA1fX5foHgMHsXHJronzDTF3f0cbxtc9Fs/7b7jqNNwc1yd0O0JpMFWxM6pWgBa5LnLA2TmBdHWPZDsiDOEgC4ra3IgSyMUL/VYgSvvl+fiE7kOpxHzXuHNH+bM+omgEDwJ/h4GZ/QPoSBTeAX2Ryw1YSUbG3rOzGACg040Cxr6Vb7jC1xk6zRf4Hi+aJMsEuQ8CzPpA4Y5FfVOse7nUwdbWZJkSsVS4yZvGnonQkuhr8v0gULzpBxZ9HbXyCUPsNxgDbzHdqX8XyFyOQO1KhHBXslqvt4tmCWtYXLGXeqVAD0O49545wcH0ASG8xqBuRAnkYSM8SIWrq3HJ5pWTix+N0EH6wWF9JHlzk6Q0ZkZJdrQaugBRwdi38r4WH8O3X1w8GPrs76Lzwvq7Q3+6OpDSIJa5eMP+0eASfanvUDnoYIJfLATh3MJa3YDxj8od1m9D5BZZ+ClVNA4WCwGbuJaN0C6z2wdyF2d3/d3B6NLVEWeW/+7gP5Zn7fLkYs2PPMmAcpf199g/uFLghyHcqXH8uMIlM4a9S54qmDIijHsrzD2FYYDWlhrKQbtJqouEKFsIZhmg2MWng68pXepLpU69pi6HB7clQrJuKpe6+TcOFWUvWaDYZV0OaQ/FhckVuMivrtLRU5qhjyi/re9ECQZB+LZwhkpcSPOogvFohSkYDI0kiSEIJ4+9O7w6zhNy/pOE4qjIBac6PIRY5uQjgulKTrK+dntlI/mMKKA25/N5Wh1XEm3ZJxdj4hA+VGQzochJJ6OG5/yerFhSub10FLErgVzMmEX64x9Nwkw3oh1zVOR84cHqH9wnOEuucSahV0P5tQQPAoZwmAeca7DLpQ6ngeKhHXvnzCldPgDlTqckfPPE7B0nRdn37xCRs6l0UlIDOlxYJOkPFvSYxuKXHv2JhY7nbXUaJungbZ+xSd0mM4WQqcXLZgnPOwzh2kLyaGpBhHCkG1p0VbG4NQHFTquO7juHzdNhU9eSW4FU6oz5O67uMlzVGvQiiANlWlmTIcLuZ+QcJlvGYAiHywJyGmJVcSq+fxqVuNgfJVTOOL5kCwDN+quzNfSI3tBCdtC2wGEPWqEUJxnQu3GJH8bhOu4SL4HLOi2kX4vdhZ2xPRHOgcM68+JxXqOyghgNAnDa9ajgGWFBEPtoAZ1OwJbt6QCdTmKXGLXrUJCMQpHTfo6q+qCuS3zd1OioWF7jnJ1vgXEYgZ3GQmRim2/sYLXKsHhWAAHs8X9H+PZOTMvQfXOgDnDOcz/ZfccLEUn3yYFewHg+OI9n2CP04kUD5tGWTrAogTbp2RfVjSrSO7htikqiGSu+R+UTnUTJfiaU2WPt9civOZZussUyF82/Qt12Bhgq5FIPzJ9QUjUlon1TYB9BeLRwsJTdjKRCD7z0hxY3D+CAv8kXGWKXl7K1Typ0xXuGSvoIHjYSKUA826A00DcxQHiPomhNTUT+akfCg22NG8+wcQf58fRYPBx51FWn8eSh/wb6u3c9Bu8ehDInCzvhlJSQSHLSEIrce6pCDJW7Y4fEj1j2fNweuxvI6qhoVkUuvxmCyp3c3kGsftj6WWBX/0z5V/HrGMLR46C9YyPaAuRxwBdujdSLlBQsICPY7qUDo/5yMpb1VxflM+6DocOiJod1JVag2MXMiRPJtMk+uZjr2rlWefdB5Xs3OT2u1jFWdSsmlzpgxswJSUK5/uzSy1t0gf8ULavDV0+3wOqyuCDn0rSG8Ztg3fv/BboOkQodzc3nSS49xxsVbNzFdWLD1+44AG6jmSW+MWk3DDc3SZPV3zB6+CRpiVLh1ihcnfQMfC3SIvF5LaKqOY2y1TV3iPNEzIYjFLncmCyRnNBk/dVB7KCCZoTLJV1uVODi4fIZCXf+3Scnr5syySssOa2NCja58D6hBU4oXwHPOMWyrVl2fMfC1h6PyWMB6qtPswppvwX5yL5szex0t9lxZOMIl/+DurklZcRY0zEV9517Us8/nwhc6Rwmn5Wurb6txo3YJgmeCqpjvwpmf/TX49of0HfvgL4/mD73g2ksY8mG70G765s2ckmMK1EOYcDtqBbLROpz17TqysgPGzeQ8yZDqtjOFuBOrpPwLeWowOG+oY25qAhRJZe62DG7ZyswaqDY5eKyqonwZoKWzzsrP1xSw/m0dqZ8RgpBeLWQPFEG6YP4qVoRoHxa6icA3G41YH6zxISBbnoGAIfL8Me6TOSTMfd5e0Qy54cWKIs5TD5CWFt9W480pG5RfkCPQN55ochZtflYkEJZYvNFz/io3Ymr1ruLunZFsdys0fkbb3Q4tt9+07nLwRilQnevVNgf1UeJgXWV7fHMChceE8ZkwlmNyuFhgrCHyqUOJxC8QJd3zfI6l3IBCEX2OLvgWGNsw8cNnVOf9flyW4T6wIMtS0aW8QL2GCLfiHKaHWDdAVhkq7OdKS1p/UCkLEl8gQc+pu4okQ6lLreWZyVqjXdy/IRR0bbxeSQHyDE2Hz9II4bt11tUTmh+7men1QkRuc06xpHIV1Lx81e1411uAQj9mXYTV10xGZfvv+5yas/AoQzMHmXf4JQ5w3PabQRWhlH7M7Utvu1nb3gkrU1OsqwaYB3vAVsVTHp3Jpa5Orj2PhoKH9dR8TNeVLrJCkyVocHskznDCd00Kwrugx3/b+Bl3XPgxCl4MBr37Y4Be+e4oZO/LNFDhJ2d4PHfxsHKm57pzc7Lt6TESQ7GRIkyXxr8/vPohHSiHSQw+nN4XOZ54kfHiIH13KLQ7Gr8ioOqwrc5OWP2ToHiG06g3MlTbGeip5xWUJb49dfNnM3WutJ/LoW7GqHIQU5NEGmUndi+NPWXG4aCaRxQpEMZZGt2P551wvY5HpPHjqavvmndRL3WJ8luuSUuFGqp0b0lFMXG8Hlr+FuhKE2i0sXT43bRoNZVUhoRoHJ77DSUFPkkxd3mv7swcJ8qQuTg9XeP7eqoyOuyyKDQPyqY53/gjbvmIiy22VdZjCghm9uqtktws3v4SaR36+4YTYQr0E0qx8U+KEQwBLZr3MycVC/+aJISVee314AHK1/oeMw/WPyrqWo5R4pU6HBoZ4drFWK5dB43k/lURrYcoI1xVODyZCz/ocyLKRVO6Q66Kf/3IRFTiCwcMhS5yz111WlkXN/e+waLRpVRiYMbYjVkUvpaCnYGscy9nxLKQHxQvBCxzEV+5jVqI1GKspMrcHLcBWKG+AQMOX4k7VeSlsJ9dpGN++AjWxp8hItyHyLABjQeEkxTd/uP2+DzL48nX7rHfv4P/OK9qnC5d9kQKxOBUoW/9OmxeDCyuuo0fPwxyW8bQNjw3x1MO3LkB+eoD+zNCfAMxWkLazJhx26/bOYdx7ZHOm6jVLS5zMEU1EORA84uZ3JX90i++IMhHA6e68TM6kaUQC7Hk2kiWsR/i/O8o8RsaEegymqZJ9lBU9+RRjsvq3OUMlqG7BI6zB47hxu6aXaTXstQaR+OT9iX0vu+7pPbEYRS5XRBaXKDRSMIVO6OvTOlmBoBwOzb+kIn9rL4gYRlyH93xHaQEp29UuEclTppxvd4gUpVxA+ofGtW+0lRNsuzogq8QB/QeKxjTd1pFu1lscKpiGL4uKnFL2ejcU43fXawWJBNWdgLuc/7h7OuS4OxRYS1wfD4Lg5F5WlWB2tRRDF83NjqoCSAbPCjAocjShuHXDid9B8dlPJeeo618OGrwy0DJidh4X6h/eRyuK1q2rB8uF1/d8emmkmo3B07iz0D4m1L9jLLTWYl4cLKV07NKqLel0WzAuXUULILLgoawdLtPaJk6T9vQkjRpQmqT6OCvcCy5Jl9vJOJ1DYmAiKobQyPrzhUVJ5GJm6fBHve3D2V9XRDq6Zz7g9ZMO10uq8VRb1plglrDHplNSpwxCe7dXCfN3ctGufk1UV97WwIUigkqdTl4qsNJqpADRQ7zstFndSNhFcocu8vjFYudTAhthFcYcRSoTPeNjKs0j6pAnLnuKOmLFGePgFpPkEIlxbaeudEKIuY+RL3Pl8mj91+BJkX1FAu/oPgu07usytfN9d1USfZaZ5mpGMQe4sQni0cP5paGCDcW7ik9Yeg27qxwJCBLWrHBkO6ttiJBM3YRAjPFjRjESE8WyB15bUHQ3jKJyLnMdUxk+wEIZBkFuAx2gaJaQEeo22QzBbgDpbUtoqgma6/OvIHzHU+nAbHoBWKXHtHlzHUv/a7PbZzdNPkC7SAXnSLZS5YH5JycVbgvK6+oxKRuRUdcBQgDvvcHUp/FM36yYTynKeHDGhRfgqtAHHfvVXuXFC5gzPPzQ3OMJDZaFTgoZGvFBr5ytl9iZ4LiNAlB18iDo4Ii0Aqhh7Sof+kFuBuPXx1wySroOuvjpiAMfuN8FNS/UALPTVVMG59Prq/fyv3uP3qhun4cYVL5vTyqcjFsBoggC/+/0IJQGWx3I+D3+ESpfU7dI1FfzkVkIsJaqh2kLId5EORAeYoFVRISxAHqaG8WjpM8h/yUQsE8MYvL1YQwA//6ZEaNS3zwtrlkFFiHsq9sJ9eJ6J5VCx03xeYTtL5a8I7xBjCYaU1RGcs8Z9smbI3H0kKhYnUwYW3JjOpHjK8xXNUSTEeTLAu0nFFg+to6AlDhLQAjUgN5eQJMWh5mgFpwFyczMv0LqmQ0goLAricqzDsJz4qcLf4tScv2N7Xl7ljpQcusqRXTd3u0VornXUllwufOGkBd+Fahj9GnaNlgnMphq0CxL6NDwl9n9cd1j8X9RCictyOBsxB7qUpWtWXd5j0OCGfmZ/whyRffLmXDgF60K25guoP+V8rcl77gKs6PIo/gNInlL8dmmkurOImK2RyFbwQcdyevJf8hpjrPb5hZ7aIzAWg9GEuOzTTMFfftoiF/+4gtSu0+I7rO5DJpEI3vECuN+7zX4Bx4/BqAH/O9tC4UwE5bgFS8Kih3LkfuvcTyxx2ZsBC7G4ZPq36HrBwlgkQHwUAcB87OQyvoPMZVO6ibaV4RYMLyHqsUOSBE3glJZY56OIopycNWd3mvrtiAzo4KnCwSqKqklIiDB9duGlNdqZwQsHjJIBnLFUHiRHB5cnz4bum7oSuT7RBhbvSumh+ffIsYnSJePEbaDxXFrABvI2WSz0wg3fNcqkLJVX99e2rup++fQQvuT2usvszULu9goNWgPi2AZJBAeKgMhivZkOvZKeISt0HcAKiEAhFLhtVX1Wp9gAALt65KcrX4bHk4GtSsUPfifz8DsSm5787uF2yHJNyPnP+u7N8PaKvbCEJ2xZs1/ZMFJHAE/qAxnd7hutOuT2Hb6Wsz+OHAIWzWGWOjkV/+QR67/PFz1hT5K3CcexGMkYfs5EVlmk4Na5/eTsEQSp131yxHJKSXAq5NS5ysztBp3n++/xnuZ1bQfTmqg2nFLJqeiw+6cKUVbdbkl+WSfqDDAy6KBXLHLBS10dITxkVON5mIvjaVSxzuptkyWZAtFLhX2D1hBsreEwBq2hOk8XQJnD33n/3MIDAa9PZfrwz8ZbPUd2UOU0PgUID/YxQeakt2voTsVGkh3WxGSiuIhXP7nWOkqrIT4qynS3R1i4UuuBl047Y2V00cUiF3njV3iNaQPd5g0NIyqUunJrc3LQHX4FZ198drD6LJc5Bt8JxiQulueULP1dUgOymO9HGNv2C/XlEhG+MjX+MzWvzN6HYbsl9lpSd6iK/ruVLXO1zEMZxiYuys6YxdCUNlW/s0L7lGRP7UREWRiVNpyEF4oYhNrHb7uUd8BwsSOBJ6DwkngWOaUQe/Vew3yZuXgjbnG+OWQSS6wx1ogLGrYZy6fdl8nj8iCQyjAqcLi2PyNq5LconKQbhuMhD9G1p9j01bWOl3t3RcDmSdIiQ2w3GGUN0zZrPTWpd0jgggDnl4z5PWphU2JgVK21K+hi0ezoR6zYOwup3I2eJaZp1JzYvn8vl8v3DAWeui8ttEdhsNrn+lGboI8pvpZfmfIEjvjNU4kJywxGKHO+2WG0xvAlf4GSOi5zIJp7mFOv13mmOa5xk4AIXy57xOmcTQAo+FrdhS5xD5LG6tbWnWdhck+D5Qy7elNUJfiHvbsXeZeakmKMxKIveEsSkMIaJjMHslXOX1kkQx+OiOU8Txzk1VAj9GT5uDQsFyzU/eTajHCNNfUT3KJOcernvTtb4sgZ9tsYl9hhpRj4Q4ajAYeNeweleVj7pXuLeDpCRSCmEh48uR5obVJaolHCNCjZpaSe8dSsen/tv9lg+1PUKCjHBf3dyWwQetK2/bo1IYkHS+bgiEQK28+h847brcUy0t3Ftyu+z5NJNLe1Y2YGi5eDaNVXurGSvVjp5H8bxY1we7G5CsN2X+idlsVRxt1jmwpkqnOMSp7UdJddUhAxx1TlKgHu8xPFmrTMzHD61IbWkJ1pisRfu4UWyEj0H8YwlxhBzNPDY16PxOfCp605kk4BTAnilAohlKIMMd2qz3aY8x9I7tGgydJlUPwK9xjhMPh5j2urTcE34Yf6AcLmUEZx9chEwRX78uKKMKodYFsochL8Uftc19K6roUKzfa++5MdlKQr+UYHDrJFN7LyRxTH/3eFEljCv07KW8I1L3DAe5wsQX//dsX8NyzQM95Arc+yjPCPcZ5ct+ANeLMQMyeuvTl6Ct5TVz1CZAhftQqE7XtCaIhU62BuKh2+olFct/31rJP1BmsVIyj6g8bIDK+tOI+DbtkUc66+umOQNg//ufsY+LzJlJPq+zGUhni4y6aaw/bY1bPi1jMKGAxoPNtTU/Wux4UXWCDFN2y8bcexTJGbQJ2TYVJA9lKMSp5Hcj0VsPkH3jCi2nbP/Az21+S9HmNZfnTBJSFzqA6EjncNGupqsNsTHl3doib4lJaZKfRgTj1B5cLCh/jTsyxoVTkntpzkPkX8hhuscs4PsDPSnj4EBrretlgXpDtPx7vKiysADPP/dARt1IpTvaLnPDreXcmrxD855xYvb4gynNJkBcH3PF23y2cOHepkdFgtpf+S/u3gz5TVh7D6YxWdUPxTlD9G5CYZxcnYnC+6JrZY+Vab8bA+GcW7l+DG9I/odYkkK9I2pQLdGtHWdCvUv78fm845DWXVbhZwu/6tf3lc5TqRHkEi62D8WpFDKZDQqcj3vnxTlMqlrLCaSkEvnew+lXKDNdYarO3H34D5vUrBu0xNY5agLmhLkOF+QGRXmRSjaGhHG2KT7FEFJ68B8dTVl9W0/ykY20uyGZn+YpD9Oc9Kv9Ec8Fy0FUg+OssY0DXNFi0sRMY5CZF/7XXOT+JTkzU3CzgnlJVqSfSjUjgdh9GBVOzTbqtJ9wzRXlfjma/11DrVhUyeA7k1/HG4SsfkcCIwo9ly0dVzUyfI4TCQg83L5NGDYs9DWsdA5olO16KYu9AUyj8vr/bEewTT8M2hEbxSa0hsvbG8V2Jxyce8cPx2VRVVdoCyLwlEiNp+NzYji+XLVpralNnpA+7wocFPiUflsSfr6E00936gq6a4SyO3VnzKti1S4NezxjwY1aMESlh3UdZLehce+AFF6sIslnmnYhmtcRCQUuRkTkltEzdAyi0iFLiwuakuumtIJlv3Z+m8O+6OUxMY1fU249vgJL5HsQr3+6oAJLXDSzYpIG7FsG5dztEUctnTn2+JxUWIxTuf6q9tzM/mRmSsGmZXXX10eqYlP09xqy73ovzlczaFV9iR2ZPjojEfu0qjAwXJ5JNgrj1xqH6aCuZN9mP+xH/UnEDrCvmzyUu2CrOlLFnFN8BoZPrvhArrGfXa4oGG6QArmSRfLnHq4oLbbJMuepE5yJVsj5Pmhhkl5HpOHmNdXn+i6T06665xutzuuy5JoVODmGSU7RjntXkUpPpthXxwuwitU5tKA1l9d1MmqkiPvrL+6vkS7qMT5Wn92Gt87dJM0WU2k2oJwI06yShosBLI16/YoWa4SfBt4t9pj8bpXUFXdVmPwc95ldzQkcvfeLtLV6xiZz62rCcO2sjbRN9v9s33rLjIAULyLyybeQ6NplL+p/PD8lUoVxrUN7A1krgGKfXC/1eN+G4L7Zz3un9W4NyTqPqOH6iOqa1TGc42CcXoIPltEE8k/sHU5opUObl6l3y2sx4yH7d3wE/yEkqopURvdPnTT51B5bfna+tu64U8R1vScXhZIfvPYyS63c9fa3fy/w6TJKlggi9j8GVKDYs+Tz5wnT5eroqRZl25w6IvcESoPbjTU31ZWPCmyBRTQlP/udqMJRTnnv7u+DIDwjUtcTWMRgjT9wMLT+/aLwyVR8kO8ImJfXN/gfMnFkw7/3R4bkTgnGGUL+pdwHhOK3LnhqMhv8G1TAvfvChCHGX2sy0S+Auc+uzwZpQj6hzTCS9FRkcv1W9Vk9Wl+I93krb878F0b9o30gQZ+k9RXqXRrBPXFU57GeSq1RuTzUkpXe6Kbk2gPpS6KpkyRFK+D+7ypR1fsHfVjLaMbFbiO9ENS3UFDbb+7vtw5lTJbrD+74rqoS8Wz1L7EFeNhUWQQvva7i2aZp+A5eVSwNWLh+JEqTe/QKisipH8SsfncrRpRTCMlOr0ReKl242z6C1cKY6lJcTe/9axAKqFcutu3uKERTWh2g8syyaslZtFQIZqpYMJaMbfhqkS2h2LZ4VIsc7qqaU840mVN/9n1igS+T/K/TGI1wRulccmmr3Iob+N79EmKSTEqcFqLkutC/23L9q0odocRKu8da293YOQlYqAmQluWEqNCV0Od3Eu/xwrkz3tMRqd8DwGUu9z5dqKwYwXh6lco3IDNJPjsqpl9MrGSRs59dpojKlclQwX/3X3GW/OGbKaAyjelXn25uamQsNX03xxv9oH7fCf/h6RO7y7wnwIPc58dZoAsJxbKbkz34eumt8+jYrliuRJ0WoQSyPUG9Z94dVCmd9KNrFzqgDlDSS5GIh0+zr9lH1RVkWJmSJSf5aGyU0PbkMZXfPhoQk/NKzxDTenVnQDPgwP6wkJmW01zV60hBNAWrPZ6CTnEspTMQ6+CO3yZlLcIOpBbdZjH5djZ316D/GDPMhd3mP3ZvlK8IrK1LjHNBn9EF3vrl68xV1rUloyUozotoBSlDpgCc1uBfCM0EIFrLLocyDkt9eZmmp5hrzhZrQtiKEPL0QpbmD77pJm2MtbA6R8QRph4oHNhE93h2dhEH+KMKq9DclCL2RarqKbcZa7HOANpKiCLOe0C6jDGdO4YUbcXmM7ni9Pqc5Nlv7+8SbJKVDdNow9mnne4Yhrj1cFqlWHq8dqFIDJsKvp6Ihv10H14Iwt20jUQOFcD6gjcpO1m4L7REWtueSIPiY9v5sgVQlUVY/BgXswxamer+WPc0zAW4XFtnk3O+lgzThyyrqVijjM4ZIwVtXvsW80SQyfDuKFDMzcj9M12/18mt/pTLASuiGG2hrE5r8qIQw+pXgS17lyU2SaYNqZwWlsuVDVU6qajpQJGv41HjedikThHD0m5OCtwXlcfMOkHUVO+VmjxHdd3nYOuLkCdsbIckk6qYsEXxoYCZ2CMKwKfmDu8jacUExniCZzeVuNyxJXqxDjjCkgD+UjEFlPiiLi3kYHM4zezUG+Sp+5+Cc5RKYIMNv/uy/B31X/owjpRb4GsWtej14LLhBGkWiUps+gt0Akuq5py2nVSoRbk5Yv+Lq2/ne0MsH9kR1mbxLwHIIo7vkFVfVn8QPnvL9/+9ObtyxcsDzF9JprdvHzxuMzy6teUTWOS50XNhv77y7u6Xv36+nXFWqxeLXFaFlVxU79Ki+XrZFG8Jrh+fv3mzWu0WL4Wq3dorbD89O89lqpajII5cb4FvSd5scLpmKd++w8kMUPPJOfo5oWKn357LVb8DeBJ2vbvLzElKVvOLJseC1raXrBTKMR6+fIFZTt6Uzyw3mstev7mu20mv0/K9C4p/9syefzvPL66lFN+Sb3N06xZoNP8AhO0yapHek3vIx27dlr1XlykoEYpS2/uj27tEhZhnJe4zuJQrH1oHgHRJ1Qn3SumKhrCUTDHSDjj0U56t+7PHecoJ4LqoPqOF3RrC8DUYvhnkccZY4vue5msutxZir7Z47q4Kx5Gc+A/ysOCKkGB63IIXceJOUccbDj03OxOcf7yWy/1k0fYoLuzsh+S+i9ffEoeP6L8tr77/eXffvrJGen43aLtlFrPAtGGauktyfOdgbceM9Dl5jSsBDvtoH+eH30eWY4y/CcaVOlnMqPrZ0ZcI6Ji+usp2Wwef3/5f7FKv744/c8riR5X1AWpaPLFv71ga+nXF29e/N/O3eGzU0fv0N98OsRy063TJou8H6dnP9OeBQqyoadTdfJttE56r3j75drx0TNZpUa5+8ZnLjoaHTUZvaIyyHVn9F9z/EeDLlDRppzUIXfVC0+y5PZ0SbreO25r0f/ykyv+8zoL0RAjqvlcfkh/JBOrOO2abt81nqMKsGDt7LoL2I6GmoP8/Mlj++npOYni1SOPqICdVjSa9lnW3OI84OB3Wl0WTapm+4BjkeiG+kw41coiFmhhGzPcL784ox7Oo2GIredafVH5rOY5eF5OSoR6K3/IRnOZPB4/ouUqyB5FkHQbVvsQFNyvbIRIH0QuxKLbLgbGPwF4/JZUiJArsuyZMPzGt+DYknWI3BXB2BpFPaT21S/5h4K+f7wN4vODLCse3jeoqsn+/a2og5D5aa2QTSgp6VUgYoEtWjw0/lGN6by60fs4X0TC5H1GcJIBB3n1IF6d76wkoANylwJtrS2RAJ+b5TUqv9zQtVGFMPXER7rB06u7wnkeDMQn4nZjonXNIEY6XY1cY+yOcnbnpYPVqizuwzaCcaRctXyzM/+wIHNeyJzZ9JnxZxuFuW2oYVY0zFDeYLrYXedhiFGjNZE5s1wX5zkuUpXPQyy8J0W5TGqXCyI1roskq2P382CxxPlRsVxy9+Zh/i1VlDPZwc0NzjDh8jDShZ/I3iH2yn2EQi2/PSR0d+LrkydNdOjTdjmMhT4mVW23z7xxRx51h6A9/Vjc4jyWSk3wMQ4jstcCpVUfgSd+bpqDhEClQNiMEPLEdOuOjMG+P9bbMvgCeWd3ZSLnm94/c6s88ZiPq9XZxHb1REMGKgdemOgtdJLH8Tjsnvp7qP99xaDFS9CjM+phnKce1iyhekhPjjjvtSCCdgv9TUxkb6Mg+ydenRVVnWTQ5bOfqe2uyFF7fo+zepPHiNgCNmb70xYUZ2RnxfokLhXMAFp1e36wJbWKcifzULR5fk6rCTwxLu9KhOzx/+yKnywRVOJUwO1lBm7DYV4W35IgtX6DHhmbsCKP4+/8Vda+lyNx5KcHsXjk9DYn1Du6o2/gJuEQXjF5Jgwynaa4wb3p+rpE9xg8yYTaBXbBTe0wK26pTvhMWHTj9+J2B1k7K4vNmzJ7vakzoAY5Xvb3KR2uI94a6COFPxd1bJTrUB6BO8123pDr3gfqNuuteyoY2tmI7sRzHBPPoHhAOytmu9HQFkKvjUrSBfaKhD2yoFjDMH7DFSbQRMDje7zo8hb788YkSvHFHUuWEnelnRDo2Dij37z1jNPlPw+b6nguWj2OWKeLvZQeaR69wzFRtNFDFAWEpujMby+aZSTtIwq+Htkl0TIzYbCB/YuFcrD3HKTb8pjn4kcTfYkk6xCDZIepYZOy/YWzRxfe1xg4T07Y4Gn1Ht/UR0kZdNrscYTv7OfojwaX6Et9h8qzUZBF39AODN9aSTBe47sLq4ZOTo1TqjQcLBZCk0HdP63eFQ95ViRhxoAOR9jUfM2zdvn26IJG9qm/svlyI+HzcuLskBw/rnCb1/Rd8qTCaGUy7BAyLwiGMJy7PyTVRUKTS8SY1TEmj0tQoX7ILSgZGPWNO7gtEeK1Pp9xjRBdosdYzlvnKG3KMvAGaEBy9JRmqBUbYfKOx3eGSlwELtMBI9v8GdqghXXKbs6GDEQhsizW0ygiZFmgtSTrsbUW+eEUjlK8TDIag4z8qlgwsTd/J+dZ+raWbJMeXY/i+XdaHVdBJOTC0IQxCVF1qNEyvydLjCBrL6QCj1V1kf74R5N0poEASd6ephi+g/sEk7o443AGmMPBPnrtXjiPNt6PxUM71s7dLmwaiPaPb57YCfykKPv+HSJyoApBe5ikP1i8JxoyMNBxlR7uKL7TlobkcDEcS4NUCrZ9kZnBy2YZY2JafMljLHxs4GjRocIovipK2JwCHzZPh01dF6pX47ZLhkJ/x9Vdhqs6HGG3ljNE+JII5pG1xMv+SvRthgqnQVacEYLoW8eXbDFtA90x44jexk3VxsWK4Eky74FYXZxwbQyXKJd4GeP6g8fdXapEwtwbqY7zGpVVMC920muEFU3MQJ2Em7VNcl64xKhdu0Eyn2yeqKoP6rrE102NjorlNc7ZiWdSZiX973MbVF1ug5BRfEf49m665Ts+pkRH/x0vJsT+YVraDLtSbJkzII4rcGLdNcTxMpn4BdG2PKIER47vUflEZ9Xd9DKuHWJ46fXSrzkWb0At+jGuHdKPw6RC3fYabCgYcH1CSdWUiPZOqyq7ByUbmjhY8p4jsbeJoRn6Y9yUh0XysMkXGWrD3QJmsdCrgBb9GSpPa7SMYaYZIaRkiInv4q5oLT9Eak/giNnv7s/E88Loj+AT8qynUbhd/GtFpzUlgwu8kR/yaEnYoisOfVP+EmSHI650d1zVUdGsipx/TuB1kpewxPKl7SeJXTyzXTCMUdd46NbtYFe3fxi0DzNuREp9llwRW3FLZP+Yw6ImGlh07yqaOyX2QunZLo6L1d6RaeSoGOfO5gwz1xSPqE19xRAFn/q5XeA/UcgIRm5d1WVxQY6PaS1i9svv0uL4Mr5FiBUSiOnw50l+a7DZe3BIRG/EuDapLfT52gmbRSwDzTbbPm6SJqu/YfTwye9FuL3/eyu6noky1I3mEOfJOm4vodo1++C3c5EJpHgMYsl99aOCxoPPTVrWzz4uh5/RQ8gKPq0uyySvsOhnZLEVDuvgikMSlGRDv7KCu+TzmOwTWuCky/Dnri2Ma08Q0YZv4JksbJr70LyeIzwEembyEHy5Y6PR9hWD1sl0CvU2PNHlvdmfCb9MYkyIfpbk6L4/Tcc9Te9PwMbe7d4JeH9AfG4HxCBLhPMVXXcXsXw+sbX7kXk4Fgw1Q1UTkbTnTYYi54u+WKHJr8+68GFwDBrXM3H/2CAGsnNU1USmMsHHp9ZRYrW7luuRninfu3lNFYc4eaLs0L4YiI28p7BiEwvE3u0wQRQ+fqzLhD/uTWH14t2DnolIM6ruv7hr7kdFVpQf0COYci0UebdLt/n4InsfTXzy69Z+6y5DfWWeCQ9t2FzQuR/5dkKoHtSVSM+s0il939SuOHMEVLG8epsgngrOCJsPDulBx5kPeIEu75rldc5FgPZB1EW32fzR6pkdffxF8sAgLb88Ewm9Hp+vfGzrBgnHgbSBzxR7NEy9CMN1WhGh2tp6JjkKd+z1zHzHNrzfK7x2bAKXDjXDMlxVJ0TMNetQIvOLXlcOfIaXDhvmQp6iHpe5o9p/NW5kzz5vcMqoM8jzPV/G4EuYtq3hxiMWjRZbmDpAr1xaLSvw/UnrqtffxIe/VJhyBegI+kwWADzEWLy3NWlON8Ioz4RFdmYWOqFymdw+Y8o7umq4Eu9bUuIkB2MBPBOiThDzEg44OU8czUBnkCnjO/hHTLDB7h0wwSo0hXe8hGf7cPqgqvBtTlZg7wY0QeSlfTwA4bQYnp92wyectQ3/P5dx8i9GCxnHzjRfmvrLDUPJBjlFOiKeH57JHjrFRbTmDinOTfEW3UJDS717EDSdYVtU7Z4JL4YKuP7/gS5XnxLmP7OWgFf8LAc9dBHb8u51rCM2jYFCPnExWYMDBLeBu0P2LO70kddlkVFMYXdHymU6A5+8NU6L60p+bkt4ksf4Ec0WQd4NoWK6dcx6HhOtGKK3FARppSqMKcc3x7F2acWzorz4o0lKVJ3fXsfuZ3t0XfyLTBMfpj5+SER2CJ+hIU/Pgkm2mSgsbd5y5L6zhsJ32o84/xEpnZj7Ic/eObhPHfE85Go/nPcNHhpqcvxHgzBDeYORKQOOq79XNSS9+FoGudUBaMKyFHf4YgYIoHooamO0xfJvBKMCeCE7fiR9q2K5Xu4DC/y1AwsMEaWfiWjctIeH9rGMnQud+mGTjRvdqHYMq3jIWGIGRLdmaSLQqmf0guM55dlmrwfioKLxDONgiuKlv50hR/9K2ci3I0s1pAD8JSNRWItrqs+WeZIdNPUdFZGtV9U5SgnBnokI7zdl/+08UIQfL7nHUMGnDzpbp9xBMyLazrwRGfsXyluXxQ8UZ4UwdAdpiqoqHlLy5z05vZdB8QKsF91JUTbLM5r+6HmssMtihVP35dVVC3vysunFbZXu0+4sf3awWJC9M36k5F17e8jWB+OOZ7JA2IDcWbSrtuMLhM5juKm7C+6rV4s8PGE+t5I4KOHst9Bs9R+Tqqa9CLwN6LAoptwTGwvpEXgS2k0J9JyEz/uyaFaeEqirG/11SXg6l8iHr8/drhO0lmMIFLr2IKVqL1Ym8QfcTfHEluUzkVE7IR6eG7tFtOJZc25LvOfBtGws8FMhP8sGxaf3KtjchdjGzg9dPMTubOwRpUWoH3bD2IX78+6NhCDkkpDVvCBtNWJPPIcVA1cXbjEiqjZyY+RQnj13HjVlifL0Ccrl5om4RXhOhIzl5evfvVflZfLY7UrhJ+xvieLdo78wI4f2mqyF7DRPM9LVyXwKR40dP87T2CVtbAhfO9MIR43OM9JONswzwq6xWUdGGnJYrO6NjcQYEfyY7g1JdoLQ1DRVtzw1gdUtT03tDn+cULyMTyZnxJDAlvatMNExWRNEU2+ItrOYODr2OXpIysVZQTa46jsqEWGtMNeRozuU/iiatZt37FOj1EC0x7m9EqDwUXJ1Mrm5wRkOTv0xaP2rKGNkPjT0GEOjmJeIyJEjMv9jPcVr2gkWWjvORNAuRVNMpfEFJnepfqCFinTBPT26v38bDdnx4wqX7OT0qcjX8SYi4v0vlMQZO8+X7zARbvU7xPjQnyU5NAcp2w8+FNki0lzJyCMyAof8MMl/RDtKCXijLTEe7+lRbJRdsoXYaE+vk0gbUiehmVLQ+f/FWRMNUfZK/GebbZz67yfpOLjeJOijsZuqgXNUcbECAqXRisZziU8cGXHEXpNz56ATxe/6WUPrVii2OfUswbE8efuTYnuciUPTDiU9q5BFuGpak+Ektq6p80Nt74UGf1w4R8sE51xETY+4TB8S+mKrO9x+LuohDmGQe3eaolV9eYdJTxPymfmLfkjyxZd7FyXXOQ3V14ocGv7/9q6tN25cSf+VwTwuFpPNLA6wWOQsYDv2xEAuHruTwe5LQ+mm20LUUq+kTuL59YeibrwUryJ1S15m4maxWFX8xGux6k2McbCaANyT56EiVe1bb6oN8/Ry/wqNofNH/Eh2ESuETquafef1NQf138cC7f+KyydHCHHVB4vC5Cjyfo4zJlhXAtB2nUT1slPaChmfITeRramH30IFPj+8LVpRSbS/aGAMipYZ3vOdPO5E7rGyp+rxs7clYMfR32OUB5RWC3ZfEtbs/In3DhUFFfF8cKSttkfIom/gmfMIg1/3Ya9k9Ov08eogM+Fy7W7at/I+HphXQTbJI/PQ95hdQ8GvLcfQZhRNQl+ztvuler4ObTK2tdC2C3YX6P8OsA3AM/gB923RsvKyMHqLIZ72YYsGZbCtRv2/wsYIJ5ncHJPkSof2q+qFpx+M2k37eI2ylogyE07QxIo6OJiOXsRFr0ubPffFI31suq5TnsndmWujDvKKJRwuoyRK+/A+Tsu3Iri7js9NmN9bBvr8YzvwtG0bJDhYdUNE7mbX8eVNOJJv8mj3JU4PHi8XiTNd2EUJuSFEvq4w24QdntiNMQu1H8CKTjE6lZwejtQ1PdxPiefGwU8PLNYe5TlPq8j2aDUBUjy8w/LUcZOvf3ycO92jqMjSmyyvseJnkd4gDpH9r8F+34WpqW+D1QMmLsDcwHw/0eNjtfHxw+5if4xTiYsZH9DMOqwNM1D4eG42FzcWiwkyIz5EV1G+qkly+Gh5F+XNwmHQyVN9LuR2zUrXHXK1Snfy8OvVycf/ia89wr3CwF8nIqnG/QXdDTN7Lm6YE17QrGScg67OLTPOAhvTOeaYIlvZir75iH3wvC020ffr74jS1IUNZnKFu/OQ5c8DHVfGy6NlbanhmS3DRwYRvvI15WISlHOYf0UWYSNyuYw+P7MV+f2YLU5Ndue8eozW+Kav69aG187+6xE5eHfznuU9Hqv1SuBw9bxLEJvw3KkHKjZ3KI8zuX+C2Rqiugwg3AZd7JlmMRht0ofeDdmKkcZlHCWOlz9s7dm/jiAWxz+9zQ4r+dIojYD1rEEPCgyWHczY9LnsHCFZ/boSWNbvI5u8HspF7EuHRax2ZewSjPI6rYgtxlLjLl7PaIM1eYu+osQh2VJ22JKq//7LbfGReIX/9y83VZtOQf6yvJR4rihvLYy4V6mTvL5NOJmkDfjdIW1AwMHW7P32AQF5Khm1/uFybfSI8hzlAVh7PVnFkD6IzirmXwOpDn4OA/f4b8ryBD+E50ZeW/N9LODnRHb5L+yCKNOhDVYyitIqeX0sM/nqC0qjYb1VGxxVfYxl211OXPDbeWIlwBx+r3uTZ0d3GLK1B2Y1cReDrjssGnGgnCgecxoV9ygaeM3TnEJcPtfxeTwx6x50zt0Xv4vLt5JRwDl0tp8I3h7OtGwP1sz9eXZPaH9O0CYqvqykt7XruX84rEAv8Peg3u44Ld6z9Pr7qYIY4P7Hr0Gt2ZPTF5fFPMEHu2r/L0/nDwatv8dTwf053TbVB+WHx9g6fUiv83zYIN6I5JpkXqaRww61ymVDDu1dZekYkH9dp4OlwSz8y/LS0TJnkqTTg6srEYiGjbev8rZ4E+/3aFD4LvznoRo17lC+o9YTDu6GLSeTsycXbe+zb59QTg9vefbta/uLZOivg9NVI4evE+lkVemFawWHbWtqHkE8Mtqtxn2WuDidMLUHLbxu8cotCbFq+pj/BNSIgHpIzgfvTL24qDmEQTePJVb1b7xbmwuqD6xhFepUdL5BgRl758lEtRt2tmF9nGUMts0TOqJPUR5XrFaCNKKTTXcaJey0HOBMeIII4e/SrLmGAwvhvBKQBJmwdBd9TpNgkVhf6ANcKvc87R2nA4bf0AlovW0f3maH7C7eVXHG5+HY/qY8JpfZnpq/hr3kydISA7d9Kf8eld+y/IvvvrnL42OUP5NPp80V5/IaCuIy8GUWYXn9HWuZHhAJTD5UPpiZhZjmzwca7j/KSOhwraNKdcjytnfQDpnssBku3mYVAwujmDu53GT5EXcZFRveE3vnZzPA53n+nMTF0xSvELxefAETtM+I/6+zKsr/dbrHnTl0CrRbg72LyDvdlQxAcz+FCLaevox2X25TzH/3ZY3OQR7e1k/7gntyL6QxLv3fRen5MSJL7XyDjnjMXs2RU5Dh4FNcZWURU8YNZhz42V779PhnH6+3j5th72cXr7eL71FCrFr39Ep6uJuuX3pZLfw+hEvoYTjPiuIBJcnPHvTTg+aDI50nfC1mp3WSp/Lz85RIEaLeMsKacZ/9eUZntCeZWi7KMto9rehpOaWb/QaDqTzMsxurFR1QdeRF96xbKDTNfOpyFXATu3iT1LWG+LjRIfqxLp/jNMqfnQ77tV+kyyPPd3gLCDt6DmWM9nHUgMLe7mztAKGqKeCvZBy4y+MsHxhorXpu4t33uWJqcDlg75+ceRd1k4UQ9B6dkmc7aS3YhpD4SkgxPpTj5W7nm6XJWyKXmaK6n/VzO+vzyuEBDwubPB4YrwUz8RLKt15K7Rwz17K1hy07ULqvjh6jJOk6zaOnJi3pSuYJMF8hO93/w/46tdnu6tcoLG9T1xDv8t5luczv3ewGsMC9YKms4eK3KLQxQlwY194/D0Vi/pmAar9Gj9E5KfHgRhCIawX47q6i4ymKD2u5w4I+C0cXTnjSc2NmNNONf7sePnJn81zlRzvgdtnH4YVbPQnWb1nFMPtDg7bMfhnnw9NfsV4a6+PwtXYS+fRHai+BUxY7Y/e8fvfI6z8teRkPJO/Rt+Itqj7cFXpewMp5DdAiWYwaPYr9ViSCcPXWYtv4jLDvQQeun4YPAn73g4BbjG+z/e7P2fUdiopzjpr8Yyv5PnTzrUsov7CBAu8rXIR2ew1159lg6DX+XNJiRcPsTxiNCqPb4wlv/nGbj/Fq3qoFwdBNluzt49uZscamr1j68Nb1wWf4w6gv8WmICJvoCxpSv/ZI/5AOW8ljFN/EKNlXf/l/jdV2+lWWPsaHcx5BLgBOW7Tr72UeeUuGfpUl52PaOsf74HiPinNS3qaPwiWQW867OuoSlq6Ku2S/COTrh3jd9PCc7n6A9wUm1u4tsb18rrn0FqfjnW5w37vcgWTnfIfAt+fW4tW81OLRw4DDoy7VG4uh5nwpymuqKlB18DRH3md+L8Po+ru7rkDV4StO0sSbqND5wro9eLsdFKeo5vFQ5r5G9JrhZZbJrlOMxnHcR4GfZVx/rxa7r9EpydaUT6VZvTu96HlU+nRNt9z2sAr1vM7oYTN8jW10YeryON3kvtSBr0GMA/sgB2V52uRRWhxjErJxuFUhjoMcwPC3UR8d2D5qMLzqqfeUvhlb3FC59BthbxS62FV0HzdD1ZcQf0XvqLfyjlf8Vk4CllPRz3MXX+cuLq+Kqu+7+lejufcP3PjpgPUwn2dfY2yVkI8Tbotm6GsgOuS+18OJ0nSbfBA3GC++Fs/4z2oo9XX602KjPlfydgzkeTH14fGxQIN8zsjV/xAGl1G5e3qI/x60RLjDHyEJ4DULd4gq1isJj263BHBz5f2/+HSB2Q29Bk5QlPbBBz3OsLhrmnVgHV6UjoXqMuHy/LbiDAzWo9sF6uhnbbBhy42eyMJLEIdOJztxuOpBTlyrlOV4iK5f8V1V35ZbOl2WkXGn1w069TfQouVkxzMYmDOxV8Wym7uaYSKG1SBq06c59G3LwrRXm6acepVpy+1zGdqTtPh2EvQ1Q/Tk67ggk18bTcOlK1sexh9o05ZTXzKN2VmSqjrsq6Tlt4RTXzVkb9IxcMboUbq9pfaqoIOdGFz1kL175x5pwbZjm6aW2qe0+NbH803NED3ZcG/+t4mcbqblfaMix425rYTHNaVUgFZ+JxnqyiFXREN3OrbLosE7nBmsjea/o7nLkuRTVgU5nTRdhoExhn5kWNGLtPjmEiOQrhuiEyqP8TaJ/Frt3+i3iUsxXK+jC1nN0CQtpVHsD9wJtyXiU8kaOvzXNUOA4zLJDj8KOHz1ZWWzu6xweDff1wy4OrpHX2P07Q1KTo/nJHU8ZlhExzIKOy9u2uqDRPkrKhqLhzgWpgVde29OFeTX39xR95O34aZ6q9Hf2zk5mdTI/F9UbLAVEw+s3meWnGRYvyiKbBeTnm0vyUjM/drT7x4VxClx2yba4sB/ne5/qZavfSauVqIHlDz+1v/47pyU8SmJd1iEf/768lf+k/mQ1kl6f7kgt5rVWVWxi/aiObAae6kMgOSsPCABK9u/CU3izxjldaSGqywtyjzC5ha/+Tjdxaco4e3BERoOD5WmHUu+5DU6obRyuFDpbdIunT5NbL9rhusBnT1evaBAZYC1+G8SRpXItiCg0WKLKGNL1wExRqdF4Eu4w6T33MX2AfhUqG7mazO9LBaOAj3lDbVKPpYwCCAFk4wATPMbe0n7Zlf0MwDrJsoPiN8k9sCQAkHV8T8gSK0BMjVANSelY4EzS5JlTM6VpCzIyA+Ln4KJGsuYdbsD1a0otG1PBURJLaMgQftzmDnStBc9oKVRxGgWxOST4YXzPcJbGLyV2ZWoccGK9Ss1lgPTo3zRKOiCPcnkYnUUQTDHmWAE9Bl40kkaNnGdmwkmdQsyoNcl/fxDoNAGBZPBT+6DORbuyqhExB8/3aEt7IM6K4zR8rLYYksWjylGHQssTQal1gHTI4j+47ffXgo913Nq3WppTt1vSwcA6DM8865XgHb4Nzw7MNh/oiNCghFuMmB0rk/dQx/d0rqtAZ4pjTTJ8G77kCiBx5lW4RFQpXykIGlS7X43EbI0C2SLAWK1uLLp4wlgJX+8MjaqLuOkeokLP9xxwpRm+rIa95aLBqO2WONPjwYSFSott7ASM528GqFBUbqy1UxerUY2k9dkuGrdApdxwdFKy8jQ/7j4i45OFZO2pr/siMnbKSbhnhQ+DBHdfWyB1UTVCMBeY7S/BQGDXNUwgGi1MWmKz5o4DSaaZ1INNED53Xow1G0YIzArCFcU5lbMooeHgonVx6TFhnY2cAKfUQqdCfXijwInyELzgVPvZz3NKrp9y+1xXNJtptpX+szit/tx6WMKHIRA1v0TjybdG+WLEzZ5lQipEd/gnr2ty/Rj/+Mog4sQ+QGSJTC2OpVHAJc60oWkTfV7+clhprs6txkuVgwzqy6fAmaKiCgjwYwJXzHehMYEKmE2ckzB0ic2eTwWSXvzm9xoFRY1v0khBhOsZp6zxtwc5zoWdZrpzmkw+QHQZ42EqRCoCSM1GQqbneaihj3o9EIoW81gZ3NSMcdxrkOYZoib/kxqenyNeCrlAq87VViuUdHV/uMe/f85zlEVO0R+4z+nsYsSGBSHKV/NGEZrZTOOTX2evsU9H39F+fOmShck7VuaiOlUpsDuys58NPSFC6mqYbBhNa9Rsk2Nictzuk9QFe7poizz+PO5RHXk/m1fopvkKEqgg+nSMS9jpJqphRSIQ06NMhsHxahcVxMZ+trzgW6D1cXcE7p+MMtbl7nhnOnOGcHMK8C0s+VPlKgbnQk+uisBSWT2ed3ycEJDyFrRHQ+vkUmTLfFscLWYaW1CUI0/WNm5QMxjqGKObRWpB+Z5Ug8oASFthef0kGYmzdIVZoW9xQxpMwDb+EOb/SX4PIa3hxPaxY/xjhR1e9vlgA2WH5JLRrkSAErUWwIUYdE/nKo/tyZ60c/U9XgwA0KoUBgKXQ2kbAnDPI13h9DQuBkqZU3ahxnMdHA11zY0cH7IsXkw2CYfsFUaTI15KhuRPnbuXFYRvczgOxSqdCWrBUolixXChJFFAXCZeZNI+hTozB8BYqYdPiXK4CRu4wLtU5THUVp2Y+tVdvwcp4Rwco8AhWwQtJTka/IjUClqIsWcXAxU+FvM3nz2QB1/3h2K0am36gbw/PMckSQyH9NYjlGGiMYCW7DK4VFuoFlDjxZ7bvhb7phoAsnVjn5LHvJayOmPJAVCoMvHPoacLRpHPXb0AMvZnDXySnyKknMHUrWGYXAxLnKJuiZiNoQhMeyEpzBQrrW1wDPPYGpYT76cHNH/fqKF4XJWf6+zb2mSRfvp4qi1ErBHOt2Pq4ik1qlj0tacQqltH6LjKUGw/K6dOLtRwqp7RhwhWONPhoVNjHKsX5UXQ5pEbWhGPIKJIKubTnpGFurXVaS76/UxaYyWbgawmv9RwzQgGvE4wQ4/Ux8gvEffCvIUZhGBg1tpGRn6HxcfOLhTxaStyQMHd6HtrXISzyqiuW6CHTGd6yhhHiG1bKa56SPnLzen8BxBN9aiajDwqgrTge/6e4nyNEouzuVTxbF2buOSXM96xFNpwMilJlz8CKhUzwaQk2HxJsvPR5LrwTfw5AcJXZsMJ+rXxeOi12U5INhkp3g3NgpIoyIMmp/XgYNameUAYUv++0eenU9SFFAkQuc1P48yEZEGRRECQUdmmIDgMWqol2sOQwggt32PhcTLBIOOeV+OO+IQ8hksPmRiD+m6kBAaffVi2a+jrl+IWJOB6EO+D5IPUbV2IW0yTJpflp4GsVbDpCHW4BP3/iI2zeOCZsxlrjlqJl/htsE2PxbRAb2JsTT58xaOEjrToKq05KA8LMFqwqoyapm0O3lcVRBrwLdiP0b8ABAzH1OmwheRcNqJj9y1zhlRnZSiBAEvWUfDTq/IkgAzf7eOaWAzoluHHXCmduv4I34sr6J8v70757unqED7v+LySaKDezdq/A9bKRhm/Y/hRpKxAjB3uhhhAuyKySHCrHVghVy7NNBYA0kOyjPCoscKAZ7QZr3uaSvOC2sf6U9hpquhOUFttDWSM86YHp122fQ+K9H819mVlKIE9a/LxlCvyPzX2ffoG0b7XYYZFO3gtIjzSUBwRhywfPFnl5BWizjJhHDmdRLULMfnA5fRhiFXrDDdMp3f4cNTfKqyUs16JmuFZAM9dj8uG0CdHvOfxlpRyYkRLLdrrwVGjnDgwBaECbRp07GeQGR8LNFVmPaSthLj5PeOXjNF/bym/5Uz+oSrlfKcp1VqRBTA1zjQepgSmVvaMCUrWAPT+ixi9ds94Bl/VLHC5vLGFivgcdafcm2bEUGuorykcv6FzE+pgQkvEbck4QvXk0dS0M2kzRnkjRQgtIhZag4wG3OuckLX5NOVgK3538HPAVgj3sg74Wrqi/mrJ7T7kp35UHjCz/IRTKBkhjKxdJy3zaBaatFCBrvT2DMMICUaGg13fNUJt327c441PtxFz+To8TaNK76eTiBVp9Nsw9z+jS9c9rGioI9Rm1RPzAYf7UWGWiNf/Rzs4ABUSila2BsSN4D4BaXNhQlfdzJ8VjD4invibXbYUv+uOlJ+0sDRMScOfNkogKRalUkT6txCZbMwuKOVMmmOE3EWUFvEznM6VI2537SF0+RbTf/4CRcDkofOOiCzGKiQZ74P58/FLo/rFKQjh/+g2xafU7Oli4eFqNMiQIL1+xqV6B0qKgfO7U2eHcdDCds4dxrGFi0eH5xCJi3SnTEXgGyyn/CYCTz6rphuVfv4GCf4F7QdKTRD1yDLqP916fezvSpGu5uJ/T4udgkX2LDSQjsyEKKJI2x2onO7m2SU8IWimQLBqdPHZjVS1Z3uMq3M8io4eXyM8ufr77unKD2ge/xFXJ1z3MTuWQGvhoCFVvuj+ShDRGBvxOpfAmEC0isMHmo9TBpSdMA8oEH++ImJCTDBWH4yMFxGuy+3KZZl9yXoJjfI7CMRnhFJSrP4xa5Ms0XsmGW4m7/Px/xAN6IHyBDMTe0I8ucZndH++hjFyUVZRrsnctl+EyuW2vapoYJADpScS2wHUiw+4xSsl9GxTzzh6huG2mRp6uaFn7ET2LljaBbZ7Cjxt7USO3UgPoaISTTAFIwyWVLCy/AWCGVyUwVHmVFztHxzwBY1TklVGdaxP8TkaAuDycY2quZk8Ls9nrK8xLI94sl6+7B7QvtzgjZR8UX+iJYmYs4RmALzEwlGBoYjVxLmTaxU5zB4YXUyabCRME4PlYwT5gOaHiqMDFwKn/VBhdVpcVDBDSVZ7U8N6jC8X4PljWLFBwSiC8Msnqz73gvYKL0MDxQep9zl3WVJ8ikrMd6b67uqo3qmxA2kO0jFv5cVGDfZlq+nPWNt6sKp69oyC48Avn3mME0oNNhqDjorbTUYAWZqy1u1OQW+qh8u0uKb4lieIuF7tf15lFFsEMZ8nYtKzDUjbPUiTpqP+Co7kpnScACjqow9dtFN8ymJu99XNGJJTW3V3MgwgpMqu2aVDpjZ2hJJnsYlq5zTI+OnlW26i8EkO1gOR1SVsYcjumnmApD+fUXDkdTUVs2NDKPq32KiLq4XhWxl/Y/jXCvbI8nTcASbZx74aWWb0HGbXFrfo68x+vYGJafHc5JWUXxM93qS+qPv+WRyAI4UANGKhjCzHrFqe0ocMgU6t5mGStrnkyHKrycMZIwZgompPA9kOQ1rk45l5mBez6i1oKFqAQ59jnBanvOePYbGddm7xnXK5+qjwjVQ3rqqZnt0E+dF+Toqo89RId7jVLUeUNk96CJ5juufqc5sfq8uqY7RP3/df85wX0efk76KMOBwjKPvV1GJDiQEicieLgUboQk0TeF/VQeJQDNdCdREV6hh/zbbRUn8N9q3PQ40BNBATQJkusaj9HAmPmxim10R2FRXaqIeeihzchZbZOd8B7YGkkmVFCg1Utyh/BgXBcZ4e8gtSCCSQK2LVJqW2XdgQqtsMdQiS6HTM0sSSDfyM6gPKTHg2l5YgLzbQlkLbbmhrbpliNRcHYXKYh2RYbOK9tQNaVvonoUKDXQlEP+uUKdA5bcGDoRdCSh+W6gbAJuYue9Q+ZRBnw5PAA6HHI2uzRIPz3gY+4qnU+i74crBFlkSTYP9CZPQVl8ENdOX6r6idiElfkJtCfj9tIUa9n12Z4F/XwQ10JfqYCafcNWzrfFUexfvynMO9XdXApqoLdSwZ722hTbYYqghlsKsvxU6cQSK3t82RNt3EfmK9KpG6fkxInWgcY0tBlVlKAyxV0Vkj3N0hAdvkEqFSIZQJwJK4q8of97ER8jWbDHYKENh1rd0rG1Z99I0ih6myWwb78Jm3sRJCU/S2ipGogm1zCRVDBwCheojaKmMv4KmouZjAKlUctCUtrI8nNAufox3ZMNFxamVSSWjV8kH1zGWFK7+oXk1J07FSnJwZlbWcJLOWC4biUz7dBNBu0O6UNFbpNysnU9RHkdpHyX3Kjt+jtNI0i8mlRRyKetp5P3zHJFfPqYxNBGwxZAMLIWbdcxNopl66//bf0d8RblAZpJY45LXswk5bWqWhtwCI00N3VTdvdkUp+muCJyiu1Ld6VCM8rs8BncOVBl4MtQXaxrpXWSENvoiqImqVMv9+jueYNMouTiXT9X5XT00SU8v1OSQFOoaGulIaDjJdokqg9olxcXWaMtEaGVniHShoiGz80RCLGtEyb+hMOH/R56dT7JGmkJFSw2FpqUm8rjQSPM7xL8pMlzks7mepat8lky1zGcpNVLAGacFKWAySAqY0lAKRcvq1sy6UTK8UGXS7jTaSVD5luFG6jJpI3WxphEwr6rQHEgFNQwSGpxgSTalfZHs1Mpo+8mmbJS2Iu80lkJrUiafG2BMphw2I0OiVY9PnwKoyJPAavJUukMnMaWHePok0oDHUCKZbeOyFZSM0EgMs1WTNAA/0PsSShgHEmJLcQzkMBNAf5hOh+sWD9TpUvBQnSYwb6qOfq9qrqbQNFkTae/MIM2kGploAsQnhpchLI1iNcISajckbHBTYAvCEqhWjhyprg/7SJpi7/VlYL/1xbrxkXnFKo6NTDE4LjIUWlgm0o0AVQZDMTFc4n/M5Y1QZVAjVLFuHYVShDdbqsFdJAHXVQKVbouIeSCyf/0MXsxy5eBWkSXR3mhl4C1A8zt8g5UZ3Gr0of/Eaakrgq8v21IT0btTD1iDrliqiOmxiTRel9CwlBK8p5MRW9ykbNDxlMBDCUymu1npKQ3v3hQSiCSquzjjlttjSnnDAoXqtNO02XtUke3ll6Y8Aby2YGl0Rs6zosDME3mrIgloZIFKZ+R67SO9YefKQQOzJNoTWjAMC3BUC9LBZ7Ygqbkg6ua1jeqP1JiQS+IRGlMMHpkxFNov9niK4gM0bvVF8Bfalmqvs8kKSDUo8RTwpTZHZHDu+RaVJco1w7OMUHYmCtFqTRAVeBz9C8WHJ6hPuXJYfYbErMHXMQZ3Aastkiiapag0LXMRcIRmuXKoTY5EN+s/pzvFpE+XgnM+TaA97OYjaQAH3DwJfKjNUxm1LLcqVy5v09SqvAOhxj9QTW7iobitSM3vfhk3LbljmYRO7/l1jwq8RCce4gaObY22cv85gULlp9cQIX3D7dG0wnNBJFGdcW8vTqckRvtN1tDHFlJo/BdgMjNp6DrmAsnXRwKFmRgNuV6C1mfK4OLd8P5929MZfyWdh6mZc6+5ky+TdML8olkI4SHaRCCR+eqyVAZrge5BLLgA6Epls35HYOCgKW+KKZW5aZo2JX8oJ8MaQKqAHEBtI5FODIO2hQapVyBq3/1t7/lP1VF48fcV+EcrVFo97hkCFkH+xICtqXheUHMxeS7wglXf1DT0Uwu9XWDqYEaBnpT0FlG+ErE3h7DooMexYvvQmF20jFlFuaqyhx1EU91jDSUnaGwHuSofaHg35SbKD6h0MGVTUW4AqcIqBedpQjyNKb9HliDEJ0g/xKlVhh7auKlWv6bZ1hxh5WiSgUIKNdiXQF09yRsfexXZ/cK23TCgZpcRK8cT88pyJeH3LkRR9SsWBRduJwUwk73GCWJA+ShiXlkxkgDKStSbi8not0Lbji9gHpAwgCmg90+1CZTPmuxVbzbFKqV5Ev/qcht8Uk/2+s1dRc50ckVZQu+9NKLq3cayO/tQDJ5yYv2qAVwxKBWXHO4wDIIaQj4Iyom99OMczHAZJ1Vw6I6zwggcaTgTGGFogNJtzJSetVxrgTbkJ8A9eGYYyF4x25uhfamqXBqLRCGWx/xrW1JT+pLWYZncPpdkHmwCa2WITi429ICUiK56GMouoNnXoPXqWfJ81UFt5kXptmMMKA5TehGcrQe+gq2rc0Xe1W/PoPXqw3FfRDUg+Wejfvc8UtHvAo3/HufvTOrRTPYE3F5N1ZWKYnFjUk2uFP+6niglezkP1gTNIhaGNY98yWNSzVOfT2oe5u2u4kMB6fx/LNDVXj2jqN7f+0AFc/Nn9d2ANUN+OlIjwQTBjWXzFYE1PeNhpkZrL3WtwMVXCokraFIXykIaxgZIfCX/a5WJzAJE+wD3o+oKIXEChDVhmKjilDivYrdsQBLpGpalUygDBEiptVAEPnHFlbva0mAjWzpCitQaRtW1KopxXWhl5aFa1LwkYVpg1rpQKx4M3JhEv02SVgm2X3LtAC9msTLICk3R7RH7ZbfUEiJtiE2CJI4QbQiPWwReNT0eBNJgaBjXBFygI3q9KLWFvE7IVa8i0hNtIs9rXkhlPVpA8mCImcYkkhBUeuNoKgYzkzqsFM3NMFaUwyW0IiyWJKYXeEXtwEZxxam3i5lBDLiyIcYUvCWhwnyjVd24LYSV3EY21Uy/ANElW+k7KKcOd5AvOJ4zZ/lUaRBjKLz/5NRapSTaAGrMwySK0HSmu2ZjFiNsqA3C+tEWtQnMF8TE+kncpHawT3Rp5mRjHTrZlGEhNxAUlpGYQhVucakmdoDp+Ng0sVZ4EymXk8Z1tcortTZbN87SjCR+01barLFJ1XxGMYoJXyYGloo5HNfKfSFkOFCOPhoGOhBvo51qnZhgwpBeTHwE13rZI4vO6t7jD9HxlKCesbzPOUpPoo/Y213c2S37GEZUWUKpOHcd/LBHiKdLqstD5Q5RXzFti0T+J+rwqrbBgZVumCKRXGR3N0w+hDGp2f/oz9vY9DmguoIe4gNccHUfiec3ahJNDXzSR3/WN5WJVBGqlT7cZhVDoMkkajfhZhV92950XQxupZ0AqhBGEYKFk6rUr37UJYG+9fpyZMEUZsKW9xrD4cgdVd7SkcMl+tI0GpGZ+OS9yGDccbG2WNFvzzYsld1a0wwWdJreJF8izVb1xVJ0HkUf+bslQcQNniKBdHLBXd8hMaHzSS04Mr6roqqxiaMIMSyFUw+K5L/tXezkvnxwBcXubbAvH5Q3gOGizAPgyTSN3Q3tUlMP7tdZmKPLTSC3AU/iV3Ehr0Jfz9Omp5dfsZUVifxvZcOr2maz2N7h/dlTVKD9X3H5RLUgKq6r4k0dpi6fr4NUlebicDcE8932/OVmgCt4UgSsKf3ijRKheDLNR7rfje3D1vI7Lkxlni6PimZApEgCDIh0Dpi+HpjexV5FIGOLcimkpA+xMFJkqCFMTFLO+DGL9rvQV/KLjmlM0ybDkX8THIVfpfl0QaSaNBWQu3pk/u/5yrVkCT0JDtYU5ldlXiLXjVAbxke75eMJl7TnYzIsaQY8kDLMUAdkjmq+ZFVCqAEH3AadLaX139+hY43wCa40z5ZU5NodgZdnSrLUXs3Xr0nX5cFAqi9DThzi45jcFIoNo5TW/75xbDPACdG2QLo2YOQwravAiyzDXA0cXdI4NS/RWcYw/ZvLfMMmUtvepnEZR4liLamq4HsdCaeLa6YeTQq44cZo18piU3q7SOsGUlfJCV6Em+Xwc4ic12eu2wpZ7ETLqcgVUzmcZa+e0tWp82R8ZDw8m0Q1dYF0IWatcdTWqRtcTV49P2oJOQ/1F8gwdbB7ZCjJTn8Rp8yW4+J4R+dW3N7k2VFlDxV5CIPAaSSbhY0yK+RgU2wyC0NQxEs3Q5cDc6vYv4lE/jduQh7PuqY0RafLyJ4AkdPhsR2k1Pe1uxOWkPOzGeNlmTZdYiBnJBRefIzy5+vvu6coPaB7bNo+TySwLdFWUhmFTVzZGAROSsnuUOhcmvW2BEyVOdAI5A9j7VnqZaktSY6pnAy1dUIMfZrkoISRaaZPf2ZS7Nt1Vfxv36c1EZgFcnsTwwOpglrhu+Dswq/MevlQv/0wSWLpyyy6lx0GteTKDn/mMQtzbdmsm0ojsbRyxaBMoEQfVYZPmWlkBgmGGqYNU9DQlbyqNRPEMLktt2w6ddFGCmrF9Askea9nYUXydoYDmMOTsFCn5nTxnbcxh4I6pDnA5Jv1d6jMqelqjj4v6LZjLTMGQOtdEYCHmAOVYqPIa+qWSYjO9betzteusrQo8yiuJmG8me/Wcm00/E22FXMEAtthX7z1S0iH0PnsKkuSO7FebunyIXowO507ycCSFLlaKas0TZOahMoCaQoUJnGkaLWBHMOjDsib+dC+vZNlqRxm2P5Vn9pcHZ1aeONXgxOoTaX6NO19JjsosIkbxjE8noDkqPU+T5H1dJhh+2QdanN1dGrhjZOBTKB2s+kWsrYaz2qS+or37P7bGmEmlaopHmDoM+B67CamwMrobE1D1eX6Lsd8TnDTmtaN68jINe++wUbWRyjiCP0fFvpX/dWLmklleNzLKO/KXr2o81I3P+A/6zOod9keJQX59dWL+zOufUT1X69RER96Fq8wzxTtqjZ7pi3NbfpYxcgnGY05iVqStrhNBYLKaB+V0UVexlUEVFy8w98SybhOnHKq86DPaH+bfjiXp3OJVUbHzwlzRv/qhbr9Vy8EmV/VIRkLHypgMWOsAvqQXp7jZN/JfRMlBbfdlLG4wtb/A+Hf677En2aJDs8dp/dZasioMR/epKJ0jz+5DTqeEsys+JA+RF+Ri2wfC/QWHaLd812VSZH4GMmY6DuCNfur13F0yKNj0fDo6+M/MYb3x+//8y8/AsfvjekIAA== + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.Designer.cs new file mode 100644 index 0000000000..8f80e193a8 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.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.1.3-40302")] + public sealed partial class CategoryBadges : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CategoryBadges)); + + string IMigrationMetadata.Id + { + get { return "201609281549565_CategoryBadges"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.cs b/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.cs new file mode 100644 index 0000000000..2e9ff0fd5b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.cs @@ -0,0 +1,43 @@ +namespace SmartStore.Data.Migrations +{ + using Setup; + using System; + using System.Data.Entity.Migrations; + using Core.Domain.Tasks; + + public partial class CategoryBadges : DbMigration, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Category", "BadgeText", c => c.String(maxLength: 400)); + AddColumn("dbo.Category", "BadgeStyle", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Category", "BadgeStyle"); + DropColumn("dbo.Category", "BadgeText"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.Set().AddOrUpdate(x => x.Type, + new ScheduleTask + { + Name = "Rebuild XML Sitemap", + CronExpression = "45 3 * * *", + Type = "SmartStore.Services.Seo.RebuildXmlSitemapTask, SmartStore.Services", + Enabled = true, + StopOnError = false + } + ); + + context.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.resx b/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.resx new file mode 100644 index 0000000000..4c5c3ff40d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201609281549565_CategoryBadges.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/201611261017137_Search.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.Designer.cs new file mode 100644 index 0000000000..69ebef8bbd --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.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.1.3-40302")] + public sealed partial class Search : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Search)); + + string IMigrationMetadata.Id + { + get { return "201611261017137_Search"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.cs b/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.cs new file mode 100644 index 0000000000..4b58e2bf10 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.cs @@ -0,0 +1,107 @@ +namespace SmartStore.Data.Migrations +{ + using System.Collections.Generic; + using System.Data.Entity.Migrations; + using System.Linq; + using Core.Domain.Configuration; + using Setup; + + public partial class Search : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + var settings = context.Set(); + var catalogSettings = settings.Where(x => x.Name.StartsWith("CatalogSettings.")).ToList(); + var searchFields = new List(); + var searchSku = false; + var searchDescription = false; + + var searchSettingNames = new Dictionary + { + { "ProductSearchAutoCompleteEnabled", "InstantSearchEnabled" }, + { "ProductSearchAutoCompleteNumberOfProducts", "InstantSearchNumberOfProducts" }, + { "ProductSearchTermMinimumLength", "InstantSearchTermMinLength" }, + { "SearchPageProductsPerPage", "" }, + { "ProductSearchPageSizeOptions", "" }, + { "ProductSearchPageSize", "" }, + { "ProductSearchAllowCustomersToSelectPageSize", "" }, + { "SuppressSkuSearch", "" }, + { "SearchDescriptions", "" }, + { "ShowProductImagesInSearchAutoComplete", "ShowProductImagesInInstantSearch" } + }; + + // migrate CatalogSettings.DefaultSortOrder 'Position' + var sortOrderSettings = settings.Where(x => x.Name == "CatalogSettings.DefaultSortOrder" && x.Value == "Position").ToList(); + foreach (var setting in sortOrderSettings) + { + setting.Value = "Relevance"; + settings.AddOrUpdate(setting); + } + + // rename CatalogSettings.ProductsByTagPageSize + var productsByTagPageSizeSettings = settings.Where(x => x.Name == "CatalogSettings.ProductsByTagPageSize").ToList(); + foreach (var setting in productsByTagPageSizeSettings) + { + setting.Name = "CatalogSettings.DefaultProductListPageSize"; + settings.AddOrUpdate(setting); + } + + context.SaveChanges(); + + // delete obsolete catalog settings + context.MigrateSettings(builder => + { + // migrate catalog settings to search settings + foreach (var kvp in searchSettingNames.Where(x => x.Value.HasValue())) + { + var setting = catalogSettings.FirstOrDefault(x => x.Name.IsCaseInsensitiveEqual("CatalogSettings." + kvp.Key) && x.StoreId == 0); + if (setting != null && setting.Value.HasValue()) + { + if (kvp.Key == "SuppressSkuSearch") + { + if (!setting.Value.ToBool()) + searchSku = true; + } + else if (kvp.Key == "SearchDescriptions") + { + if (setting.Value.ToBool()) + searchDescription = true; + } + else + { + builder.Add("SearchSettings." + kvp.Value, setting.Value); + } + } + } + + // add searchable fields + if (searchSku) + { + searchFields.Add("sku"); + } + if (searchDescription) + { + searchFields.Add("shortdescription"); + searchFields.Add("fulldescription"); + } + + builder.Add("SearchSettings.SearchFields", string.Join(",", searchFields)); + + searchSettingNames.Keys.Each(key => builder.Delete("CatalogSettings." + key)); + }); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.resx b/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.resx new file mode 100644 index 0000000000..a6ccd629a9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201611261017137_Search.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/201612061904083_ListPaging.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.Designer.cs new file mode 100644 index 0000000000..12528770f1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.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.1.3-40302")] + public sealed partial class ListPaging : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ListPaging)); + + string IMigrationMetadata.Id + { + get { return "201612061904083_ListPaging"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.cs b/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.cs new file mode 100644 index 0000000000..1f21f69193 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.cs @@ -0,0 +1,114 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Linq; + using System.Data.Entity.Migrations; + using Core.Domain.Catalog; + using Setup; + using Core.Domain.Configuration; + + public partial class ListPaging : DbMigration, IDataSeeder + { + public override void Up() + { + AlterColumn("dbo.Category", "PageSize", c => c.Int()); + AlterColumn("dbo.Category", "AllowCustomersToSelectPageSize", c => c.Boolean()); + AlterColumn("dbo.Manufacturer", "PageSize", c => c.Int()); + AlterColumn("dbo.Manufacturer", "AllowCustomersToSelectPageSize", c => c.Boolean()); + } + + public override void Down() + { + AlterColumn("dbo.Manufacturer", "AllowCustomersToSelectPageSize", c => c.Boolean(nullable: false)); + AlterColumn("dbo.Manufacturer", "PageSize", c => c.Int(nullable: false)); + AlterColumn("dbo.Category", "AllowCustomersToSelectPageSize", c => c.Boolean(nullable: false)); + AlterColumn("dbo.Category", "PageSize", c => c.Int(nullable: false)); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + + public void Seed(SmartObjectContext context) + { + // Set Category paging stuff to null + try + { + var categories = context.Set().ToList(); + foreach (var c in categories) + { + c.PageSize = null; + c.AllowCustomersToSelectPageSize = null; + c.PageSizeOptions = null; + } + + context.SaveChanges(); + context.DetachEntities(); + } + catch { } + + + // Set Manufacturer paging stuff to null + try + { + var manufacturers = context.Set().ToList(); + foreach (var m in manufacturers) + { + m.PageSize = null; + m.AllowCustomersToSelectPageSize = null; + m.PageSizeOptions = null; + } + + context.SaveChanges(); + context.DetachEntities(); + } + catch { } + + + // Set new default paging global settings + try + { + var settings = context.Set().Where(x => x.Name.StartsWith("CatalogSettings.")).ToList(); + foreach (var s in settings) + { + switch (s.Name.ToLowerInvariant()) + { + case "catalogsettings.recentlyaddedproductsnumber": + s.Value = "100"; + break; + case "catalogsettings.recentlyviewedproductsnumber": + s.Value = "8"; + break; + case "catalogsettings.numberofbestsellersonhomepage": + s.Value = "12"; + break; + case "catalogsettings.productsalsopurchasednumber": + s.Value = "12"; + break; + case "catalogsettings.defaultproductlistpagesize": + s.Value = "24"; + break; + case "catalogsettings.defaultpagesizeoptions": + case "catalogsettings.productsbytagpagesizeoptions": + s.Value = "12,24,36,48,72,120"; + break; + case "catalogsettings.allowcustomerstoselectpagesize": + s.Value = "true"; + break; + case "catalogsettings.manufacturersblockitemstodisplay": + s.Value = "8"; + break; + case "catalogsettings.usesmallproductboxonhomepage": + s.Value = "false"; + break; + } + } + + context.SaveChanges(); + } + catch { } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.resx b/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.resx new file mode 100644 index 0000000000..0f75080b69 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201612061904083_ListPaging.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/201701121659419_PictureSize.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.Designer.cs new file mode 100644 index 0000000000..11d3d07d2a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.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.1.3-40302")] + public sealed partial class PictureSize : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(PictureSize)); + + string IMigrationMetadata.Id + { + get { return "201701121659419_PictureSize"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.cs b/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.cs new file mode 100644 index 0000000000..4130cdc89d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.cs @@ -0,0 +1,20 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class PictureSize : DbMigration + { + public override void Up() + { + AddColumn("dbo.Picture", "Width", c => c.Int()); + AddColumn("dbo.Picture", "Height", c => c.Int()); + } + + public override void Down() + { + DropColumn("dbo.Picture", "Height"); + DropColumn("dbo.Picture", "Width"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.resx b/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.resx new file mode 100644 index 0000000000..e241025651 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201701121659419_PictureSize.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/201701251835419_SpecificationAttributeAlias.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.Designer.cs new file mode 100644 index 0000000000..b8a74acca6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.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.1.3-40302")] + public sealed partial class SpecificationAttributeAlias : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SpecificationAttributeAlias)); + + string IMigrationMetadata.Id + { + get { return "201701251835419_SpecificationAttributeAlias"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.cs b/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.cs new file mode 100644 index 0000000000..381b4af621 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.cs @@ -0,0 +1,22 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class SpecificationAttributeAlias : DbMigration + { + public override void Up() + { + AddColumn("dbo.SpecificationAttributeOption", "Alias", c => c.String(maxLength: 30)); + AddColumn("dbo.SpecificationAttribute", "Alias", c => c.String(maxLength: 30)); + AddColumn("dbo.SpecificationAttribute", "FacetSorting", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.SpecificationAttribute", "FacetSorting"); + DropColumn("dbo.SpecificationAttribute", "Alias"); + DropColumn("dbo.SpecificationAttributeOption", "Alias"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.resx b/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.resx new file mode 100644 index 0000000000..63dfc803e8 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201701251835419_SpecificationAttributeAlias.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 + + + H4sIAAAAAAAEAOy923IcO64o+D4R8w8OP50zsY+97NV9onfHWnNCkiVbsX1Rq2R7735RpKooie2szFp5kaWemC+bh/mk+YUhmTcmCd6ZWVXqerFLSRAkQRAEQRD4//6f//e3//W4Tl88oKLEefb7yzevfnn5AmXLfIWzu99f1tXt//jLy//1f/7v/9tvp6v144tvHdyvFI7UzMrfX95X1eavr1+Xy3u0TspXa7ws8jK/rV4t8/XrZJW/fvvLL//++s2b14igeElwvXjx22WdVXiN2B/kz5M8W6JNVSfpp3yF0rL9TkoWDOuLz8kalZtkiX5/uVgnRbWo8gK9epdUycsXRylOSDcWKL19+SLJsrxKKtLJv34t0aIq8uxusSEfkvTqaYMI3G2Slqjt/F8HcNtx/PKWjuP1ULFDtazLKl87Inzza0uY12J1L/K+7AlHSHdKSFw90VEz8v3+8irf4OXLF2JLfz1JCwo1Iu0Joy8Bw9krVq9s/vu3FwLQv/VM8fbV/3z1y6tf/u3FSZ1WdYF+z1BdFUn6by8u6psUL/8DPV3lP1D2e1anKd9T0ldSNvpAPl0U+QYV1dMlum37f756+eL1uN5rsWJfjavTDO48q359+/LFZ9J4cpOinhE4QrBRvUcZKpIKrS6SqkJFRnEgRkqpdaGtxVNZoTX93bVJ+I+so5cvPiWPH1F2V93//pL8fPniDD+iVfel7cfXDJNlRypVRY1MTZ1ny7ReofNsgUmTyaZr8DjPU5RkwDAN+MqLpCx/5sWKFFRoSYYfirJDODktrnCVTk/x43z1NHkjn1CVEI6mZCtnaewdKpcF3jQCZ4b25pmrj3hNlsXqKmcLugzl5EuUrVBxVH7HqztUhWJrsPw9z6anQ9PU9yLZkA22IkJM6rtN/cV9/nM0b2EjPybMjYoI8qXAecGksl6+WwiPq+Qu8lz89nrYffV7cvJ4Qjabu7x48tmZk8dXHIbD5qxuy7At/+mXX6wm2ZG93uFykyZPXyjLuzCqNf8sUFWxsTjzDhEJt/iuLhj0qxbPgYO8OejtNBz0LUnrGDuFY7OMVmbqevHsx3yZpPifaNW16cG9LY6GeSWEBzZWt9VMh9vUAipWkt3VyZ0jiwB46NQhQp/3RV5v5hfQffvbanqu9f05ecB3jIEUM/nyxSVKGUB5jzeNPUVeWdcD+FmRry/zFFrQPdT1Iq+LJR1fbgS9SgqmXvvJlL5bgaKkxXOQIOq2DBvhm4mWSzszLdW1O/EU7ZPaf9RogfITgkPXeoSD21ma3J2vyWDPcIoM5P6z3WgNR9wqDT2PRT50s7VU3oefE31VcK3MZKK7mYxLVDIZV6oFqACplqEqwF42jsSoEroTuv7amYA6ioIm4DxIWLOsC9WuOlpv5+jStb6lI8x5SVfXRVrf4UwSIqaqV3m9hIRPPK0qXCiAupVRhHgJhQtUrHFJF+clWjKjvrNAWKBlTe11r0RcB0GgbivSZZLr4d/mIuvtn/88RduDNXTylpWL94TxNirosoK3dZGHr4UqwxLWQ0pr2AAetIh5VD4Gw7Z6+YpHdFi93qt3ohV0ViC0IKy6Ye2FKc9XyePpI1pvgm+9CKJWEad4hCnQVz1aVvgh+PLpvGykWsP8YbiiykdboSRKhokFk3jisJRjftpFTta/u0Ci1Ur270EIqduKdZbYpirS+kRMfmEezehA78y/ZB/I+rhgKn0YtqM0zX++r1FZkXPJt7wKRhhgE5Huici6e0cY82u17DDRP6/w2lj3NFt51gy1Nfkd26ikAY9powJZpRuVQhqcVuyT2kdZ+ZOIM2WnmvLrRoyOu8UVySJdKA+W4Q2yIEneoDjIc42MIlTaT1n+uV7foOLLLZVgZdgApjDqNssnbIlBax9agi59IuRiBh2N0idAXfOLcdxZBRgoG1SwwXKCRxwkLXhE0WTGi+OkRG0HKHU7RbfzoTOuzoZMzmvUYguYavYhtjVxSpAJIor54bBLqNvqaPS+xn2rzW/Xa8+StGu6gYxxBXlKZjmdvBULr/S4DZ3lxTqpQjfsDtsiSavJu360WuPsJF+vOY/h6V4ylNFsTEe3tzjFZLmEUjuOxekdSlGEdxSd4epoucxrwIV7CttVHD76mJTV+eZotSJHNN1zBluHEYPEKxCVlF8y8Dzp7GxSVh/zO5z5HlBJfcZFRFQrUXgrBC1JFXcTnei/5sAGNUAulXZ/AMRVbz3GaUqmuZ97XTdFWKCvYxB1hwU4116Lmp6u2y3M9aDRyP0WYSQtWwkIqdhhN1Y9aqVBGIZQE9t8M6Xr8ekjVWiS9Kiu7qlKs2RAulOOrgY4DVYVpDmxq+U6QUQNqNcXeVnBY+uLwYHIpVKvARCvLjZvPdV9ZOXqTo6L4V4KMK7dZGd+uIesCOzcuETql1Ds2qVLRM4YhEX+YCZasGsjELCLMITUVQWYe5d/JsXqIsdZVX7ABAm9cQf7LcEpeq+GA8agAXYdSXfVabXXSMCA+BNg1AJQBHQVgYv7nNU/IYfYc6KUwX0XoUDyK4Ek2qshg8w6PUE93iGt13n2qkVwONOr2yKHv7oLDvAs3lSf4aKsItmizfr4LA2ZrBhxWiFrZpNk079HP6EnzkJ+LGS8E6wQ+faAs6V8Fje0yL3onWxYrax5M1dDbydv6O94Q5W/JDU8Toh0TX6fZ6i5zZleRiSPM7UUZkBQH82aNQTu7Z3m0MMMO7pQJGkhYrmz8sEvUm3nBEi5iyMAZUfHUGG3CB25nJWNd7hAS6pvvmpxHPQNdVuGDXOid17M2aVsLTlRPGfKaN6QP/OPiBLxvJzjFdjVfYGQbYO/RmiQCFpU4KXQmKdjUH3zD7LUrvJvSbDNehfegs3lgXRJjvVkDgjujms/oeo+V9jExjDXQ+VGvOGxhc8MDRwXjVWcD+z8RqAY1mivAHZHEEDuPAgVdM4d08PnGUyLQJi6wz6kEUv6fWiq0C0zhsaKJ5fO7zJC65N7uhImkUqcYJlDHonapIMI81vfY2U3QMEcYTosb3VbKiNDXJ/GqbTVm5sCPWCTVS7OFfcu6EFe51zP/Vxa/NpdP8ghrb/yjeCX1uI6rHnNmm9JFbrobdxLbF+3m9xwjjYbwnrhiy+qo8rXzWoSo1V/xxTbBUJ1m6b0lfBa1sdpftd7sDkvaVq7fMXh2A3f4rYzV+hxerc8OnhqUY7nxdxhBFmKo/X1ADiwE1QusRIIFMxGTVcCeIgiOOwF6rZiPVCMdWfp2GykwNA+hs2W2YNjLXU7W4vvhPcz9VTG8yo+0qvkbvqo2Nt50GgZ+NvW1GFubNcCf8cZWeQYZBPdCHL7FGxF8doNReOndsuM8h62xwq9iZUKwe16DBEWoqjIV/WyuiSncfTT5xyXVAnp0asRnt1Q/Nou7coGqW+lIdwsSuplUnG3en5E+YDSzW2d/hcqrwinpFGQfc59cKnf/zXTDz/+47n1uofknv1BAPKDPxDK+bUqj6WlRUYO56cai7GqzrWwnhUjUtaQX7TaVQt73NrRP1T6HM4LRmlImwt/VleQNlnoaRaROIaU/YZLTKDPsxV+wKs6SdOnUD1kOxdgi/ucaMIz6olnpH9ztjfrO8eOa9F6k0Z4ohg3wEyHJ9495OFAE+cpLjvmdw+t2A4V7bTfaE+Leh3tqB8JY4eOKVHCoIP7GA9p7+F0tNy14NmLH/X0iy7J6ttkSVWQguyjldFLN06z7yusW+BxGjkv3+Pb6iQpgi97OjwxtBX66AsX6Et1TyjebCcRcpsxnIPyY3ioHUWo1VQ3pi8riW50tFoJfQge03n5Lv+ZpXkSfk/e4gmdua9Z2izwDmHwGD91bvFfbiWcniGVWjSnjxvcJIV6lzyJOO1QsJfvDEUMtv+QlIuEaE0o1qyOsTk+UCG9oSFRju4KhHjF0bczI2SzWE3Oy0sairuI4BDdIzp5Wqao6VSojOMxXqAC58Grr8fJ9n6GOHCtnDOX8tOMVokQ4CNmxF4iTzFdeUnaYWycAXv7NVriNbVNXRTkV5v1+S8vXyxo7Hiyf3p0P1oAl/PytAwmJ5cPMZRxiIpD7yWzB7I0CbrG5zD45Fblyx9/q5PW1hEkspvjGsN49JBgUhenHNZA9zCwp94bFs4ijvxj/rMZdRtjJdh5MK/w7RMzCJzlRdfHY0ROX2GIj5PlD5bwlCY2D45LRE+DFON5Q0tyAukPvcEaBTv0k1nC63odZ5IajMljPIyMAGjVIsNoBmWVrAXaweP66biuqsG4ELC2KPR3XN6nuKziIG0Xf4oI8xK5PrLfeF9+Eu2cocPLYPvSCEn0HehLupq2gfZgcsKuYSdqY7EheJLUcSD2OHu/Bnp57eGhwONq/Rw8MXXmrNOsQkUZhb9asTXCjCZmilawzdomOXxcYdSsyWCBT/ZQVFZHFRGdN3WFTvL1Dc7aa72ITEj6TGQei11HXWhTHK4xf0f47n66pTg+x0RH/x2vJsT+YVra9DtNqDzpEYUJk3g3FvEeV8SJ+7hDbuLyAPEDKp5oZUfrSacGEhVMvmg1adIlanefKMfuHt8nlJR1gWiXNOpklDSbfZtHa94dMlTE9mjpjzFqO9LW2SpF7EbLYIGKY15v2rtABQ1AFcvwMUJKqREbJx85yxmxOpZnsyX2WyToJNOVXrfQV/lgKx8cY9RQkjOMBtTVRZCXBjoPn+sxoOTPMypXOSiNgTz9k5pZVUdck8CuWz5QelepQFVOSEp4V+J3J5PG4GXwserMYho3MRFENQAJzrPj/CWitu88oKb/EJhqDCCs5zguMMOjHUILo+m9AKHquAjm2ef+EjyeW6HW5y60v+wEetvGY+2PLdr+w1U049FXUI3PUMtzvM1zB41AHcHJ8pQrVopTHsZTmn5LCkyUOugcqe2+pp5mgixqqWbJpqrnVImonQbuMFrXIXqPi9fTtTw4BpSZkC9XcuEIyDmQmXA3rOmsCCp3dwyh7LAA5tpl3oYDdLcvhjhDKpR4QYYI8mnuVEWvYBitUtv/Ovg1q9syePlOlKS9m5kYnhpfS3pWWpLhRvBJ7TomY4xu1Oqacj2VO9/8buNpYetdVZ7k9SbP+MBu3pYpCdNkEU+6mWH+kWyVhfLogIkSz9UPxHSMv8oNRzH54M1X0J3lBzhpW9ICu25OPQ7z2UxuV6ijG88I1GJIY3jvUQ2GkynsLOZhSCYZaztLi6pdz+rXo32bcA1gHBCgeiggdFgQntaaEPBuqkNxUDDUbRkUjImeEdFXPZFaNl1LzPcC5DivCJ/O2mKyujNZ5yO2tKiehuctvs46OJkh7Ve78GM9rDo8XIqU/pC+rIzliNlaPB1vDOmLtgX+p8jGFi5mfUy3q5xsqWhZiah61dmuB1/GLnqTBSljx/7LJLvTesbFmeHILxDje4ns8PusXfM1iOdFscteC7cJUfy+YfTz0yRJMmJf8XocDM3XvMAp0trW3QhhOCRgfx/YAXGhAIUyOQSgCBAWB6brgfNZ4hNa4eRVW/9wkNCIr4ZExzhLiuFhQPuXzUIyeXOuWagc7RY2xVFl5Jln9xps5G1nlx0J5Wc4RZn+SPRrpHesn9HP0M3hvLwqkqzEMR67xRTobLlSToZiB9oKNR4JfHfUyqQxIHdzBJTL90YQkOd9q5OrgSyNYQijq4Egur0k85iEnuKZR3KQ0eq2yLJKfESzdhG6BkLy34uFeEiHTdk2LNJUR+35ThReCqi34FNc8avko6OwjusPZuqrJmybvZDmrlkC1i2P5rBq1W1txyY/q82R44SDkXa3jLQHs+rOmVUPlsa9tzTGsGXHNiZ6O2aYTYqwI0cMpW7soS+rdFC5pCSBQEEqEuAAEcU5ksN3UJm0IoSRK8IRRST7ZZ2ixVNZobVBL4uUa2CDJnc6bBPpRkqy1scUioNuSN93lfdRLsmqddRIBjT9IXj6RBFco8kTZZ4+T+tsDXezYdaDYrfcqieO83T6SAQVb5ia4fJtcF+fzlVOuUdqHev89p7RI8qA9JQ8osNuo27LsBFYPgt39rBO8+IDevyWpPX8rbc6+sec7jlTP4n3PRC4mqKHJ7fh1ugB12HlqNuKZJAePZAORRYt7t4yZvwG9VOXLWcMMHqAzZwgAKeEBflXloFOZ3iFru7r9U2W4GAXsTbvw+4YbJ6jpUVtFumYouERy8AOQq1rfpPQBHlQVzMHfNDUdTWdCFEppo1kobh7Mka+iHhhxrVlNQD7rke5OTNNdUy9Q0B9UEPUbQ1ECw6/2hE9GiKm3wfnwC6J2tHcp8Q0NPOycDuy1LhuLeRwyEqO8CxNwHRYp+q2Imn4sZ6cnJdnROuph7QUW9TH1Gnaew61iPo0AKvDPvU/FGtPBpxgn/eJU2Xqb8wdPpKLDIDtIB8mlw88uf8lZMSYWy0jrI0r6cOsjf5QrEN1hQnkR0isOJv+x5QlcOiwcKkC4z3Il8nlC0z45oYuSv6HRteNENy0ecDZDjvGG85JfH7Nyz12kD+FCLAMDWg7MB2feIxWj86WBDosjnTRogqSnPqhegtOHdqD3HQVeMEJeeL430QOWfHrNm88LcRkvBCpM8gTt2CqVqIpTPiCZNK3q8Az0MejuiRofXBMIGGjy9aDVD3Iv6BN4ixZomqRFxWnDbNFOy7wlqq6hQcL1S1IC1GMhkickJPsVXIXfmwlSA5SwVsqxHotZlIzooUlV6gAYOjyEN7UhP4O51kN8gMvq9uaI0f9LMniedvgBZntJi/39I/3YiZpc015psfmmOHM4KTlmNDM4A2wmxmYjsoS32VkFXUvMufIbLqVNF7nJcvZG+4nF8dcOxyS/3OdRlC4DTIvXgZkZgv+UldfbhlSpk27O0QGZ3XSJdUwJHyyraoyPFrXn+CKabrkJR6D9TVB2yb50LVtyP9hW9Vn2MasIdZa4ngQAY9qeEQH3U/d1lYe1dh5qsd81LLbL2igvbkNkum2j4SexuIfwQ5rT91WJMWpRRPtwoeGNief1pvpA5yfl+17zOA3Fty+lFVFnlJsOxk5y12j8clKZrmJeyssIs/5jAe0u9rWcR2h/j7GYqgiYuYw7pRdjtW4VkpeIw209W3z0OmRxLDwRdxMDruIxS4yT3KH7dyCTfekMVRTat+Dx1aXGNoDt6vbUtAt3J9vjxaRRcSCxR91UqDy8u5m8kE1NtzVP8hKWKMJw+d8Z9bnGRoKe8U12ZGQiYYYau1HnP3g4vNs5Rm/q7a1O3qWpd5pp6x57UhD1lzPwN0dgsM+o1m3LY3e17hvtfnt+sq+7LMTfy2CQxsAqCbbSLq2pkouQU/qqEl3O0e8ClOehzgNnT6SMZXx9VVomzpklGBV/TNK9GnDVSklQAA5HycIFSTj+8zhAUeMHsdBzqvbiqQGwTH4XB9M+8ddjHOT7ejI4v9YmhsouDB71r0egw4rE4aQlqYCLOY99NAEYKeVCjU91NliXc0Vl+gBo58fULq5rdMMlWW4qUJCGU2svKCuOpzPcDdVrYLw0mYBN70LXYHfk7IdYDz/jVEHdecYicDXQlXp5GKooTqrmKoFMSBRSsqTfO0ZipnWfsWh2A0eaztjSgUQy37TNDZHgmFKaa94PEpu7zCCjM5N7PUAOLA1VC4xMQgUzLKe4Sh7fj2EoDQYnZLsroaUedfL6Uhr0P2FdhFjObo+q2Gia+5WowU+XFRkOdAzIHgkNByks5VnzSbBSSMhggNTHm02Rf6AVi2+E8Cf2PU9bV7FRxo5V0nUOJOHFEF+I1PusZ0sVe6xbEscoMYb7KgQ3F3HEK5nJW5/ht0jvLQA0b1Bqyp4aQHUcldkSXpUV/d0T2te5l2iJeFbn9NTl2HplQ7xQWXQCKGWgqEqw+maiyk8qeGXznIzulusfXcUt8n2jnHGlr9QXmbcN09TR8slOabO0yD58wGvUDFl9hmjZQwUnDpBcj3UHCSpVQVpC7CrFXTiOsuLen2Rlz4mAla3fNWjOIhQdVtX+QYvY5mlY/gVz3+YOb84Wq0KZgGd2BFmH4Kza+VLv6RAYSKXSpIDAHHVHhkKxraGLvKAUCeHck03OaBwedZ2JkigMRwHiaZui1FpZyQana0YLlmL+uYfaKmTjn+a5iXW52YhlGHd/4bJCSzQgJGUFe1JsHNbiyfWFHf4GrnrZoh6HhtCIx6VO8K4GJa1AozXnmDqYAsE9o791HWtAQjfAgKl/0Hwq9tiBHpf5PVm4oCGtomqd+YpAyjVGV8HyuQ4mwOVoFHOIc9xi/jXSg01rGG1NL/mgQRpzpXB0pwHCJfmbSeCRDrDcZDrGiHz7KXxs1zjke8f9SIDvkdyEhfi/ZFSnniJi3ZunSVFEyuo+e8gJNRtMQIZo7lFunCgbQU/+4jqEBycuLXJPN8aQmNlnlehsyDvgrBFHa0jcbC1ueOjImsS0U95k6TgmJO6KFC2fDohNWdotGnsMqkGBVjvPf4X77VwlTy2G2oMw9u3xBwkMqJYWdQ3FRGJ6Xm2TK8o2liu9rrGTh/naeyKNtYlUJ5rhKNG5xlpK3XmGWHb2KwjIw05LGX3xkbCkewimKoISXqG0NQ0Vbc8NYHVLU9N7Ra/7v4zooSbnEndM777tMLEymRNEMW7zlZo5Rr51rmZn0mxushxVpXfUYEI24X79J7co+WPvB4es895lJYanyVWbKd6xPJhP7q9xSlOwqOW9OeDzeQ0YE7V9FBD0J8UiMivE8JbY93Jm6UIJoph+omkXZ5FDZdoM50DclL+QCvVlEw6wpOHh7ezNHT6uMEFMyR8yrMhnvlMbf4XSqanJ7++mii279ANjpDGtEd1tGT75oc8Xc3AH3LDMzEm1/Bxkv2Y5QAstDmLiOHbPD+Zszn2XGUICDJHk+c3yQzKRbubMgWwf8I69bqvyYGgwP9kkobF4kiW9OegGsze9CxLRtX4JSq5qMcTSvgNNaTPS3C50ZlGu6hveh193iFf1MXyPinRnAb8iwT7Ph3sLCDNMX36eWmbo+dzInA2dXNbMrvV+B1KUYRYd7t7PcmfhC8RvXrj8tRZXVt8SGi4pdaK8zmv+tRtoUSjT1s21dU9Jv1LyGf2WulDkq2+PHicrJT3qOMrIPA+la3RaxFwuFOFyiU3DBDI1eFP65TYtAD5I45LFF3z9kLsDE9fy+QOfcAlTTMBR5UCAK/bG2IutJQSSrqr1oC6plt9j2/ZKdE4CAjw+muJVt9xdS8NxgwtDcqiiuvgWC36EFTD3+ydqNR/oUjqrFju1TMiNRQBw/tiRc+GIrhnXLlrzy7RCqE1WvES8rRR74GO8lBGpjACS4Mx13BOL0z6rn6J3JXKZB+XyJk9x8U+vdpYSWMJUpR2AoBC6IlQYfFHQUHokaunQVOCAvPgDaNuq79jDTQfN1It1AYdpPQZd1v7HXaoYdhku49yHEcttKu+0MqUKRQEq46PJZfXSoe1BV8vNwjbYZmr2+roFbpCRztoDGRt7oOJbi0nEigdNe213aGGQdHtPkrrUg/tKlAERWsOpd1qQLBOFyRwwoXMQbCo2+qMcpxUgGIA2kmnGF54E/tBnJddZ1nC2ySCqatDeJLXm5ks5peEGBsaqHsWk2Df2jzRcxYoo+fYOUbWNDXPsD6RwxYLwTVxOzSPXscdzDa5bWPuRAYo9y3ZyuwkbeC2o4ElqXY816o68ogUoMpdWQUftCVzPQx63nIIkGpzGCdECn56EkXPv9hqeiBR881wxWKkT+0f3Dc0uTvwHKOZZSRTuy93to1GR5yaZOPWpqbdVnxo5/Wd7XKjRIhxe152yKKp8R/JAsmGBE2ORyAqrZusdPYcYnioV2erFBE1K5neS6IR8CcsCF8s/lYqSkdlmS+py/OqU1bgW4+IWpJK8zNpVeH2XYeLRvC6BriItFZINYlKBtwXcqISqVDTtYsYiUr6i8tAvZKiOOiV6raiaIPNPAVLJPfNmL3zzAdnj5080xokgd3FPrjcgIt/r8UGXKwHhbQG8B3WoIbvIr3jbwgeA8dxkibZkOvL/y5oYtPtXAa0iSSD1pEN8naB3Np0cJLY0ALHv3GK7BBkM5qI102dG4+HLOw8anofooP8U7cVRQe5KpLlD0LxmbzI2QvguKc7xjPI1zf9HUrxAyqePKvPrvtYO9eJi17he+fq8qd2BOUhrgcZIHdwBKB0AhxDBcWR4lHGEEoHo7t5RTI6RfGk80mkF8deblwMMdeBar3Cq8XzvELYMbtEf9TIK0dEayAYoTmsA806iBGtLNoiiHVcinP5dImSMs/O8qLhpvnNIC3/Imb2jnJBENYB/cssu6kV0/NN5+1RJbe37cuLqS9WVmucmd8N/+mXKNlGRrItTtC6HXq/53ui5miiOEtDEMC5EwQLPGzmTDk7IZtQmD+FiOmws+nWf4Sd7SIpWv3G8Yawuc/zqMhPcQwPy2gWyDhuIduJ30SWJCoID1G3iMkslnGUjn0QxQNzK04XYyl1zcPzZwwlGHDSUMNGfWMsNQTtJUogc7+9LbK6+12pFeCaVwVj7nKUS18pfpv/JiihOuyC6rYMjtW2CYpdLbbosSKf1pvpI5xQB+g/alxEyFlObWgUvmX4WHjPy6vk8fQRcdTwRUUQnRA2uMuLp2gb8UmeVUWextA14uU7OC+ZpxcKJdhk2QkkIcSexsFWZxj2GhCJg7i2rSPZpq0rBpmr4VYiynSG7yDY1W1JFJs4OdZEOwVTyY9W/yB8w1tPoivnzX3eDA2dlwQTWfZoGcFLNUCg2kuu+WWWqHI6CzvPq4VlXVC+bmM5BT9rVyA8SC11WyLJnnMgC3GsCsMlyEPXcmXelGlXBzBuWlaMus7iLbDDytIw8dMyRc3WHLgaKKILVOA8OP4E86Rh+AJdFxcVmXal+8uWzhaRYieeZ7jCSbrLkozvopUUux7XUIuuEaBRXo2hXS1oyv1/brEsBz9zlOdeYpkxPfn0Mb/zkMik1h31L+KwHKSxui2OTLt0iRMvtvVuCCaBzOBS5mCuJfhh9WrAJLmkg416G8E3BF1EQOXa3sa50JbIGEOcUPiDSFG31USFJj36mRe6ANVvpjHUGMxDE2XrPc0osKOOZc3HYVvhYQvUm97zu4/oAaXhuT7zojK/BrJ2rnJs/oyAzxbMZ9NHew1eaJ4Khckd5g59LXR+G2/+HMnH7RYVBSpmaSyqywWVDtpHKhMZ0j9U1caYvOBNDHJ9LY3xumz3oChKkko50ipF8ZQhlkmcz7PhsaU0uc5fSagO+4u6LZ5OwZGTYh2w2AyGm802eLmjpiDtSpT4F1yXaihplWpAg9bsRcGiyPT7uu+CHeM5rFbNao3hjUt5KNZKHWJLBCuo9c0/0FLr+P/nybys5jflUIerJIJTVGs+P35qEoJFRNhHxAzFOZEM5dkYlKNjsXI9hh8EqQZMkqQ6WFeTFR+Wxdx7HlrZ9wHI1HMOMsyG1aXi9ZD+fd1y+HkQ/uq22lNtsHthnAs3z9s/tRFak8ulZ49rII+LVCibbiWIsIdIy3u0qlN0lZQ/PNieVitf8UgOTK9uy3BEtjVduDI3YRed+SiWxSTPTh83lCP171LfxHny2NwyKFv5yw4Zn0HVd/MlOy2KcCXnM9H4Lmuf57cfE/ZWtKg8655mK99W6+WS8IlvuzzZpmOw8/IDXpGFHTpB5M87uiouEJHjUthTu7pma3OkQV/mP1tp3Q8bZwn1iDjJM+p6gLLl0yeGjrU1XnNwBzwEapP/FK18FbllShT+3Ct1yIJ6WBB0r3okhx1N3VZD+lAtrsGyHW/z7uhAtavQcZwTfTaNqEbyfVM4A7Q8ej0G5d0BIAjAIQAEC1ItvxYhqzB/1dc/LMDnvAAXaX03f6vRXpMl2V1Ntma3GbDPgUUZAy9DnshSB688eyViOiyqqRcVGdH7Iq838zM3aXn+RkeJAOezPXvcIlivvqt7tEbfkgJTVD7WEVq/fDVCc1h36rYYoSJw7iyvGsNWw9s457UpuZ/h9tHd2Lvq5r8Dt0/Oh67xX7TuU1PpeGUay3JGj+cGh7M4K/hDXmpD0kWyuHzM7/ILvKQrYHdiLXyo1ulxvuJUoOniMeVZRdZLF9D4M6p+5sWPyWf3osBEMD2xJXzS2rXC42ExnKePy3tyKkA0E5Y3ak3QH2UjcCAgOsJrbS0uIpAJWA4NZKzhHtVInhnzyARwxZBGUPqxjEHDAh313XLeS9/hAi3py69XHZLDjqpuy3i/No0BsZkYQ97fP08SjNU+2d5ffLeTjzlFEE5WGzPsWV6syaSzFqZtLzQKkCi06AIr77ca3mNKF/awqIHiGJlsO81WZGaja1hux5tPCQtsF3jKabEcRLO6rWdgp57wqH2cLH+cZ6SF5Y/AZwMnSZWk+d0rBcYDi0adYPDGPkZM3Givu7fg8q9gPdDx3wQr6ejGCjGDqKoau5BjqRpArcdxESOy6qckq28TZlMortCaKBR+N0etLIHQHQSJuq3t7E/fMPpJuno/f8u+Oqv9AboNcBqBl0VUBz4+8PFsfNwK9whsLGA6cPGBi2fjYqYo0cwJrRLkzcRjRAceVrfVnyreRDqdvN3OTZP9jl/kZUl08DScy0RUBz7bVT6z30eb4HddbAtn1mjrl69GiA6MoZlQnlBN2CnzvjpZ8J13iDORhffAiwf/VqMarU4JP6VHVZUs7z3j2rZPO8pXIMIDT6rb4ggW/F6f9IRMAr3qGjEVNQIJMyKBuopGvVIYyavkDAOvGQx7OtkvxUc/Nt4eQdr3RDHiPuE1om1uoWW0wknLI6YJUEcDwIr3HqCMuG7AB1ukGkoyQ2pAXQ2p/MgdOj+uZhoED205mFEV10FxKB3GNKplGhL31XJEfI0gO/Gon1F2rsN+pZH8Bc6L4GSAlJ3mf0NOWzV610R51p3PP7irfJahXaJN+hRlfFbtzDKmk5PJmzheLidvwxyvKZJeRp1qp3epjen5syDC7arAwekaCBqvJOHN5rdc5nV4ahjSiRW9zEzStJ+FCMFe+p0ZDvgSWUcQUwXYaxW2A+JJbhrR9RgYHMgIRqfjjAGDVJtxt/x1Gx7PQbkxLFPdcevPkzjAtoZo0ynTsnHzg5D5R3iRF2LMEFffzZLMZBwCOds6ytIQmn6ilpvXRosydRbzMvHeoduErGqyq7K1QGq5IXVwc1hvEnzn4/bXy6sOx0FWqdsySIupHiobdcyJGo6kc27T/TzIW9/eba5ZRAHuGf0yFFAdVqP3apzITktOlY1q1wRRRNPnV3heZ8xY8VNiHu8iv+kJOi0qz4hvPK5mhtpvg2r/aqxtLSs/o5/lR0SXeuCDhV5kwhgPklPdFkyx4MQHWzrKxZEncW1fEz75+YSSkjBtk4Q86GXwCNNhvWjWi17TmChL2paTtF1S0k39Rng6R8CWud+R5ZuVfjuMtFJ6ZIfFclgsz2mxnK83eVGRVm+xV7yyUf3D4ti1xXGWp6toedZc2yYcQduM84I7DqY4AaV+4E1YR66SHygMQxNd4UsWfs4ki+QMo3RF/5oheFXHFSd5dovv6mLsPTmV7eH0kQgb3l1xwuBUab3O+iARE7d2iUoiT8+zW52PSJym2owdBDnN2REvOtU4nQhwdTzaYq7H4MPlsRpKuj7WgIYlRnnKlv7RQSh3dtGwXnGoDpuqZllHCRHS8Ic+Au6baSJBWUUnmWh/ZmH0Hqstae2M5h+SUv/cMVqYqHPHNBpNra5PU+8ZrDG6gUubuensky39LFf2XjGPVFi+Q5s0f/J8ryKiOEg0dVvtrhQq0razqCPp1zMqNQNTxjhfWLjNRApFavaSidOQMWpulLC5VbW5KpKsXGOWCSzGVEA4x2+0mFCCwTwOuo0VKtLzfeOtcHOsn7wl6+vuSIzA2rNIhBtxcHGukel6xA/oExfPM8B7zMcHTRes6lb1NkzcmK974OFcpYKRTlVKwDCn3Mcw++So/kHxULe1w/bJiaKeULFNf7Wkml5uW76BjxKqPH/AhLBbfXZ/XrabYrd4A91+Ihlvw8xYMg8R3pnjjEj+pNviHMbSjnsaE+0sVtMZVf4vt7clCvSPZ25jYSiOk2p5v8D/DLwHuCCLvEm6sjtOdTQVJ8ux7aI/RnvE9ne8OSqW9zEcg2itIRlXuC42KEfws68gfUx85GVU3KIZ6Ec6ltJAr4ZSqJIRDPT1mlMlz5IlqhYEMeO+8/IsTe7Kfg7tNcsFSgh/vWLoylc81mj6JeF7In/TJ7pLcQtxPGGf0PoGFd15F1cljXHz8gVLI/X7y1+kCR7BM6gjDv6NHn7siNDWATxBG4prZsEUG8V/XuB3eKqWtjhXx2l+YztPVEklhwhE80Ug28lqzBAhk6QxjgRP0OiiS93SFidogekGdsEe79lN0yfSEbwh3aXpKOgAR5Xf2EzBUVnmS8xI2GkZLI1Gs10SBYkJyesue6jQ/9Ns9aIRntpag6gdLGFQBbI7sxERYhLB+vvL/0Mavm2D/bbENdgNQWjkzXhMpJEv2TtENYkXR0zzpe/dymWykjcvQtHV+Eu7aOjjO6LHloQ9cFbJx3ScLcm8pS5DEZBYnvZpJ/vmxBKyS6OMHtRd5tCmH3yuWbk/fbMCMU20++01x6wWPEy03FXXN0sGBqsouZeHdmZduKn941vtOOZiWu287QXHkhNVuwsB6c1LJdfqq0GcK9ZwYVxDawDzjrO9a1tyIVaepuYVPYICSZHTfdph+COEe7VUwa7PsDrBOdiPBUl6fpSVP1FxzfhExxQcnIrPGhBXbuMRA/wGMfBu8BrQ8Zm4DZgLm5Yp/NZ4bXGPmdNdE7X2muheRAdbVmh1Qg1jLKaRikvMVSGOHNdy4UqL9qBtgBWalCQXipFTH2IWY3JAve7QKzsNQYN04QGdyAK2YE+J7a9a7QhmWLvaObJpv62ytUXcWpyNzCjAQWzYgrgwoIjVnvV+efVKNux4sZCiDzMwj4Km+8Q2Y9FjmubxaonLQmPcACNppWR8dgL7MyNTgbS2aX9UcWsM1iecG67EVBwgg0KsNaTHs+ctADPAWHZM6zP2Y5zSy/euAWM3x/DRqSCgtyeFvLo8qNFmsh/yHJr6K1bQ0aOF9SGL1IzGoLB7CpRpFDNILNN8We2HXELPrcir4zS/o/cYZgOPBAnxZQfkwpAy4r0y9ii7PwMLKudkL4w+tPcn+Zo5LvSMo+MSEVjFgS2cKxNK6AE+VDH4bvChagQzsaJqfmya7+pszwSJWSrbceYJpbkQAAaNkQ2ckyUSQg1w4jhDxlTHAl1v5rAxauhs07yYd2U7nNWkPevG0vGEkgFAcJC7RpBOTAa3AVm9YeTbl3f6IczBm9p5sjKDN1V2hjHbpIq2TCPmMJ+CMYW853Ibu8+Y4yFsgTHH82TFmF2ixm1ZUdp03EZZKQKCZ+UWxumQLOK1l4zx9l5VJ+Y42yroug9S7R0um0wRRxsyKTTwazsa3c2erhLEVB28C1Np24CsL3aM60AaGrf2NmHTUxjXFgQMkYKHcyEHiH8b60zXkRnWmo7O+7ne+BG5LLlRvelW3bgZ6Ghlz9FBdGq3WRcSdVWmo07fgr3OFYEm3Y9L9EeNC9S8nzF2GqrlQpmoyqJt/wC6AnDmSfSSdVakm0HoWZHIph9d/W0foq4J3+AHVDyxwDyG480IWHOA8jg5jVFDvMb3czq7kaY38x2BQDpbcRVXb9ucdVxnqxSdV2h9VFUFvqkr1DxJvh5KTAxng0PDh8rqHgxq1RX13sONeVdP/i4jnG8tuLCAlcG+r7U7C6QdiqUhS1XPaiEEcb7Q3h5at0xj2QZfw7Noz8vbtnrJA3Jn5PlY2Id5Y6oW6q5shff20Mratt/b+np7k4EJpAoabvMxviqbcbCR7YygVI5iPi5VzpdNF7o6O8OlljJRhJ+YR/d4K1eNYQsMur9CdGTEHdkPDRykrKhhWF+Lu7FJR1vozrCwcUTz8bJxPm26wtfbKc62FL5QnZn4eY8FsW4cW2Lg/RXIiw1a4lvcBALqLR62DKyvrWFluKIHUxt6sIfsbTei+Rjdbo73geXhkXxheT+vFRypYj8PXODbXg0ap6e+Ht2BHtFZLcvtL5WA4c6wcAJ4w6Z3MIYd3Ui0DO4n2/Xk3dqWo+2W9VqDl/72V1z42Le+Y9nwjf/6a/BsexV22/JVcqeJzyTDRr5c5zGrVTBSHDH8UoPzW1LgJKv6aTnJ1zc4Y4BOrge2eDSE06DwoKl1h7bty+Da0fnkguuc2vRslzwgdOOzPNBZoNgJjt/j853DsHZjaezhSc9iVH+rExYp+2uGg1YFj2cnlsaoQ8D6GA18m5sB1NHd4HhoTm16xtfbNd733QE8xH4Ehn5OAn53pPozEuXdUMyWOlsE22FwrTlOGuO+sfw2rG+u8+2wCHbG2CYOjgXXv1ZxqyNrapE5rBKGJ8JS0fdHvWxMq3ZnV4/VgLe3kqz4w2FViSi2vbicjga2+r+PFW2nNPktq+v7rZO/y39maZ6s7KLmgNDge70W0OklI4h8a4FztN2Zgb20tLZpf5dC51wvEpqtp2cLk4AZg0eWXgJyyCytYN/4sgvuy4zSC6a01W3TqObWOOwKo4KMmkbB5rNnqJgABoc4rId04TEFesdcH4zPtq0D6ocyA4vqp8qmA3y9HWBQk6FLgpyALffRfKXs/axMuL8mqkv0gNFPWzvrGFqz9zaAHjuw0MI+saJ2BPNt2/Ac7R1LfkDp5rZOMxrVe8xUVhykrG5kWq6mN/+qW1czNLxkdoytjQObm8+N8+zA+E3FrbH/Z/SzZM9NjeG6JUiIqTsgFyaWEe9VuG5l92fgSuWc2LS99XDdtPddhOeecXRcIgKrONAjXDeIHuBDFYPvBh+qRjATK6rmx6b5rs72E63Ype6EwaOnGtlSis7TxwoVWZIe1dU9JW/jwiukDVXSxqo2RCpdRRfy2XVgr/KTOA1phvXuNMcutpGtCYCzvKjXLMuAkcFlUIibeygX1gVQO/FpFGuwuhMzcJaauPvDRlf5Bi8t+WgMq2QkBubMSQLyLbES3Iu5eAkm8P4w0zX7932R1xs9J3GASjZy5iAeKcA+XN92bs9U9X8uxgPmw6bpodYuCLGGayyETDPkKcRXg1nFfDvKd0DX5xV4o/mw5rsdUL84fjFrSdyA46tgHHIV94F8vSMsqBjDrDqcPD82zbMKW2PFL8XKPukoBAyxIoNzYUMQsX2u0Uj6m64XMzCSjro2zY9rbpmjjOeBMVhELtpPmwfc99m4bi9PDF34+q9lcoc+YNKb4qmPia92pNTV0iVA4Cv4pImAG9RkNNg9LrUaygxMazWHNv3YegYEcCSN5HPip2YZz8W9TWsA64Iye0f5djSIbTHtaN5sOsEqbHdzZ/ddeh4V4JTbu+sFuoh3f1hQ0fO5dnh5LvaJ2UzucxLkBAy3jy5zyt7PynZ76Cr3Ht9WJ0mxur4gXb5PSrT6jqv7gYNU7GKoB7FlV8WFK03NqMQixP3xHlZY9moG3rOcBitOBDFsnTFHSkTPQiZ+AWvpmNJXa9Q3CLCnahVsX4ZaDWVGntbOoU0/ujq7xcNf+SXmxsijqrNx87jV/VFE7QezLaYG59OmM6OK21VbP+cVsjkjDXBKlZWCOKusHN79YU1Fz+dSVuW52P0z0iX6SZbPRU4QlN36MdredZUgNgTgXRhS29xeWeltRjIDt9rM315Y8KGB2CkCxprWYg88/7g05Ldg5GZdgtbf4w1zP9cTaQwGhppvIZzCyo+x7s/2And8hvUKz8Puby5dv5szc8cqJr4YQeuYztUWBzcARVFXcPXusCA4hBk5EZwjm/Z7BNt1L6Dd2Fh7rAjQEZ0NRMz2PivxzG7ansylO6tobMtTmx3wXLlEVV1kl+iPGtm8jIDBYXWAg3TTnMEm9kxn1o1hFm1ZN097oScPnbaUe6oK0R/txRSATppJzpo9SYqKS1+tvZvR1IG1lDG4m6aibkp9f6jOLh1vs7Do2SxqiHEqbHox1NqiiiyMxLhtKGtMzoT7uX8Yh7ENft3LXUQahcmtQlVhck7dR18L0yC2waZ76Hlxkafpt7wio2gfWNMPR1n5U5cOWl0HDEckgDuFIdI0BXHr0PmdY1iLoczAsxZzZ8W2fa3tKen3aPkjr8WY2NJntdJuiQBU4sG6Tiq9beuQ9iCNcee43XV4M7C+63xbKRli5S1aU5Z1QUhxd5E8MSvjeYYpXtO9jqYWbFsZV3Azr+gas7/YiHIys+rMLOYSixmw6gdXb2e4sLvCk9jGlkdUCGx40+vu3LJ5gFtNS2P7Qtl1dFtgf9N823RJrLu11UCn9YHM/cf87pr7TXlGuQA0dSCe50Bc+FzXCmRTFDq/c5xtMZ4ZmNli7mx6IVTdCfY12tkg4IkYdj8Na7oRzMybe2lOs+JCE/c5cp0vt+1EVoMtMdreMhiLJ7Kob8plgZuE9HZR1sAqypAxPLRz6Bi4qS2FXtN2ZgZGMxN/L9iOjPchqdAnVFKn/OuzIl8b+U5TBw4Iz4O7hYFXNzQ/21n0Zg4Tqpn4Nr3g6+0K813lrqw31JiU8bhmts52cl/mZzqZ7DZ9GGpt70xxe4tT8gVdm3xqJEjwNNEBOZ0lJMyzx75SdmGOk4CKsFZn0y07DR4tUyESNB2U5lQKgcPn0tT9elKB3jGY+vaPCvpxzHI61c2Tix5H623P5aPKC5o+C6+T4un0cXmfZHfokiy1k7ogTSyf1L4fppqgEwit5OT5YWwFZN2279OIQus+zcCG1rNg0xcNmt1gUPaHG2eOqsRnyTH6LfMi2Jm5mRAkuAP3jepvje2Ok+WP84z0ZfnDzcBiqgixoKKOC1Mam90ry7PtaGbgbdv53AvLjWowJj9PQ72ZeXofnT8tx7JFht5DV9C/1ahGq9N1gtOjqkqW9+ze/QxrjlfqKhATg9AuLKxpzjW1+bY52DyUGZjXPH1Whky8xRMXPIRPaIUTqvro0k6aq87IxKNmAWYejWiy50r2fdsad0LzY9MZvt4ucOt1M7ClPnq2qoKBMz35cdwEwIWjPu+cVmAaybw8C86XTRf4ervAqdzi41nMTb7xhJlPqvKtAuysWTE7xc36EW1NFANzatMXrtrW2Pt8vcmLivTtlik7y3u0qlN0lZQ/lHytrgIx9AjahZE1zUChV/ieT2MTM3doBgY0E9+mE209nN3RmltjvtNHZ+ZTV4GT3Xoyn6aZ7TCfuUMzMJ+Z+HvHfKShNG8c6zs20fOEXEHNeAOsO+8B7UB6qI7Bt791m4YyG8+qZ83SIHU739n+lNSpnkiditRARbdq1klRfbn5B1pWtAg9EkZYMsNvkmV5xbD89WuJTtKC8kf5+8uqqGVmpqgXqOJzwJUvXzTfOb5qU+5JPCpUTx5Pkgrd5QVGIJa+/MmIi/yij3EhNG2REcXHfJmk+J9o1c4g3CkRyty1j0l2V5PTMoitLbPrHFpUBXtxXDLmU3ZPgDMiv0DFGpcl7rKDQ4hFGCNS3pEAQjj25DD1ME9TsFfku1Xl5pG1CkX31t1ySLrhGJG0nj8gTXpnKVNH6JlWsWqaMosV04ZA+oSq+xyc8jGEGSGRIoisigciWsGejQCsic3EVVbpaN6CGFEep/kdTXkJ4erKzNzUXC+ArNTd8xhQdCmVIBxDFjUTfXSi01puXuBlVRcgjrbIiIK3CEJ4xiZXO+rqujWCMPcuyerbhMGC65Yvt544GpUNF2it4EsAzIwapfgBFU9XeA0Omy+3peIQaEpDSD58lyva/rn+GU4rhXg11LFtVMvuYxgLrm/gTbwBgNmiXmzQEt/iJVOs+iFrGoErmIUuWO0LuzwFZbAG3rMx+2ZsiXeVgIrcUGqL6FtS4CQbokqc5OsbnCUq4phrGRv+W52wL18zDIoGvtx3FA5dt23CBrczUrEzLE6IQ+fbWDAmwdleT4NCs7+6Nh1FMCrImRDWW/pCI5rP6GepErddmRHJ6SMRi1mSHtXVPT0ANotIrZnr4I2N9WnAIcxcbnYbNMrjIJ+G3YwoUWGw68X7Iq83yl6wUiMiFvwDwtFGUrFUE7j0MPC+BWc5NWAHks/A2OH8QZbYdQjt6KdaCFxKNxs0NL2JEk2TZMaARk5rANMLTH9gcYpSqYFDPHdLJCqKjSPPG0fLxQ2GhzkK62zs2zg4H9w/MYii6ewihnsCzzBydC5XtModRxV3zEhbKMqKipsUUXQc27DBbTY6DFELQLsDH0zCHhWzFOrRNWFOjMYtsFc2vZEeLysl/9jf06jB8M8EYZ1l/CTTRLXusRhIr+H1nWk1cpcm4EocXSUZZzJVaxXcOy4Dmq+FBk1faN54UIaIkqUVCSKMWa+7J6dvplfewFbIEYCFiSuHjRLtcw2jSYu9H1CYbPonGTad+JQwiavsS1tuNonBfreghUzlO+1ggblC602qWAUQnK0V7EmHWISxPoZqcAogFlKdgq00xsMxhHngRV6WpGKqQSnCmAfebCdqk/AIwOIwDLi7wKdi0G3JHr0BqfnMxfmKgWeskcOekSfXmwTfgYuoK7OwqrKNRbtgxiBWp9OPqCKnRdNShyEt+pyUZPV+R/juHiTjCMAW3TtMuKFU9FSEMSIdecBAGAWPI5MwfsqWOlk8FFuc+8dXzfBZX3QMsEKqGa7gAiCg4+6Y9XeR18NNJldHcyk5VBCvxPmwUZp6vd9EP2TlLal0627bROcywTcx3OaKTg9jYtkSkr9LNlMRhjaMD6ykpJ94BW6iHox9YtKJ19HXwnWzTD5DDfUg9RUhMgL36RoiGvADhBTGGk7MPE21rDcG0AyFhwMp09zj66gxQjE1Fw1B2xtPAnjoPIi+5xykavi9J4KBCDwqgAwgJT1IML7kvyYykMjCJVFEOR8DmSoWtdSjM1eGaCf5K2joZ9ECtKyGEYeTlfd1uO59MgBKgoCaoUHwIL0EdwwduUCcE1OoC5qjoY0Ioh6BAAnRg/O70ZBCRDQTEQTXGDUpxoDmcYynNpgsY3QAcfRc50Gh/kU711GZPACUejAyMEQYztlLQxgAF0AVJZFDCHKM05TLqaejigBqMZxxjQj0ERDORKTWM2wIkKGhkgRrHpVYRUenwY/NglwSYo0WGINenfebVguUgdQDkWAh0nD+eBqayKgm1gppgyf5mrl7D26BMD0kOP04RPBghgGRAvRRktpHa25c78avwyHVGYLTKL0AOKhE976BOg0aQgbQRXBeDKfNyDvxuvdSBKgDQ2qGBFYAKSQ6UeoIBWOFThwKdBGo1JmnzVSCQsJoxyPEgolEJSHWi4w1yibW3lvouEiC0ewtAigoezgvS902JaKall06N5nro80mxWh1lfP9lImihVePSlcNIhbnxq2hlRYrtK0rp8CDcvydmo6NQDj1mCBwiEKCg62GSiDGublK6K4NY42ruHDBqGZM9hojhvY83axEIeQgBG1o2EO7jLKrFJNyPc5p5XrfMPycQEMysILFCKF6EQgHogVoB48zlhZxPX4aodQhxnDGvX4ErtEfrBSHMTKIROPnH7Foo3x2wWeeV5PMqrpx8DZYNATWPTcxU96qcfV6Hz2hiT8rbSfMCrCyigMBxjWtKO5IYqGFacWo3LoTGX0IOCXpZiVar8APT6aUNJNhjcOSqmgoZnnkUGKeWoUWGzYzmQRqPyozg3mSa1buGinz49dzSqKp6xgHqayqIaP9ycTYyAwaNtQHMx+C4G4DNfNjGCFnZUv4UaIFIQ0VjUPW19cQV/ng0kxmQ5vTElz3yvRa9TYUuBv3QKO55XbHBt6r61/c6q7ZPToAXbHaMUX0daJ/Ouy6eLTYfLlbT+EZl5m2I9ZzqmCqgKNy57bOHlirT8o8mPlsy0EHn5N5XGoZ1fQ/FlU0771tbQvWKIwUsMWkobT+1bt5Eqy7MJ8dQ9cl8/5tUzuIKuadfMo5mVWJ0nVkHNzAazpGKIKowmPa0sSMugDMjhAsYqop8lgh/svCYy140Xm7XN+1rlVfret6D16rqE5Bba1iKsJMSH/2XPtaUegwF3o8zmTSonOYo+7duvtE6XugnjQjlwQonZbbgqPstxXwdnrn3KK6i0xjdKKCATWXcxA8eNc3xM3R3fWB6OZzpLpeJOtNioZAPmr2ESDNcz6uEMxCAjpIOVaR3IM+fVCicQJXgD4KSPWA4AoQffiwSRoKKRDO8LJpaFmjA8lANkPR6DnOZJlVl7lEDxj9tFAKBUDjChjDB7u2wlhnJNEHlG5u6zSj/uqjAiPN1DUth6tEEJeq6mY0a1PVjAe5u5hoWrdzGUg9OgkWohcXpU1DKBnVxG7ntMHOlXsIFgfTQ4LTj0MED+YiEClAHyWpQx6+GJ/6KiDVw4ErRHj1MvtTXl0wQO0rGLuK6iFb1Ycoagh3qKGyXZMTv5zpYyRqqQtAqcclA0N04yM3aogEIJuDIizao5kkAphhGGNoJVG6OJQmqgjo5iDLNR+EUkETHsYwAg5UQY3ETAYeCUCDUVDNmMzRxvTUckYDYzWPzVji8ESDCyaGRNEgocGHRtWJDQ7OZq1zY4khOjh0Kg6BCexBGxbX0+JxKwinHgwEDtGmC/2qoQuIauIHrU2bOnkqQJi6r5Oi1jSYS3ZCcXKvh0wJao95uILG3KWrp/OYF4PxWrjPw21o3Ocno2Qb1diSjA204/gafpmQgE0DAPVgZvZdguygo6SXCGJYPwOkchWaz6kipnlIoLEeyUA2nddYjZwJMY+tqIuGfX1BenyflGj1HVf3XHRrmTSmKurBGWpCZOMieWuoZkKsYqdYp3ooSPn1EGhcTUO4gnmgYD0d/Rwkk74NgJTKOYpFya/8hFqTc1zLcbyjylMSdtzQ5HKPxpo3iH4OxCCnBkilxGsD35skHodpShIAwfG16qgWXj0kXTWIUorY/hqiaVuYWI+F2jauUHMlt8F6LhtHvL5z5RnCjlmilSQUINTjGgOqwtCBD1h1eKZcmHxyiOsh5YSaCGNA8xhG8DqSmFU0GCXkD62isvfRuQsJaLQoiICmM7AAH3SeFnFNbFQYZQIxSHMQUicgoAqwVBjnK9FKHRDpxEJ76LyZhZSwFndFFozkePs0MzuJaWAMb8x14DoRoqwFyyYpfY1WPqmRz/VGXOqDbl2qgR0GqVudgfSba41KDWvsE0pYh3FprBWBFJvJzyVP0295xYKes+tSPgsy4NyiAde4mqhrhbuxaHAroiUrAi/77Ahg9qhrIGEVsEPY1tXId0sUIJFVKbJ0+4lte9AiBzJ9RdBbxumvrs8zXOEk1RyhdBV0CoemHqzMSMm7tPqMDv20p3kwOdm1nFjMTExlXfuBq1DYkNj21G/ZIkB146T6BH4eUqRdS+nSZJrrwNWD1tQCQ0GPksRpiKnDC+mYUsa4qOTT6UcgnN3AdFqRF6Xm0oVMNLGkhYkGxrHPOWYpw5/Z9QiGNnhDgJWULhZCmiWTqwWMfGLKjfMYXp8V+VpHOh24RltT14KfXQjZF7X+zGrU85LuKncgHAdsPbahTmSicYgnJlmf6fJaY0SRgTQCVoQFxTWXfFMnrCVcExtM+hybxjc8CkjdxgNVgPey1MbZVoFwBpdm+qiMPpHB66R4On1c3ifZHbok0zRkzAQO+cZKmjO5qS6cbiU3BZE24wWpOeQMjUtK9oc1DcfQloMcVYpBtTHCqcmlyHiq1S2MddQjNVUFUygo87dqSGpsaOpsE4r2NUY7UxX3wWpMeFGJOo9BD8ygen2G4e1EA60eoLoSRENV7lcNBTUNTPyYFm7Z9DTbopbrYE2PtqNRde7n3FwvrsfJdLV0HcNaDXJUxUBDa8qNkQL0EhIIT8SRo5TGtgzJV3JlE54EU7Ij3w4YfUE9ZR7EHaX4vV4s79GqTtFVUv6AqKqBVg9TXQmio5iUWEM/DWLIj4Irj/O80IVyGmj1ANWV4NeD1pTTIJ6NckPm5uuLLuWyim4ArGlwchU1zUaJpo1kAzBDUlA7Gxqq/fa6wULv/hKcoaIv++01nYl10n747TUBWaJNVSfpp3yF0rIr6LJwDzXbLy8Wm2RJr7H+x+Lli8d1mpW/v7yvqs1fX78uGery1Rovi7zMb6tXy3z9Olnlr9/+8su/v37z5vW6wfF6OTJC/Cb0tm+p2TCFUpoycoXOcFFW75IquUlKMh8nq7UEtiBHnOrLzT/QsmKXoI/ClvdbT+quwTbKRfP2Sp5DCk1N7h04/d2eBGlT7DT1ivbpFfi0bKDhGRkWnXw2QsTNuKIeqblYJmlSXLSJoTvJuyIjz9N6nQ1/i7ynrr14Kiu0pr/HWPjv9tjOs2VarxDR2TGpnWyEnkmlDpjLi6Qsf+YFzYtdIZp4VkAOAdjj7yqPkQ5f7TFd4SoViNl+ssdxnK+exiiaL/YYPqEq+Q/09LMxQvGYxiVuGN+h/hAmIx0VuuEFaMZ9tsf1Ea8Ja62u8s4IwmOUCu3xXqJshYqj8jteMfnMoxXL7LE2Nf6eZ8LQ+e+u2L4Xyab19oCQjopdcS/u85/ATEmFrniPc3oHLy5oscxhLRc4L4g0FdZy/9VxLV8ld8ByZl9lTL+9FsS7uIO8lrYQYUMXNyS77Sp51OZjc9i1ekySSdJm79LVnmYHk/cu113rHS43afLUurrwmMYlOzPb5AN10wqb6BaJxyQra+7qBDPnqjGK9pODokSJIA6k/7gzrPExJ53H/0Srtvuh4kDE5yMULHBMwzlNH0Qcw1cHxaINSiXi4r87YKMEQUQJa6OWjDAKZR5YFQjdcQHrZlSwO1zfxwwL4nVFMDQbFldW3VWZ2PX4pE6bDKYQW/eF9ni/ZviPGi1QTg/oY6xCkT3OszS5O1+T/tCLC3noQLGDal+lgj5PP2z/yHFR36S4vBe1Yu7zM1ZwGimzqArmjF4ye1uEfUzA6LuVGdFMs+bj7kFd7+XlNC5xxwjsGkKRi9mHep9dpPUdFiwO4xIXjFd5vZTWFfd5Z1bBBSrWuCzxELovZAWI2Dy434xiV3e7uGbOIcMjj2v4ujMcZArWac89Oh85C87RV99VrjkrEOoefAoqx6jEwaCUPJ4+ovVGMM5xn51wtdt388RBQDgqs8fK3OsFbN03FynbrKzG+XIsaPmSqVfwtiR3nqaB0ppg8JHQYLV90Ediyfj2agNikr5oO1o4tZB/yT4QMXjBvJ9GHRTKHNZrmuY/37OH/lf5t7wSl65cPMe5QW1FI2xOGBx9rZaiKY0vcbHxrEB8/Pf5TnNblDfdW9xQqQO/QbaUParK00gg2qKIofs2p+T5XK9vUPHl9lsTXmqEalz0jM/s0lvzUEbk36J7sqMexXRM2SwDiDWHkvgTZ6AxOeKS319u/5tKt29n7r8H6Pfd3fJMtO6aFbHw3x2U1k3/gmrUpeGziwJ8tNkU+YNsZxi+O4yzQGQvW33JpG1uXOJgpt2sFBjHJbNzqcicx2l+16bF8OBLbe2JeLJp7oq6mo2nii9w8AUiQ6ARw8Ve8d+3PkvajDw2wlpffyJJ3TQqienh87xeX83oZcbhvztgSyrJbNF9s8fSpjP6L0SOD1UiXJVIhc54P+dqtH3ZbnE3l+AplNG1qCbl+aZ9BecPhQ6eXEnZjkbw4uK+b30euRRLHlOnrT3tXiILl3HJ9nanLhuVOE7++84dUeKYwgPU5Ln14/c1VmjITYmD3ljS/FDigXn46mC4aZ5cjWw2zadteG13dc7yYp3IKoFU6o55kaQVjLUpcTD5rdY46yTR2No3KnG6FIUvJkYFDj3swj6IhBwVzH0p8Q6lSHo30H90v9zo31tC9xt94bYuKT8m5GwAn2iFom2eQ2lXPuZ3OAONuHKpG+YuVpQSuQSwM3vVEBQlZK9SRHux2KqUNafZqYgMrKtEfljCf5/3NMYel8mLkfvsxosyquHrvLsm2SA2SSZ6L3QfXfAQAVdI/rXcZ6eLoQqRbw84WwJu1kKhQx+ldyAnjm9A2oXwRtxpu6/OmN6CmN66YPo73lDTT5LKTpZCkYOecp9nqLmuENQUvsBh/SSPEDbu8xz7zrZOGmwNhDrftyvJ56ChqjmN9JZlm6tcY/fUZZsAELjCHopcccIOPGKZw97yM/+IqgoV5yXg4yyXOmC+LxDS4QbKnS4pUYGXIGaxzEFu1+xt9VX+LREU4XHJvjk+PzMHgY7RP6HqPg90JB3j8nk/ZkCwqzJK+frZ8+VzfO48v8toMLd7GkZCvMUcF+0OZ/K6XSBj8qh8+FJffyoTbzwlOsI2fHNToAcMHMTGJfsmzrfE3N0dbRhfd1g877/hqtNwc1yf0N0IpcFUxdaoWgJa5FDkgLN1Amnrnsh2RBjCQRbklbkRJZCLF/qdQJTmy/PxCT2EUon5rnHvjvIXXUTRCB4E/g4DM/sH0JEovAO6IpcbsIKMjL1nZzEAQKcbBYx9K99wiW9SdJ6t8ANe1UmaCnIfBJj1gcI9i/qmWPdyqYOtrU5TJWKpcJs3jR0ToTXR1+T7QaB42w8sujpq5ROGOGwwBt5iulP3LpC5HIHalQjhrmQ1Xm+Leg1rWFyxl3qlQA9DuPeeOcHB9AEhvMagbkQJ5GEjPFoKV1fjku0rJ4sftdBB+sFhfSRZfZssacyMguxoFXQBooKxb+V9JT6Gb764eDB02d9F54Xhu0N/2jqQ0iCWuXjD/lHjAn2p7lHR62CCXywE4dzCoG7A+EflDuu3JnKLLPwlVTSOVisBm7iWjdAus9sFchdnd/juYHRp64gzy3938B/L0mZ5crHmR55kQLnL+nvsHlwp8MMQ7tQ4fdzgghnD3iVPJUwZEca9FeaewjBAa0sN5aDdJOUiIcoWglkGKHbx6eBrSpf6UqlTr6nL4dFdgZCsm8qlbv6NfUXZSxYodlmXfdpDcWFyBS7yq6108rRM0UeU3VX3ogSDIHxbuEAFzqV5VMF4tMIUDIZGksQQhJPH3j3enGYJOf9JQnFU5IJTHR5CLHPyEcF0JSdpV7u5spF8RhRQ2/P5PC9PS4m27JOLMbEPHyqymVDkpJNRw3P2QFYsqdxcOorYlUAuZsx8+eNvdcJMN6Idc1TkfOHB6h89JDhNbnAqoVdD+bUEDwKGcJgHnGmwy6UOp4H8ZzP21plTunwAyp1OSfj2idk7zvKi698xImdT6aSkBnS4sEiWP1jQYxqLX3r0JxY6nrfVaZikg7d9xiZ1m8wUQqYWr+s1PO8whGsLyaOpBRHCkW5o1VbF4tYEFDutOrrvHNdPx3VVSW4FUqkz5u+4vE9xWWnQiyAOlGlkTYoIu1+Qc5hsGYMhHC4LyGmIVcVL8f3TqMTF/iihcsbxJV0BaIavztbQE3pDC9lBmwKHPWiDljhJgd6NS/ww9tdxV3gNXNZpIf1abC/sjO2JcA4c1poXT7MKFSXEaBCA065HBc8IC4LYRwvodAK2bE8H6HQSu8KoWYeCZBSKnPZzVFZHVVXgm7pCJ/n6BmfsfAuMwwjsNBYiE5t8Y0ebTYrFswIIYI//O8J392JahvabA3WAc577ye47XolI2k8O9ALG88F5PP0eoRcvGjCPtnSCRQm0Tc++qG5Ukd7B7VJUEs1Y8QMqnugkSvYzocwea6dHfs2wdJMtlrlo/iVqtzPAUCGXemD+hJKyLhDtmwL7CMKjhaO17GYkFXrgpT+0uHkAB/x1tkoRu7yUrX1SoSveC1TQR/CwkUgB4tkGpYG+iR7CexR5Y2oi8lc7Eh5sZ9x4+o07yI+nw+LhyKOuOo0nD/030N+97TF49yCUOVnYCacsCYkkJw2hyL2nKsRQuTt2SPyIZc/H7bG9gSxP8nqTZ/KbIajcye0dxOqHrZsFdvXPlH8Vv44hHD0Omjs2oi1AHgd84c5IvUhJwQIygu1fOjDqLydjGb66KJ9xHwwd5xU5rCuxAsUuitbqDlKBhs+OuBbVk+gnxn93McDiRDK6sk8uhsSGC1V+h1D5wYFPj6tx2VXd18mlDpgxc4+SUA6fXXpJWA7/U7T59l89HRbLq3xBTszLCsZvgnXv/xfookYqdDSEXyaZ9FBwVLB159uJTXL745q4iwag+Gau/TAp3SZ1Wn3D6OcnSX+VCndGFWylZ+A7lgaJzzsWVc1p1MC2uWOcJWKeHqHI5S5njeRUK8PXfbS+L1BOs+Zlkr47KnDxAvqMBL+I9pOTZ1KRZCWWHPtGBdsUAZ/QCieUw4GnrmLZzggAvmNhUoDH5CEK9NWnkQe034KkZl92ZnbaG/84UnqEy//R4dwyO2I87phHiL0LO8A/MQlc6Rwmn5Wurb6rBqDYZhueCioDhArmYITQ4zqYCvbPVHA4Ij/3I3Iss82W74rbK64mukuMa2MOYcANshbLROpz27TqWs0PGzeQyzpFqvjXFuBO7qXwTe6owOGuo4lLqQjjJZe6WFTbpz0waqDY5XK3rIjwZoKWz80rP+5Sw/m0dqF8agtBeLWQPFEG6QIdqloRoHxa6iYA3G41YH6zxISBbnp6AAeHgceqSOSTMfd5d0Qy56sXKIs5TD5CWFt9V480pG5efECP35K0ljwuRkXOqs3HnBTKEpsvesZH7VZcNR5w1P0tiuVmQOdvvNHh2H37TetSCGOUCt09d2GfXR8lBtZVdsd7LVx4TBi3CqcVKvrHG8IeKpc6nEDwCl3d1+ubTMqXIBTZ42wDiI2x9R+3dE591ufLXRHqPQ82LBlZxgvYY4h8I8ppdoChA7DIVmeEU1rSuoFImaT4Ag98TN1RIu1LXW4tLwrUGO/kGBOjol3j80hOomNsPr6iRgy7r7eo3OH8HOHOyzMicushDpTIV1Lx81e1411uAQj9mXYbV10xGZfvv+5y6sDAoQzMHq7f4iV7MMBptxFYGUbtz9S2+HafveGRNDY5ybJqgHW8B2xUMOltnljm6mrb+WgovG1Hxc94UekmKzCdiAazT3YRJ3TTrCi4D3b8P39GsPBnH8+Em6fg42gcvD9G8O1ylNIGlixRtciLStohxiU7w6PtzsLSfETQWQgefwUFrLxt/tvuvHxLCpxkYEScKPOlwe8/j05IJ9obA2N/h0flnid6eIwIaM8tBtG+Ri85Kkt8l5HTc+fuKG5vQLmTD9zexM45L1l0T4Gxh6/bOXUOGtx/roVbKKHIQU5NEGeWnUW/1NWXW4aC6TBQnEsZZGd2P551wvY5HpPHjqavvm3dRL3WJ8ltuiPOIWqp0b7XFMVG/3ln+FuhKE2i0sXT4/bRVNhWUppHoHJ77DSWBvkkRV3nv7swcJcoROTg4bvHdnWSZ1WRp1DgJxXMMzbKiBMeZc1FWGyzr7IYBpTtbVW7JbiZh8Ek0rtx5IwmwhXoJpXjYh8UIhgC2zduZu63iz/qpEDl5d0N4JvLFzoe849W/6jLSs6QIxU6HNrZ4VqFWC6dx4FmPpWRLQdoYxwVuDyGy34os6JKhVM6um7Ls79PwxUiC/v8VO5yT111GhnXtfe+xqJRZVTi4GBZ9nm0vhaCnUEsc++nhDIQHxSTRSxzkZ9ZhZo4pKLs5AqcXJKBaCg+oVBOH0n7paSlcJ9dZOMhrMqOhlXhchyECLAejYcE09Td/eM2+LDN4zGb7hmj/9PFeO9FXO5dtsTKRKCU4W+YOiwejKyuOg0ff0yyuxoQNvx3B9OOHNPCOZ4Fe00DPLBx2sLqVNixmy/beaGy63Gum/gbTSZ7MAF5X+SAs82Y3dY9kS/+YAiHg+eQllvdiBLI5XgyTayO+K+Mnnf8my3tCFRZLbIkPaqre9Jo6/t1iZaMliG7hA6zx87hhm6a3aTTMlTah+Pj/LX0crH95HYEoVQ5X1Ga3GLRCAKVu2NvTSmmRgAw+7a+0Im9yn8gYRny3x2xHS2Jzl6qcI5KnTTjB7xChSqWCVS+M6v9LC/q9UVeBl6g92g81rGm7jSL9irf4KWIov+4rcUvB+J3TjZ+cbRakU1Z2Au5z9vcrPfuSTDjS8YWEdYGw+O7OBSVp1kdrEURRf9xa6uDkgCywY8KHI4oTax34XTSfXRQyjvpOdbC+68OtwyYnISF+4Xmk8vhtqxow/Lhdvjujk01k1C5O3YWVQfE25QcZJabzErChZWvnJpVRL0v8noDyqm+ZB9cFDSCpd17RMnSfd6GkKJLE1SfRgUHgWXJM4dILhOpbUwERFDbGB5fcaioPI1M3D0J9ry5eyrr6ZZWTevcH7Jgmul0XyuKetMsE9YY9MpqVOCIT3br4D5v71o0zsmrjWfb2hCkIE9SqcvFVxMmVYEaKHacl0WVVLWEVyhy7y+MVi51MCE2sWlhxFKhM94m5q3SPqkCcue4k7ooULZ8ApK8ghAuLTT1LolQFjHzJe59vkoe2/0IMi+ooVz8B8F3ndxnV76ub6q8StLzbJmSjkHsLUJ4tnD6aGqhh3Bv4YrW78OJ68YCQwa2qB0bDOnaYisSNGMTITxb0IxFhPBsgdSV1x4M4SmfiJzHVMdM0jOEQJJZgMdoGySmBXiMtkEyW4A7WFKbKoJmOnx15A+Y63w4DY6uKxS59o4uY6h/zXd7bJfots5WaAW96BbLXLD+TIrVRY6zqvyOCkTmVnTAUYA47HP3aPkjr4cnE8pznh4yoEX5KbQCxH33VrlzQeUOzjy3tzjFQM6mUYGHRr5RaOQbZ/clei4gQpccfIk4OCEsAqkYekiH/pNagLt1/9UNk6yCDl8dMQFj9hvhp6T8gVZ6aqpg3Pp88vDwVu5x89UN0+njBhfM6eVTnolhNUAAX/z/hRKAymK5Hwe/wwVaVu/QDRb95VRALiaovtrRku0gH/IUMEepoEJagjhIDeXV0nGS/ZCPWiCAN355sYIAfvjPT9SoaZkX1jY7jhJzX+6F/fwmEc2jYqH7vsB0ktZfE94hxhAOK60mOmOB/8mWKXvzkSyhAJg6uPDWZCbVQ4a3eIlKKcaDCdZFOm5ocB0NPWGIkBagEamhnDwhei1PMyANmIuTebG8T0qktMKCAC7nKgz7iY8K3C1+zckLtvd1Ze5Y6YGLLOlNXTV7tNZKZ13J5cInTsLDfbiW4Y9Rl2id4EyKvagAsW/jQ0Lf57WH9c951QfOHLejAXOQe8sl2lRX95j0OCGfmZ/whyRbfXmQDgF60J25guoO+V9Lcl77gMsqPD8BgNInSYEdmmkurOKmYWRyFbwQcdyevJf8lpjrPb5lZ7aIzAWg9GEuOzTTMFfXtoiF/+4gtUu0+o6re5DJpEI3vEAWO+7zvwDjxuHVAP6c7aFxqwJy3AIkF1JDuXM/dO8nljnszICF2N0yfF52PWDhLBMgPgoA4D52chjeQOczqNxF21riDQ0uIOuxQpEHTuCVlFjmoIujjJ40ZHWb++6KDejgqMDBKonKUkr20H904aaB7EzhhILHSQDPWKr2EiOCy5Pnw3dN3Qldn2iDCneloWh+ffIiYnSJePEbaDxXFrABvI2WSz0wg3fNcqkLJVX99e2rup++fQQvuT2usrszULO9goNWgPi2AZJBAeKgMhivZkOvZKeISt0FcAKiEAhFLhtVV1Wp9gAALt65S5QN4bHk4GtSsUPfifz8DsSm5787uF2y7Jlypnb+u7N8PaGvbCEJ2xTs1vZMFJHAE3qPxnd7hutOuT2Hb6Wsz+OHALmzWGWOjnl3+QR67/PFz1hT5K3CcexGMkYfs5EVlmk4Na5/eTMEQSq131yxHJOSTAq5NS5ysztBp3n++/xnub1bQfTmqgmnFLJqOiw+ScyUVXdbkl8VyfIHGRh0USqWOWClro+QnjIqcLzNRPC1q1jmdDfJks2AaKXCf4HVE26s4DEFrKI5TRZ9m8Dde/fdwwACr01n+/HexFu+RFVdZDQ9BAoN9DNC5aW2aOtPxEaRHtbFZqC4ilQ8u9clSso8O8uLZrZEW7tQ6IKXTTtiZ3fRxCEVeuNVe49oAd3nDQ4hKZe6cGpye9scfAVmHb47WH1Wa5yBboXjEhdKc8sXfq6oANlPd6Ktbfo5+/OECN8YG/8Ym9fmb0Kx25L7Iila1UV+XcuXuNrnIIzjEhdlZ6AxdCUNlW/t0L7jGRO7UREWRgVNpyEF4oYhtrHbHuQd8BwsSOBJ6DwkngWOaUQe/Vew3yZuXgi7nG+OWQSSmxS1ogLGrYZy6fdV8nj6iCQyjAqcLi1PyNq5y4snKQbhuMhD9O1o9j01bWOl3t3TcDmSdIiQ2w3GGUN0zZrPTWpd0jgggDnl4yFPWphU2JoVa1kX9DFo+3Qi1m0chNXvRs4S0zTrTmxePpfL5YeHA85cF5fbIrDZbHL9aZmijyi7k16a8wWO+C5QgXPJDUcocrzbYrXF8CZ8gZM5LnIim3iaU6zXe+cZrnCSggtcLHvG65xNACn4mN+FLXEOkcfq1taeZmFzTYLnD7l4W1Yn+IW8uxV7n5mTYo7GoCx6SxCTwhgmMgazV85tWidBHI+L5jxNnGbUUCH0p/+4MywULNf85NmMcow09RE9oFRy6uW+O1njiwr02RqX2GOkGflAhKMCh417A6d72fike4l7O0BGIqUQ7j+6HGluUVGgQsI1KtimpZ3w1p14fO6+2WP5UFUbKMQE/93JbRF40DZ83RmRxIKk83FFIgRs59H5xm3X45hob+PalN9nyaXbWtqxsgNFy8G1b6rcRcFerbTyPozjx7g82N2EYLcv9c+KfK3ibrHMhTNVOMclTms7Sq6pCBniykuUAPd4iePNWmtmOH5qQmpJT7TEYi/c/YtkJXoO4hlLjD7maOCxr0Pjc+BT153IJgGnBPBKBRDLUAYZ7tRmu215ji3v0apO0VVS/gj0GuMw+XiMaatPwzXhh/kjwuVSRnD2yUXA5Nnp44YyqhxiWShzEP5S+F3X0LuuhgrN9r35kp0WhSj4RwUOs0Y2sctaFsf8d4cTWcK8TotKwjcuccN4mq1AfN13x/7VLNMw3EOuzLGP8oxwn1224A94tRIzJA9fnbwE7yirX6BiCVy0C4XueEFrilToYG/If35Dhbxq+e87I+mPlmmMpOw9Gi87sLLuNAK+aVvEMXx1xSRvGPx39zP2ZZ4qI9F3ZS4L8XyVSjeFzbedYcOvRRQ27NF4sKGm7r8WGy7SWohp2nzZimOfIjGDPiHDtoLsoQwVeBnJ/VjE5hN0z4hi1zn7P9BTk/9yhGn46oRJQuJSHwgd6Rw20tVktSU+vrpHa/QtKTBV6sOYeITKg4MN9adhX9aocEpqPs15iPwXYrjWMTvIzkB/+hgY4Hq7almQ7jAd7y4XZQoe4PnvDtioE6F8R8t9dri9lFOLf3DOK57f5Rd4SZMZANf3fNE2nz18qNbpcb6S9kf+u4s3U1YRxu6CWXxG1c+8+CE6N8EwTs7uZME9sdXSpcqUn+3BMM6tnD4u74l+h1iSAn1jKtCdEW1tp0L9y7ux+bzjUFbdVSGny//ql/dVjhPpESSSLvaPOSmUMhmNilzP+2d5sU6qCouJJOTS+d5DKRdofZPi8l7cPbjP2xSsu/QEVjnqnKYEOc1WZEaFeRGKdkaEMTZpP0VQ0lowX11NWX3Xj7KRjTT7odkfJ8sf5xnp1/JHPBctBVIPjrLGNA1zRYtLETGOQmRf+31zk/iUZPVtws4JxRVak30o1I4HYfRgVTs0u6rSfcM0V5X45mv4OofasK0TQPumPw43idh8DgRGFAcu2jkuamV5HCYSkHm5fBowHFho51joEtGpWrVTF/oCmcfl9f5Yj2Aa/uk1ojcKTemNF7a3CmxOubj3jp9OirwsFyhNo3CUiM1nYzOieL5cta1tqYke0DwvCtyUeFQ+W5K+/kRTzzeqSrqrBHJ79adM6yIV7gx7/K1GNVqxhGVHVZUs78NjX4AoPdjFEs80bMM1LiISityMCckdomZomUWkQhcWF7UlV03pDMv+bN03h/1RSmLjmr4mXHv8hNdIdqEevjpgQiuctLMi0kYs28XlHG0Rhy3d+bZ4nBdYjNM5fHV7biY/MnPFILPy8NXlkZr4NM2tttyL7pvD1RzapE9iR/qPznjkLo0KHCyXJ4K98sSl9vFSMHeyD/M/9qP+BEJH2JdtXqotyJq+YhHXBK+R/rMbLqBr3GeHCxqmCyzBPOlimVMPV9R2m6Tpk9RJrmRnhDw/1DApz2PyEPP66hNd98lJd53T7bbHdVkSjQrcPKNkxyin3SsvxGcz7IvDRXiJikwa0PDVRZ0sSznyzvDV9SXaohTna/jsNL536Dap04pItRXhRpykpTRYCGRn1u1Jst4k+C7wbrXD4nWvoKq6q8bg57zL7mlI5Pa9XaSr1zEyn1tXE4ZdZW2ibzb7Z/PWXWQAoHgfl028h0bTKH9T+eH5K5UqjIMN7A1krgGKfXC/1eN+G4L7Vz3uX9W4tyTqPqOf5UdUVaiI5xoF4/QQfLaIJpJ/YOtyRCsd3LxKv1tYjxkP2/vhJ/gJJWVdoCa6feimz6Hy2vK19Xd1w58irOklvSyQ/Oaxk11u76612/l/h0mTZbBAFrH5M6QGxYEnnzlPnq83eUGzLt3i0Be5I1Qe3Giov6useJanKyigKf/d7UYTinLOf3d9GQDhG5e4msYiBGn6gYWn980Xh0ui5Id4RcS+uL7B+ZKJJx3+uz02InHOMEpX9C/hPCYUuXPDSZ7d4ru6AO7fFSAOM/pYFYl8Bc59dnkyShF0D2mEl6KjIpfrt7JOq/PsVrrJG7478F0T9o30gQZ+k9RXqXRnBPXiKVvGeSo1IPJ5KaWrPdHNSbSHUou8LpZIitfBfd7Woyv2jvqxktGNClxH+iEp76GhNt9dX+6cS5kths+uuBZVoXiW2pW4YjzO8xTC13x30SyzJXhOHhXsjFg4faRK0zu0SfMI6Z9EbD53q0YU00iJVm8EXqrdOpv+wpXCWGpS3M1vmBVIJZRL9/sWNzSiCc1ucFUkWbnGLBoqRDMVTFgr5jZclcjmUCw7XIplTlc1zQlHuqzpPrtekcD3Sf6XSawmeKM0Ltn2VQ7lbfyAPkkxKUYFTmtRcl3ovu3YvhXF7jBC5b1jHewOjLxEDFREaMtSYlToaqiTe+n3WIH8+YDJ6JTvIYBylzvfVhS2rCBc/QqFW7CZBJ9dNbNPJlbSyLnPTnNE5apkqOC/u894Y96QzRRQ+bbUqy+3tyUStprum+PNPnCf7+T/kFTL+wX+p8DD3GeHGSDLiYWyG9O9/7rt7fMkX29YrgSdFqEEcr1B/TveHBXLe+lGVi51wJyiJBMjkfYf59+yj8oyX2JmSJSf5aGiVUObkMbXfPhoQk/NKzxDTenVnQDPgwP6wkpmW01z140hBNAWrPZ6CTnEspTMfa+CO3yVFHcIOpBbdZjH5djZ316D/GDPMot7zP5sXileE9laFZhmgz+hi73xy9eYKy1qS0bKUZ0GUIpSB0yBua1AvhEaiMA1Fl0O5JyGenMzTcew15ys1gUxlKHlaIUNTJd90kxbGWvg9PcII0w80LmwiW7xbG2ij3FKldc+OajFbItVVFPuMtdjnIE0FZDFnHYBdRhjOneMqNsrTOfzxXn5uU7T31/eJmkpqpum0QczzztcMo3x+mizSTH1eG1DEBk2FX09kY066C68kQU76RoInKsedQRu0nYzcN9oiTW3PJGHxMc3c+QKoaqKMXgwL+YYtbPT/DHuaRiL8Li2zyYXXawZJw4ZaqmY4wIOGWNF7Q77TrNE38kwbmjRzM0IXbPt/1fJnf4UC4ErYpgNMDbnVRlx6CHVi6DWnYsy2wTT1hROa8uFqoZK3XS0VMDod/Go8VwsEpfoZ1KsLnKcVeUHTPpB1JSvJVp9x9V966CrC1BnrCyHpJOqWPCFsaHAGRjjisAn5g7v4inFRIZ4Aqez1bgccaU6Mc64AtJAPhKxxZQ4Iu5dZCDz+M0s1JnkqbtfgjNUiCC9zb/90v9ddh/asE7UWyAth3r0WnCdMIKUm2TJLHordIaLsqKcdpOUqAF5+aK7S+tuZ1sD7B/pSdokMe8AiOKOb1FZXeU/UPb7y7e/vHn78gXLQ0yfiaa3L188rtOs/OuSTWOSZXnFhv77y/uq2vz19euStVi+WuNlkZf5bfVqma9fJ6v8NcH16+s3b16j1fq1WL1Fa4Xll3/vsJTlahTMifMt6DzJ8w1ejnnqt/9AEjN0THKJbl+o+Om312LF3wCepG3//hJTkrLlzLLpsaClzQU7hUKsly9fULajN8U9673WoudvvptmsoekWN4nxX9bJ4//ncdXFXLKL6m32TKtV+g8W2CCNtl0SG/ofaRj187LzouLFFRoydKb+6MbXMIijPMKV2kcijUPzSMg+oSqpH3FVEZDOArmGAlnPNpJ79b9ueMSZURQHZXf8YpubQGYGgx/z7M4Y2zQfS+STZs7S9E3e1yL+/znaA78R3mcUyUocF32oes4MeeIgw2HnpvdKc5ffuulfvIIG3T3VvZDUv/li0/J40eU3VX3v7/80y+/OCMdv1u0nVLrWSDaUCW9JXm+M/DWYwba3JyGlWCnHXTP86PPI8tRhv+JelX6mczo8MyIa0RUTP96Tjabx99f/l+s0l9fnP/ntUSPa+qClNfZ6t9esLX01xdvXvzfzt3hs1NH79CffDrEctMNaZNF3o/Ts19pzwIFWd/TqTr5NlonvVe8/XJt+eiZrFKj3H3jMxctjU7qlF5RGeS6M/qvGf6jRguUNykndchd9cKzNLk7X5Oud47bWvR//sUV/2WVhmiIEdV8Lj+kP5KJVZxmTTfvGi9RCViw9nbdBWxHfc1efv7isf109JxE8eqQR1TAzksaTfsire9wFnDwOy+v8nqpZvuAY5HohvpMONXKIhZoYRsz3J//7Iy6P4+GIbaea/VF5bOa5+B5OSsQ6qz8IRvNVfJ4+ojWmyB7FEHSbljNQ1Bwv7IRIl0QuRCLbrMYGP8E4PFbUiFCLk/TZ8LwW9+CY0vWPnJXBGNrFPWQ2le/ZB9y+v7xLojPj9I0//m+RmVF9u9veRWEzE9rhWxCSUGvAhELbNHgofGPKkzn1Y3ep9kqEibvM4KTDDjKyp/i1fneSgI6IHcp0NTaEQnwuV7foOLLLV0bZQhTT3yk6z292iuc58FAfCJuNyYaagYx0vlm5Bpjd5SzOy8dbTZF/hC2EYwj5arlm535hwWZ80LmzKbPjD+bKMxNQzWzomGG8hbTxe46D32MGq2JzJnl2jjPcZGqfB5i4T3Li3VSuVwQqXEtkrSK3c+j1RpnJ/l6zd2bh/m3lFHOZEe3tzjFhMvDSBd+InuH2Cv3EQq1/PaQ0O2Jr0ueNNGhT9vlMBb6mJSV3T7zxh151B2C9vRjfoezWCo1wcc4jMheC5RWfQSe+LlpDhIClQJhM0LIE9OtOzIG+/5Yb8vgC+S93ZWJnK87/8yd8sRjPq5WZxPb1RMNGagceGGit9BJFsfjsH3q76H+dxWDFi9Bjy6oh3G29LBmCdVDenLCea8FEbRd6G9iInsbBdnf8eYiL6skhS6f/Uxt93mGmvN7nNWbPEbEFrAx25+2oDgjeyvWJ3GpYAbQst3zgy2pZZQ7mZ95k+fnvJzAE+PqvkDIHv+vrvjJEkEFXgq4vczATTjMq/xbEqTWb9EjYxtW5HH8nX+Vte/lSBz56UEsHjm/ywj1Tu7pG7hJOIRXTJ4Jg0ynKW5xb7q5KdADBk8yoXaBfXBTO07zO6oTPhMW3fq9uN1B1s7KYvOmzF5vag2oQY6X3X1Ki+uEtwb6SOHPeRUb5RDKI3Cn2c0bct37QN1mvXNPBUM7G9GdeI5j4gUUD2hvxWw7GtpC6LVRQbrAXpGwRxYUaxjGb7jEBJoIePyAV23eYn/emEQpXtyzZClxV9oZgY6NM/rNW8c4bf7zsKmO56LV4Yh1ujhI6ZHm0TkcE0Ub/YyigNAUndndol5H0j6i4OuQXREtMxUGG9i/WCh7e8/Rclce8yx+1NGXSDKEGCQ7TAWblO0vnD268L7CwHlywgbPy/f4tjpJiqDTZocjfGe/RH/UuEBfqntUXIyCLPqGdmD4BiXBeI3vLqxqOjkVXlKl4Wi1EpoM6v55+S7/maV5EmYMaHGETc3XLG2Wb4cuaGSfuiubL7cSPi8nzhbJ6eMGN3lN3yVPKoxWJsMWIfOCYAjDuftDUi4SmlwixqyOMXlcggr1Q25BycCob9zRXYEQr/X5jGuE6Ao9xnLeukTLuigCb4B6JCdPyxQ1YiNM3vH4LlCB88Bl2mNkmz9DG7SwztnNWZ+BKESWxXoaRYQsC7SWpB22xiLfn8LREq+TlMYgI79KFkzszV/IeZa+rSXbpEfXo3j+nZenZRAJuTA0YUxCVB1qtMweyBIjyJoLqcBjVZUvf/ytTlrTQIAkb05TDN/RQ4JJXZxyOAPM4WAfvXYvnEUb78f8ZzPW1t0ubBqI9o9vn9gJ/Cwvuv4dI3KgCkF7nCx/sHhPNGRgoOMqPdxRfOcNDcnhoj+WBqkUbPsiM4PX9TrGxDT4ksdY+NjA0apFhVF8VZSwOQU+rp+O66rKVa/GbZcMhf6Oy/sUl1U4wnYtp4jwJRHMI2uJl/2V6NsMFV4GWXFGCKJvHV/S1bQNtMeME3obN1Ubiw3Bk6TeA7G6OOHa6C9RrvA6xvUHj7u9VImEuTNSnWYVKspgXmyl1wgrmpiBWgk3a5vkvHCFUbN2g2Q+2TxRWR1VVYFv6gqd5OsbnLETz6TMSvrf5TYo29wGIaP4jvDd/XTLd3xMiY7+O15NiP3DtLTpd6XYMqdHHFfgxLpriONlMvELol15RAmOHD+g4onOqrvpZVw7xPDS6aVfMyzegFr0Y1w7pB/HSYna7TXYUNDj+oSSsi4Q7Z1WVXYPStY3cbTmPUdibxN9M/THuCkPi+Rxna1S1IS7BcxioVcBDfoLVJxXaB3DTDNCSMkQE9/iPm8sP0RqT+CI2e3uz8TzwuiP4BPyrKNRuF38a0mndUkGF3gj3+fRkrBFVxy6pvwlyB5HXGnvuMqTvN7kGf+cwOskL2GJ5UvbTRK7eGa7YBijDnjo1u1gV7d/GHQIM25ESn2WXBFbcUtk/5jjvCIaWHSsyeoOVAD8sS2qp8GJx8+0iZP4D+TbpRDH7evgXDVynoxzj3SBmbuMRySprmLIoYP63i3wP1Wc6+xpVl7lC3KiXVYiYo+oqx2KL+N7jVhBitip4jLJ7gy3CB78EdE/Mq6VbAe90PbCihLLZLTL1pjbpE6rbxj9/OT3Rt3eI78RXM9EPWtHc4yzZIgkTKh2wz747VtkAikeg1gKszJ7SPqxHdkDwQLlNER+ZlI8f/XxwvyMfoaIkPPyqkiyEouuVxY7cb8QrzkkQXlH9Es7uEs+7+s+oRVO2qSH7srKuPYEQX74Bp6JZKHpIM0CJcLbqGcmkMHHTDYKdVcxaJ1Mp8/vwqtl3sH/mfDLJPaV6EdZju6Hw3zcw/zhAG7o2/4dwA/n0+d2Pg0yhDjfWbaXM+vnE2y8G5mHp0VfM1QxEUl7WacocgLtxQZNfp/YxlODg/K4GuS61xcxkF2isiIylQk+PteQEqvdPWWH9EL5ANBrqjjEyRNlh+YJRWzkHYUVm1gg9naHCaLw6WNVJPxhbwqjG+8v9UxEmlFx/7O73n6Sp3nxAT2COehCkbe7dJOgMLI71sTnvnbtN/5D1HnomfDQlo0FrT+WbyeE6kFdifTubDmlM6DaN2mOCDOWN38TBJjBKWHz3kM/6DjzAa/Q1X29vsm4kNg+iNpwP9s/Wj2zo4+/SO4ZpOGXZyKhh/H5ysembpBw7Ekb+G6zQ8PUizBc5yURqo2pZ5KjcMtez8yZbsv7vcJlyCaSa18zLOVXeUbEXD3EVplf9Lpy4DO8ctgyF/IU9bjKHdX+V+NG9g72Fi8ZdXp5fuDLGHwJ07Yx3HgE59FiC1MH6I1Lo2UFPshpPAW7e/jwpxtTrgAdQZ/JAoCHGIv3dibvq9nX/NddM0A9a7n7HGcWMGMkS1Qt8qLiZGb8DfoquXvGXOHomOJKvG9JgZMMDAbxTIg6QdBTOOLoPIFUA11fpgzw4R8ywwa7d8QMd0fnyZ6079PL+aOyxHcZWYGd09MEobcOASGE03F4guItn+iGO4v/XMdJwBktZiA7w32pqy+3DCUb5BT5qHh+eCZ76BQX75o7szg34zt06w4t9fb91XSGfFG1eya8GCrguv97ulx/Spi/0CABr/lZDnrWI7bl3etYJgX6Bp584oLyBkeIbiK3h+xZ3Okjq4o8pZjC7sqUy3QGPnlrnBbXlfzclvAkkQ/imVTCvDlCxXTjiPY8JloxRG8pCNJKVRhTjm+PY+3yyqd5sfijTgpUXt7dxO5nc3Rd/YNME5+nIH5MTHYIn6EhT0+KSbaZKCxt3nLkvrOGwnfajzj7ESmfnPshz94Zussd8jzkajec9zXuG6oz/EeNMEN5i5EpBZKrf1vZZz35WgS5EQJowtJUt/hixmOgeihqgvTF8ucEYyB4ITt9JH0rY7maHsIo/GuHUehDij8T0bhtjxbt4yA7l0H1Qy4bt8FR7RhW8ZCxxIyIb83SRKCVz+jFynNKtM5eS8RBRQNaxsEU5VXCbsac/VdKR78bacohBeBfMu6Gtbim+myRJelRXd1TEdl4fF2iJSHYMxHh3absv50HivDTNff4K/j0QWfrnDtoRkTbmjciY/9Ceesq/4HirBCG7mi5RGUZDyn584Gc3oug+AjWi+4sL+r1Bc1/9TxW2FW+wUv35dVWC3vis+3FbRXX2u4sf3G0WpG9M35Y6n17a8nWB+OOZ7JA2IDcWbSttucLhM5juKm7jaWsV4s8PGE+N5I4KOMwDeMbhOBjUla0F4G3AS0WxZR7YmMhTAJPQvspgZ6T8Hlf5PXGUwK1daO/pgnP5xP58PW53XWC1nIMgULXHqRUHcTKJP6A+yme2LJ8JjJqL8TDc2O3iFY8a85tiPc8mJaNBX4q5GfZoPj0XgXbuxDb2vmhjf/Yno09otII9cNuGNvwht69kRCEXBKymgvSVi32xHNYMXC14SUjomoiVUYOXdpx50ldFChbPkHJ/DwRNwgviZCxvHz9i/eqvEoe210p/IT9LVG8e/QXZuTQXpG1kJ5ny5R0dTKfwlFjp4/zNHZFG+vD9c40wlGj84y0lQ3zjLBtbNaRkYYcFqt7YyMxRgQ/pntDkp4hNDVN1S1PTWB1y1NTu8UfJ/Qw45PJGTEkkKd9K0x0TNYE0dRrou2sJo4Gfol+JsXqIicbXPkdFYiwVpjryMk9Wv7I68HNO/apUWog2uPcTglQ+Ci5Opnc3uIUByc66bX+TZQxMh8aeoyhUdsLROTICZn/sZ7iNe0EC60dZyJol6IpptL4AlPZlD/QSkW64J6ePDy8jYbs9HGDC3Zy+pRnQ7yJiHj/CyVxxs7z5TtMhFv1DjE+9GdJDs3/39619caNK+m/MpjHxWKymcUBFoucBWzHnhjIxWN3Mth9aSjddFuIWuqV1Ek8v/5Q1I2X4lWkbsnLTNwsFquKn3gtVl3syHzwJkv2nvpKZO4RCBTzyyj94m0rxfH19onRfG+vfLNskkv4Znv7OfI0ITUjNFkUNP5/fr6JM17s5fHfdbr5yn8/2rHBBIOw9wY3WQP3qKBiBQwcjU5VPBf/xhEZe5Qa7zu7NZF/0e/OVd0C+T5OvYtiX5687U6x3s74sWnDstqr4I/wdK6PDIOcdYXOhzXfCw16u3CPjlGcyqPhGYWqiqoXW83m9n1WdjESB7l373boVG6eYixphH8m/qJvonT/4avNItc67dbHAm8a3sQYB6sJOD553i1S1b71ptowTy/3r9AYOn/Ej2QXsULotKrZd15fc1D/fSzQ/q+4fHKEEFd9sChMTibv5zhjgnUlAG3XSVQvO6XpkPEZchPZmnr4LVTg88PbohWVRPuLBsagaJnhPd/J407kHit7qh4/e1sCdhz9PUZ5QGm1YPclYc3On3jvUFFQEd4HR9pqe4Qs+gaeOY8w+HUf9kpGv04frw4yEy7X7qZ9K+/jgXkVZJM8Mg99j9k1FPzacgxtRtEk9DVru1+q5+vQJmNbC227YHeB/u8A2wA8gx9w3xYtKy8Lo7cY4mkftmhQxt5q1P8rbIxwkrnOMSmwdGi/ql54+sGo3bSP1yhriSgz4QRNrKiDg+noRVz0ujThc1880sem6zrlmdyduTbqIK9YwuEySqK0D+/jtHwrgrvr+NyE+b1loM8/tgNP27ZBgoNVN0TkbnYdX96EI/kmj3Zf4vTg8XKRONOFXZSQG0Lk6wqzTdjhid0Ys1D7AazoFKNTyenhSF3Tw/2UeG4c/PTAYu1RnvO0imyPVhMgxcM7LE8dN/n6x8e50z2Kiiy9yfIaK34W6Q3iENn/Guz3XZia+jZYPWDiAswNzPcTPT5WGx8/7C72xziVuJjxAc2sw9owA4WP52ZzcWOxmCAz4kN0FeWrmiSHj5Z3Ud4sHAadPNXnQm7XrHTdIVerdCcPv16dfPyf+Noj3CsM/HUiklrdX9DdMLPn4oY54QXNSsY56OrcMuMssDGdY44pspWt6JuP2AfP22ITfb/+jihNXdhgJle4Ow9Z/jzQcWW8PFrWlhqe2TJ8ZBDhK19TLiZBOYf5V2QRNiKXy+jzM1uR34/Z4tRkd86rx2iNb/q6bm147ey/HpGDdzfvWd7jsVqvBA5Xz7sEsQnPnXqgYnOH8jiT+yeYrSGqywDCbdDFnmkWg9EmfejdkK0YaVzGUeJ4+cPWnv3rCGJx/NPb7LCSL43SCFjPGvSgwGDZwYxNn8vOEZLVryuBZf0+ssnroVzEvnRYxGpXxi7BKK/TithiLDXu4vWMNliTt+grShySLWWHLan677/cFh+JV/h//3JTtekU5C/LS4nnivLWwoh7lTrJ69uEk0nagN8d0gYEHGzN3m8fEJCnkv28/+Fyb/SI8hzlIXh7PVvFoD6I7irm3wOpDn4QA3f5b8ryBD+F58ZeW/N9LOAHRXYZMOzCKNPBDVYyjtIqeX0uM/n6C0qkYb1ZGxxXfYyF211OnPDbmWIlwBx+s3uTZ0d3GLK1B+Y1cReDrjssHnGgrCgesxoV9ygaeNHTnENcPtcRejwx6550zt0bv4vMt5JRwDl4tp8Y3h5OtWyP1sw9enZPaH9O0CYqvqykt7XruX84rEAv8Peg3vA4Ld6z9Pr7qYIY4ADIr0Gt2ZPzF5fFPMEHu2r/L08nEAatv8dTwf053TbVB2WIx9g6fUiv83zYIN6I5JpmXqaRwx61ymZDju1dZekYkH9dp4OlwSz8y/LS0TJnkqbTg7MrEYiGjbev8rZ4E+/3aFAAL/znoRo17lC+o9YTDg6HLSeT0ycXbe+zb59QTg9vefbta/uLZOivw9NVI4evM+lkVQmGawWHbWtqHkF8Mtqtxn2WuLidMLUHLbxu8cotCbFq+pj/BNSIgHpIzgfvTL04qTkEQjePJlb1b7xbmxOqD6xhFepkdL5BgRl758nEtRt2tmF9nGUMts0TOqJPUR5XrFaCNKKTTXcapey0HOBMeIII4W/TrLmGAwvhvBKQBJmwdDd9TpNgkVhf6QNcKgc97S2nA4bf0ClovW0f3maH7C7eVZHG5+Ha/qY8JpfZnpq/hr3lydISA7d9K/8eld+y/IvvvrnL42OUP5NPp80W5/IeCuIy8G0WYXn9HWuZHhAJTT5UPpiZhZjmDwga7j/KSOhwraNKdsjytnfRDpnusBku3mYVAwujmLu53GT5EXcZFR3eE3vnhzPA53n+nMTF0xTvELxefAETtM+Y/6+zKs7/dbrHnTl0CrRbg72LyEvdlQxAcz+FCLaevox2X25TzH/3ZY3OQR5e10/7hntyL6QxLv3fRen5MSJL7XyDjnjMXs2RU5Dh4FNc5WURk8YNZhz44V77+PhnH6+3j5th72cXr7eL71FCrFr39Ep6uJuuX3pZLfw+hEvoYTjPiuIBJcnPHvTTg+aDI50pfC1mp3WSJ/Pz85hIEaTeMsaacZ/9eUZntCe5Wi7KMto9rehxOaWb/QaDqTzMsxurFR1QdeRF96xbMDTNfOpyFXATu3iT1LWG+LjRQfqxLp/jNMqfnQ77tV+kyzPPd3gLCDt6DmWM9nHUgMLe7mztAMGqKeCvZBy4y+MsHxhqrXpu4t33uWJqcDlg75+ceRd1k4UQ9B6dkmc7aS3YhpD4SkgyPpTj5W7nm6XJWyKXmaK6n/VzO+vzyuEBDwubPB4YsQUz8RLMt15K7Rxz17K1hy07ULqvjh6jJOk6zaOnJi3pSuYJMGMhO93/w/46tdnu6tcoLG9T1xDv8t5luczv3ewGsMC9YKms4eK3KLRRQlwY194/D0Vi/pmAar9Gj9E5KfHgRhCIawX47q6i4ymKD2u5w4I+C0cXTnjSc2NmNNONf7sePnZn81zlRzvgdtnH4YVbPQnWb1nFQPtDw7bMfhnnw9NfsV4a6+PwtXYS+fRHai+BUxY7Y/e8fvfI6z8teRkPJO/Rt+Itqj7cFXpewMp5DdAiWYwaPYr9ViSCcPXWYtv4jLDvQQeun4YPAn73g4BbjG+z/e7P2fUdiopzjpoMZCv5PnTzrUswv7ChAu8rXIR2ew1159lg6DX+XNJiRcPsTxiNCqPb4wlv/nGbj/Fq3qoFwdBNluzt49uZscamr1j68Nb1wWf4w6gv8WmICJvoCxpSv/ZI/5AOW8ljFN/EKNlXf/l/jdV2+lWWPsaHcx5BLgBOW7Tr72UeeUuHfpUl52PaOsf74HiPinNS3qaPwiWQW9a7OuoSlq6Ku2S/COTrh3jd9PCc7n6A9wUm1u4tsb18rrn0FqfjnW5w37vcgWTnfIfAt+fW4tW81OKxMWe9vrEYas6XorymqgJVB09z5H3m9zKMrr+76wpUHb7iJE28iQqdL6zbg7fbQXGKah4PZe5rRK8ZXmaZ7DrFaBzHfRT4Wcb192qx+xqdkmxNGVWa1bvTi55HpU/XdMttD6tQz+uMHjbD19hGF6Yuj9NN7ksd+BrEOLAPclCWp00epcUxJiEbh1sV4jjIAQx/G/XRge2jBsOrnnpP6ZuxxQ2VS78R9kahi11F93EzVH0J8Vf0jnor73jFb+UkYDkV/Tx38XXu4vKqqPq+q381mnv/wI2fDlgP83n2NcZWCfk44bZohr4GokPuez2cKE23yQdxg/Hia/GM/6yGUl+nPy026nMlb8dAnhdTHx4fCzTI54xc/Q9hcBmVu6eH+O9BS4Q7/BGSAF6zcIeoYr2S8Oh2SwA3V97/i08XmN3Qa+AERWkffNDjDIu7plkH1uFF6VioLhMuz28rzsBgPbpdoI5+1gYbttzoiSy8BHHodLITh6se5MS1SlqOh+j6Fd9V9W25JdRlGRl3et2gU38DLVpOdjyDgVkTe1Usu7mrGSZiWA2iNoGaQ9+2LEx7tWnKqVeZttw+l6E9SYtvJ0FfM0RPvo4LMvm10TRcurLlYfyBNm059SXTmJ0lqarDvkpafks49VVD9iYdA2eMHqXbW2qvCjrYicFVD9m7d+6RFmw7tmlqqX1Ki299PN/UDNGTDffmf5vI6WZa3jcqctyY20p4XFNKBWjld5KhrhxyRTR0p2O7LBq8w5nB2mj+O5q7LEk+ZVWQ00nTZRgYY+hHhhW9SItvLjEC6bohOqHyGG/TyK/V/o1+m7gUw/U6upDVDE3SUhrF/sCdcFsiPpWsocN/XTMEOC6T7PCjgMNXX1Y2u8sKh3fzfc2Aq6N79DVG396g5PR4TlLHY4ZFdCyjsPPipq0+SJS/oqKxeIhjYVrQtffmVEF+/c0ddT95G26qtxr9vZ2Tk0mNzP9FxQZbMfHA6n1myUmG9YuiyHYx6dn2kozE3K89/e5RQZwSt22iLQ781+n+l2r52mfiaiV6QMnjb/2P785JGZ+SeIdF+OevL3/lP5kPaZ2k95cLcqtZnVUVu2gvmgOrsZfKAEjOygMSsLL9m9Ak/oxRXkdquMrSoswjbG7xm4/TXXyKEt4eHKHh8FBp2rHkS16jE0orhwuV3ibt0unTxPa7Zrge0Nnj1QsKVAZYi/8mYVSJbAsCGi22iDK2dB0QY3RaBL6EO0x6z11sH4BPhepmvjbTy2LhKNBT3lCr5GMJgwBSMMkIwDS/sZe0b3ZFPwOwbqL8gPhNYg8MKRBUHf8DgtQaIFMDVHNSOhY4syRZxuRcScqCjPyw+CmYqLGMWbc7UN2KQtv2VECU1DIKErQ/h5kjTXvRA1oaRYxmQUw+GV443yO8hcFbmV2JGhesWL9SYzkwPcoXjYIu2JNMLlZHEQRznAlGQJ+BJ52kYRPXuZlgUrcgA3pd0s8/BAptUDAZ/OQ+mGPhroxKRPzx0x3awj6os8IYLS+LLbZk8Zhi1LHA0mRQah0wPYLoP3777aXQcz2n1q2W5tT9tnQAgD7DM+96BWiHf8OzA4P9JzoiJBjhJgNG5/rUPfTRLa3bGuCZ0kiTDO+2D4kSeJxpFR4BVcpHCpIm1e53EyFLs0C2GCBWiyubPp4AVvLHK2Oj6jJOqpe48MMdJ0xppi+rcW+5aDBqizX+9GggUaHScgsrMdPJqxEaFKUrW83k1WpkM3lNhqvWLXAZFxyttIwM/Y+Lv+joVDFpa/rLjpi8nWIS7knhwxDR3ccWWE1UjQDsNUb7WxAwyFUNA4hWG5Om+KyJ02CieSbVQAOU360HQ92GMQKzgnBFYW7FLHp4KJhYfUxabGhnAyfwGaXQmVAv/ihwgiw0Hzj1ftbTrKLbt9wexyXdZqp9pc8sfrsflz6mwEEIZN0/8WjSvVG+OGGTV4mQGvEN7tnbukw/9j+OMrgIkR8gWQJjq1N5BHCpI11I2lS/l58cZrqrc5vhYsUws+ryKWCmiIgyEsyY8BXjTWhMoBJmI8cULH1ik8djkbQ3v8mNVmFR85sUYjDBauY5a8zNca5jUaeZ7pwGkx8AfdZImAqBmjBSk6Gw2WkuatiDTi+EstUMdjYnFXMc5zqEaYa46c+kpsfXiKdSLvC6U4XlGhVd7T/u0f+f4xxVsUPkN/5zGrsogUFxmPLVjGG0Vjbj2NTn6Vvc8/FXlD9vqnRB0r6liZhOZQrsruzMR0NfuJCqGgYbVvMaJdvUmLg8p/sEVeGeLsoyjz+fS1RH7t/2JbpJjqIEOpguHfMyRqqZWkiBOOTUKLNxUIzKdTWRoa89H+g2WF3MPaHrB7O8dZkbzpnunBHMvAJMO1v+RIm60Zngo7sSkERmn9ctDyc0hKwV3fHwGpk02RLPBleLmdYmBNX4g5WdC8Q8hirm2FaRemCeJ/WAEhDSVnhOD2lm0ixdYVbYW8yQNgOwjT+02V+Cz2N4ezihXfwY70hRt7ddDthg+SG5ZJQrAaBEvSVAERb9w6n6c2uiF/1MXY8HMyCECoWh0NVAypYwzNN4dwgNjZuhUtakfZjBTAdXc21DA+eHHJsHg23yAVulwdSYp7IR6WPnzmUV0csMvkOhSleyWqBUslghTBhZFACXmTeJpE+BzvwRIGba4VOiDE7iNi7QPkV5HKVlN7ZeZcfPcUoIJ/cIUMgGQUtJviY/ApWiJlLMycVAhb/F7M1nD9Tx592hGJ16q24Azz/PEUki8zGN5RhliGgssAWrHB7lBpo19Gix54a/5Y6JJpBc7ei35CGvhZz+SFIgBLp87GPI2aJx1GNHD7CczVkjr8SnKDl3IFVrGAYX4yKXqGsiZkMYEsNOeAoD5VpbCzzzDKaG9eTLyRH97ydaGC5n9fc6+5YmWbSfLo5aKwF7pNP9uIpIap06Jm3NKZTa9iE6nhIEy+/aibMbJay6Z8QRgjX+ZFjYxCjH+lV5MaRJ1IZmxCOYCLK66aRnZKF+XUW6u14fk8Zo6WYAq/kfNUwDohGPE+zwM/UBwnv0rSBPYRYROLiVlpGh/3HxgYM7VUzamjxwcBfa3ion8awimusm2BHTuY4S5hFSy2aamz5y/nJzCs8RdGMtqgYDr6owHfiuv5coT6Pk4lw+VRxr5zYuyfWsRzyVBoxcasLFj4BK9WwAORkWb7L8fCS5HnwDT36Q0LXJcKJ+XTwuel2WA4JNdop3Y6OANCrCoPl5HTiolVkOELbkv3/k2fkkRQFFInRe8/MoExFpUBQhEHRkhgkIHqOGernmMIQActv3WEi8TDDomPfluCMOIZ/B4kMm9pCuCwmh0Vcvlv066vqFiDUZiD7k+yD5EFVrF9Imw6T5ZelpEGs1TBpiDT5x7y9i0zwuaMZc5pqjZvIVbhts82MRHdCbGEuTP2/hKKEzDapKSw7KwxKsJqwqo5ZJu5PHVQWxBnwr9mPEDwAx8zFlKnwRCaed+Mhd65wR1UkpShDwknU07PSKLAkw83frmAY2I7p12AFnareOP+LH8irK99u7c757igq0/ysunyQ6uHejxv+wlYJh1v8YbiQZKwBzp4sRJsCumBwizFoHVsi1SwONNZDkoDwjLHqsEOAJbdbrnrbivLD2kf4UZroamhPURlsjOeOM6dFpl03vsxLNf51dSSlKUP+6bAz1isx/nX2PvmG032WYQdEOTos4nwQEZ8QByxd/dglptYiTTAhnXidBzXJ8PnAZbRhyxQrTLdP5HT48xacqK9WsZ7JWSDbQY/fjsgHU6TH/aawVlZwYwXK79lpg5AgHDmxBmECbNh3rCUTGxxJdhWkvaSsxTn7v6DVT1M9r+l85o0+4WinPeVqlRkQBfI0DrYcpkbmlDVOygjUwrc8iVr/dA57xRxUrbC5vbLECHmf9Kde2GRHkKspLKudfyPyUGpjwEnFLEr5wPXkkBd1M2pxB3kgBQouYpeYAszHnKid0TT5dCdia/x38HIA14o28E66mvpi/ekK7L9mZD4Un/CwfwQRKZigTS8d52wyqpRYtZLA7jT3DAFKiodFwx1edcNu3O+dY48Nd9EyOHm/TuOLr6QRSdTrNNszt3/jCZR8rCvoYtUn1xGzw0V5kqDXy1c/BDg5ApZSihb0hcQOIX1DaXJjwdSfDZwWDr7gn3maHLfXvqiPlJw0cHXPiwJeNAkiqVZk0oc4tVDYLgztaKZPmOBFnAbVF7DynQ9WY+01bOE2+1fSPn3AxIHnorAMyi4EKeeb7cP5c7PK4TkE6cvgPum3xOTVbunhYiDotAiRYv69Rid6honLg3N7k2XE8lLCNc6dhbNHi8cEpZNIi3RlzAcgm+wmPmcCj74rpVrWPj3GCf0HbkUIzdA2yjPpfl34/26titLuZ2O/jYpdwgQ0rLbQjAyGaOMJmJzq3u0lGCV8omikQnDp9bFYjVd3pLtPKLK+Ck8fHKH++/r57itIDusdfxNU5x03snhXwaghYaLU/mo8yRAT2Rqz+JRAmIL3C4KHWw6QhRQfMAxrkj5+YmAATjOUnA8NltPtym2JZdl+CbnKDzD4S4RmRpDSLX+zKNFvEjlmGu/n7fMwPdCN6gAzB3NSOIH+e0Rntr49RnFyUZbR7IpftN7FiqW2fGioI5EDJucR2IMXiM07Behkd+8QTrr5hqE2Wpm5e+Bk7gZ07hmaRzY4Sf1srsVMH4mOImEQDTMEokyUlvAxvgVAmN1VwlBk1R8s3B2xR45RUlWEd+0NMjrYwmGxso2pOBr/b4ynLSyzbI56stw+7J7Q/J2gTFV/kj2hpIuYcgSkwP5FgZGA4ciVh3sRKdQ6DF1YnkwYbCeP0UMk4YT6g6aHCyMCl8FkfVFidFgcV3FCS1f7UoA7D+zVY3ihWfEAgujDM4sm6772AjdLL8EDhccpd3l2WJJ+yEuO9ub6rOqpnStxAuoNU/HtZgXGTbfl62jPWpi6cuq4ts/AI4NtnDtOEQoOt5qCz0laDEWCmtrxVm1Pgq/rhIi2+KY7lKRK+V9ufRxnFBmHM17moxFwzwlYv4qT5iK+yI5kpDQcwqsrYYxfdNJ+SuPt9RSOW1NRWzY0MIzipsmtW6YCZrS2R5Glcsso5PTJ+WtmmuxhMsoPlcERVGXs4optmLgDp31c0HElNbdXcyDCq/i0m6uJ6UchW1v84zrWyPZI8DUeweeaBn1a2CR23yaX1Pfoao29vUHJ6PCdpFcXHdK8nqT/6nk8mB+BIARCtaAgz6xGrtqfEIVOgc5tpqKR9Phmi/HrCQMaYIZiYyvNAltOwNulYZg7m9YxaCxqqFuDQ5win5Tnv2WNoXJe9a1ynfK4+KlwD5a2rarZHN3FelK+jMvocFeI9TlXrAZXdgy6S57j+merM5vfqkuoY/fPX/ecM93X0OemrCAMOxzj6fhWV6EBCkIjs6VKwEZpA0xT+V3WQCDTTlUBNdIUa9m+zXZTEf6N92+NAQwAN1CRApms8Sg9n4sMmttkVgU11pSbqoYcyJ2exRXbOd2BrIJlUSYFSI8Udyo9xUWCMt4fcggQiCdS6SKVpmX0HJrTKFkMtshQ6PbMkgXQjP4P6kBIDru2FBci7LZS10JYb2qpbhkjN1VGoLNYRGTaraE/dkLaF7lmo0EBXAvHvCnUKVH5r4EDYlYDit4W6AbCJmfsOlU8Z9OnwBOBwyNHo2izx8IyHsa94OoW+G64cbJEl0TTYnzAJbfVFUDN9qe4rahdS4ifUloDfT1uoYd9ndxb490VQA32pDmbyCVc92xpPtXfxrjznUH93JaCJ2kINe9ZrW2iDLYYaYinM+luhE0eg6P1tQ7R9F5GvSK9qlJ4fI1IHGtfYYlBVhsIQe1VE9jhHR3jwBqlUiGQIdSKgJP6K8udNfIRszRaDjTIUZn1Lx9qWdS9No+hhmsy28S5s5k2clPAkra1iJJpQy0xSxcAhUKg+gpbK+CtoKmo+BpBKJQdNaSvLwwnt4sd4RzZcVJxamVQyepV8cB1jSeHqH5pXc+JUrCQHZ2ZlDSfpjOWykci0TzcRtDukCxW9RcrN2vkU5XGU9lFyr7Lj5ziNJP1iUkkhl7KeRt4/zxH55WMaQxMBWwzJwFK4WcfcJJqpt/6//XfEV5QLZCaJNS55PZuQ06ZmacgtMNLU0E3V3ZtNcZruisApuivVnQ7FKL/LY3DnQJWBJ0N9saaR3kVGaKMvgpqoSrXcr7/jCTaNkotz+VSd39VDk/T0Qk0OSaGuoZGOhIaTbJeoMqhdUlxsjbZMhFZ2hkgXKhoyO08kxLJGlPwbChP+f+TZ+SRrpClUtNRQaFpqIo8LjTS/Q/ybIsNFPpvrWbrKZ8lUy3yWUiMFnHFakAImg6SAKQ2lULSsbs2sGyXDC1Um7U6jnQSVbxlupC6TNlIXaxoB86oKzYFUUMMgocEJlmRT2hfJTq2Mtp9sykZpK/JOYym0JmXyuQHGZMphMzIkWvX49CmAijwJrCZPpTt0ElN6iKdPIg14DCWS2TYuW0HJCI3EMFs1SQPwA70voYRxICG2FMdADjMB9IfpdLhu8UCdLgUP1WkC86bq6Peq5moKTZM1kfbODNJMqpGJJkB8YngZwtIoViMsoXZDwgY3BbYgLIFq5ciR6vqwj6Qp9l5fBvZbX6wbH5lXrOLYyBSD4yJDoYVlIt0IUGUwFBPDJf7HXN4IVQY1QhXr1lEoRXizpRrcRRJwXSVQ6baImAci+9fP4MUsVw5uFVkS7Y1WBt4CNL/DN1iZwa1GH/pPnJa6Ivj6si01Eb079YA16Iqlipgem0jjdQkNSynBezoZscVNygYdTwk8lMBkupuVntLw7k0hgUiiuoszbrk9ppQ3LFCoTjtNm71HFdlefmnKE8BrC5ZGZ+Q8KwrMPJG3KpKARhaodEau1z7SG3auHDQwS6I9oQXDsABHtSAdfGYLkpoLom5e26j+SI0JuSQeoTHF4JEZQ6H9Yo+nKD5A41ZfBH+hban2OpusgFSDEk8BX2pzRAbnnm9RWaJcMzzLCGVnohCt1gRRgcfRv1B8eIL6lCuH1WdIzBp8HWNwF7DaIomiWYpK0zIXAUdoliuH2uRIdLP+c7pTTPp0KTjn0wTaw24+kgZwwM2TwIfaPJVRy3KrcuXyNk2tyjsQavwD1eQmHorbitT87pdx05I7lkno9J5f96jAS3TiIW7g2NZoK/efEyhUfnoNEdI33B5NKzwXRBLVGff24nRKYrTfZA19bCGFxn8BJjOThq5jLpB8fSRQmInRkOslaH2mDC7eDe/ftz2d8VfSeZiaOfeaO/kySSfML5qFEB6iTQQSma8uS2WwFugexIILgK5UNut3BAYOmvKmmFKZm6ZpU/KHcjKsAaQKyAHUNhLpxDBoW2iQegWi9t3f9p7/VB2FF39fgX+0QqXV454hYBHkTwzYmornBTUXk+cCL1j1TU1DP7XQ2wWmDmYU6ElJbxHlKxF7cwiLDnocK7YPjdlFy5hVlKsqe9hBNNU91lBygsZ2kKvygYZ3U26i/IBKB1M2FeUGkCqsUnCeJsTTmPJ7ZAlCfIL0Q5xaZeihjZtq9Wuabc0RVo4mGSikUIN9CdTVk7zxsVeR3S9s2w0DanYZsXI8Ma8sVxJ+70IUVb9iUXDhdlIAM9lrnCAGlI8i5pUVIwmgrES9uZiMfiu07fgC5gEJA5gCev9Um0D5rMle9WZTrFKaJ/GvLrfBJ/Vkr9/cVeRMJ1eUJfTeSyOq3m0su7MPxeApJ9avGsAVg1JxyeEOwyCoIeSDoJzYSz/OwQyXcVIFh+44K4zAkYYzgRGGBijdxkzpWcu1FmhDfgLcg2eGgewVs70Z2peqyqWxSBRiecy/tiU1pS9pHZbJ7XNJ5sEmsFaG6ORiQw9Iieiqh6HsApp9DVqvniXPVx3UZl6UbjvGgOIwpRfB2XrgK9i6OlfkXf32DFqvPhz3RVQDkn826nfPIxX9LtD473H+zqQezWRPwO3VVF2pKBY3JtXkSvGv64lSspfzYE3QLGJhWPPIlzwm1Tz1+aTmYd7uKj4UkM7/xwJd7dUziur9vQ9UMDd/Vt8NWDPkpyM1EkwQ3Fg2XxFY0zMeZmq09lLXClx8pZC4giZ1oSykYWyAxFfyv1aZyCxAtA9wP6quEBInQFgThokqTonzKnbLBiSRrmFZOoUyQICUWgtF4BNXXLmrLQ02sqUjpEitYVRdq6IY14VWVh6qRc1LEqYFZq0LteLBwI1J9NskaZVg+yXXDvBiFiuDrNAU3R6xX3ZLLSHShtgkSOII0YbwuEXgVdPjQSANhoZxTcAFOqLXi1JbyOuEXPUqIj3RJvK85oVU1qMFJA+GmGlMIglBpTeOpmIwM6nDStHcDGNFOVxCK8JiSWJ6gVfUDmwUV5x6u5gZxIArG2JMwVsSKsw3WtWN20JYyW1kU830CxBdspW+g3LqcAf5guM5c5ZPlQYxhsL7T06tVUqiDaDGPEyiCE1nums2ZjHChtogrB9tUZvAfEFMrJ/ETWoH+0SXZk421qGTTRkWcgNBYRmJKVThFpdqYgeYjo9NE2uFN5FyOWlcV6u8UmuzdeMszUjiN22lzRqbVM1nFKOY8GViYKmYw3Gt3BdChgPl6KNhoAPxNtqp1okJJgzpxcRHcK2XPbLorO49/hAdTwnqGcv7nKP0JPqIvd3Fnd2yj2FElSWUinPXwQ97hHi6pLo8VO4Q9RXTtkjkf6IOr2obHFjphikSyUV2d8PkQxiTmv2P/ryNTZ8DqivoIT7ABVf3kXh+oybR1MAnffRnfVOZSBWhWunDbVYxBJpMonYTblbRt+1N18XgVtoJoAphFCFYOKlK/epHXRLoW68vRxZMYSZsea8xHI7cUeUtHTlcoi9NoxGZiU/eiwzGHRdrixX99mzDUtmtNc1gQafpTfIl0mxVXyxF51H0kb9bEkTc4CkSSCcX3PUdEhM6n9SCI+O7KqoamziKEMNSOPWgSP7b3sVO7ssHV1Ds3gb78kF5AxguyjwAnkzT2N3QLjX14H6dhTm63ARyG/AkfhUX8ir09Txtenr5FVtZkcj/Vja8qm02i+0d3p89RQXa/xWXT1QLouK6Kt7UYery+TpIVWkuDndDMN9tz19uBriCJ0XAmtIv3igRiifTfKT73dg+bC2/48JU5unyqGgGRIokwIBI54Dp64HpXexVBDK2KJdCSvoQCyNFhhrCxCTljB+zaL8LfSW/6JjGNG0yHPk3wVH4VZpPF0SqSVMBuatH5v+er1xLltCT4GBNYX5V5iVy3Qi1YXy0Wz6ecEl7PibDkmbAAynDDHVA5qjmS1YlhBpwwG3Q2VJa//0dOtYIn+BK82xJRa7dEXh5piRL7dV8/Zp0XR4MpPoy5MQhPo7JTaHYMEpp/e8bxzYDnBBtC6RrA0YO07oKvMgyzNXA0SWNU/MSnWUM07+5zDdsIrXtbRqXcZQo1pKqCr7XkXC6uGbq0aSAG26Mdq0sNqW3i7RuIHWVnOBFuFkOP4fIeX3muq2QxU60nIpcMZXDWfbqKV2dOk/GR8bDs0lUUxdIF2LWGkdtnbrB1eTV86OWkPNQf4EMUwe7R4aS7PQXccpsOS6Od3Ruxe1Nnh1V9lCRhzAInEayWdgos0IONsUmszAERbx0M3Q5MLeK/ZtI5H/jJuTxrGtKU3S6jOwJEDkdHttBSn1fuzthCTk/mzFelmnTJQZyRkLhxccof77+vnuK0gO6x6bt80QC2xJtJZVR2MSVjUHgpJTsDoXOpVlvS8BUmQONQP4w1p6lXpbakuSYyslQWyfE0KdJDkoYmWb69Gcmxb5dV8X/9n1aE4FZILc3MTyQKqgVvgvOLvzKrJcP9dsPkySWvsyie9lhUEuu7PBnHrMw15bNuqk0EksrVwzKBEr0UWX4lJlGZpBgqGHaMAUNXcmrWjNBDJPbcsumUxdtpKBWTL9Akvd6FlYkb2c4gDk8CQt1ak4X33kbcyioQ5oDTL5Zf4fKnJqu5ujzgm471jJjALTeFQF4iDlQKTaKvKZumYToXH/b6nztKkuLMo/iahLGm/luLddGw99kWzFHILAd9sVbv4R0CJ3PrrIkuRPr5ZYuH6IHs9O5kwwsSZGrlbJK0zSpSagskKZAYRJHilYbyDE86oC8mQ/t2ztZlsphhu1f9anN1dGphTd+NTiB2lSqT9PeZ7KDApu4YRzD4wlIjlrv8xRZT4cZtk/WoTZXR6cW3jgZyARqN5tuIWur8awmqa94z+6/rRFmUqma4gGGPgOux25iCqyMztY0VF2u73LM5wQ3rWnduI6MXPPuG2xkfYQijtD/YaF/1V+9qJlUhse9jPKu7NWLOi918wP+sz6DepftUVKQX1+9uD/j2kdU//UaFfGhZ/EK80zRrmqzZ9rS3KaPVYx8ktGYk6glaYvbVCCojPZRGV3kZVxFQMXFO/wtkYzrxCmnOg/6jPa36YdzeTqXWGV0/JwwZ/SvXqjbf/VCkPlVHZKx8KECFjPGKqAP6eU5Tvad3DdRUnDbTRmLK2z9PxD+ve5L/GmW6PDccXqfpYaMGvPhTSpK9/iT26DjKcHMig/pQ/QVucj2sUBv0SHaPd9VmRSJj5GMib4jWLO/eh1Hhzw6Fg2Pvj7+E2N4f/z+P/8C1SZhZAnxCAA= + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.Designer.cs new file mode 100644 index 0000000000..03447c2048 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.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.1.3-40302")] + public sealed partial class DefaultDeliveryTime : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(DefaultDeliveryTime)); + + string IMigrationMetadata.Id + { + get { return "201702061557351_DefaultDeliveryTime"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.cs b/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.cs new file mode 100644 index 0000000000..8578b1486e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class DefaultDeliveryTime : DbMigration + { + public override void Up() + { + AddColumn("dbo.DeliveryTime", "IsDefault", c => c.Boolean(nullable: true)); + } + + public override void Down() + { + DropColumn("dbo.DeliveryTime", "IsDefault"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.resx b/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.resx new file mode 100644 index 0000000000..cdc3b219c3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702061557351_DefaultDeliveryTime.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/201702081502539_QuantityEnhancements.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.Designer.cs new file mode 100644 index 0000000000..d731aaad31 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.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.1.3-40302")] + public sealed partial class QuantityEnhancements : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(QuantityEnhancements)); + + string IMigrationMetadata.Id + { + get { return "201702081502539_QuantityEnhancements"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.cs b/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.cs new file mode 100644 index 0000000000..990bbb1675 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.cs @@ -0,0 +1,22 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class QuantityEnhancements : DbMigration + { + public override void Up() + { + AddColumn("dbo.Product", "QuantityStep", c => c.Int(nullable: false)); + AddColumn("dbo.Product", "QuantiyControlType", c => c.Int(nullable: false)); + AddColumn("dbo.Product", "HideQuantityControl", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Product", "HideQuantityControl"); + DropColumn("dbo.Product", "QuantiyControlType"); + DropColumn("dbo.Product", "QuantityStep"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.resx b/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.resx new file mode 100644 index 0000000000..dc49c84e6a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702081502539_QuantityEnhancements.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/201702091426405_ProductSellingAbroad.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.Designer.cs new file mode 100644 index 0000000000..a82ba7bb56 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.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.1.3-40302")] + public sealed partial class ProductSellingAbroad : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ProductSellingAbroad)); + + string IMigrationMetadata.Id + { + get { return "201702091426405_ProductSellingAbroad"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.cs b/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.cs new file mode 100644 index 0000000000..b8de35829a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.cs @@ -0,0 +1,24 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ProductSellingAbroad : DbMigration + { + public override void Up() + { + AddColumn("dbo.Product", "CustomsTariffNumber", c => c.String(maxLength: 30)); + AddColumn("dbo.Product", "CountryOfOriginId", c => c.Int()); + CreateIndex("dbo.Product", "CountryOfOriginId"); + AddForeignKey("dbo.Product", "CountryOfOriginId", "dbo.Country", "Id"); + } + + public override void Down() + { + DropForeignKey("dbo.Product", "CountryOfOriginId", "dbo.Country"); + DropIndex("dbo.Product", new[] { "CountryOfOriginId" }); + DropColumn("dbo.Product", "CountryOfOriginId"); + DropColumn("dbo.Product", "CustomsTariffNumber"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.resx b/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.resx new file mode 100644 index 0000000000..5f91f10192 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702091426405_ProductSellingAbroad.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/201702141336312_VariantAttributeValuePictures.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.Designer.cs new file mode 100644 index 0000000000..219cc5f9ee --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.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.1.3-40302")] + public sealed partial class VariantAttributeValuePictures : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(VariantAttributeValuePictures)); + + string IMigrationMetadata.Id + { + get { return "201702141336312_VariantAttributeValuePictures"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.cs b/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.cs new file mode 100644 index 0000000000..3cc8082a4a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class VariantAttributeValuePictures : DbMigration + { + public override void Up() + { + AddColumn("dbo.ProductVariantAttributeValue", "PictureId", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ProductVariantAttributeValue", "PictureId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.resx b/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.resx new file mode 100644 index 0000000000..0d69708f2a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702141336312_VariantAttributeValuePictures.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/201702202112266_VariantAttributeAllowFiltering.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.Designer.cs new file mode 100644 index 0000000000..7ed530fbb1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.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.1.3-40302")] + public sealed partial class VariantAttributeAllowFiltering : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(VariantAttributeAllowFiltering)); + + string IMigrationMetadata.Id + { + get { return "201702202112266_VariantAttributeAllowFiltering"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.cs b/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.cs new file mode 100644 index 0000000000..77d594e60d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.cs @@ -0,0 +1,20 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class VariantAttributeAllowFiltering : DbMigration + { + public override void Up() + { + AddColumn("dbo.ProductAttribute", "AllowFiltering", c => c.Boolean(nullable: false, defaultValue: true)); + AddColumn("dbo.ProductAttribute", "DisplayOrder", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ProductAttribute", "DisplayOrder"); + DropColumn("dbo.ProductAttribute", "AllowFiltering"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.resx b/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.resx new file mode 100644 index 0000000000..fce18df056 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201702202112266_VariantAttributeAllowFiltering.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/201703021146527_ProductAttributeOptionsSet.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.Designer.cs new file mode 100644 index 0000000000..74a832558e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.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.1.3-40302")] + public sealed partial class ProductAttributeOptionsSet : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ProductAttributeOptionsSet)); + + string IMigrationMetadata.Id + { + get { return "201703021146527_ProductAttributeOptionsSet"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.cs b/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.cs new file mode 100644 index 0000000000..bca51f2063 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.cs @@ -0,0 +1,66 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ProductAttributeOptionsSet : DbMigration + { + public override void Up() + { + CreateTable( + "dbo.ProductAttributeOptionsSet", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(maxLength: 400), + ProductAttributeId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ProductAttribute", t => t.ProductAttributeId, cascadeDelete: true) + .Index(t => t.ProductAttributeId); + + CreateTable( + "dbo.ProductAttributeOption", + c => new + { + Id = c.Int(nullable: false, identity: true), + ProductAttributeOptionsSetId = c.Int(nullable: false), + Alias = c.String(maxLength: 100), + Name = c.String(maxLength: 4000), + PictureId = c.Int(nullable: false), + ColorSquaresRgb = c.String(maxLength: 100), + PriceAdjustment = c.Decimal(nullable: false, precision: 18, scale: 4), + WeightAdjustment = c.Decimal(nullable: false, precision: 18, scale: 4), + IsPreSelected = c.Boolean(nullable: false), + DisplayOrder = c.Int(nullable: false), + ValueTypeId = c.Int(nullable: false), + LinkedProductId = c.Int(nullable: false), + Quantity = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ProductAttributeOptionsSet", t => t.ProductAttributeOptionsSetId, cascadeDelete: true) + .Index(t => t.ProductAttributeOptionsSetId); + + AddColumn("dbo.SpecificationAttributeOption", "RangeFilterId", c => c.Int(nullable: false)); + CreateIndex("dbo.Customer", "LastIpAddress", name: "IX_Customer_LastIpAddress"); + CreateIndex("dbo.Customer", "CreatedOnUtc", name: "IX_Customer_CreatedOn"); + CreateIndex("dbo.Customer", "LastActivityDateUtc", name: "IX_Customer_LastActivity"); + CreateIndex("dbo.GenericAttribute", "Key", name: "IX_GenericAttribute_Key"); + } + + public override void Down() + { + DropForeignKey("dbo.ProductAttributeOption", "ProductAttributeOptionsSetId", "dbo.ProductAttributeOptionsSet"); + DropForeignKey("dbo.ProductAttributeOptionsSet", "ProductAttributeId", "dbo.ProductAttribute"); + DropIndex("dbo.GenericAttribute", "IX_GenericAttribute_Key"); + DropIndex("dbo.ProductAttributeOption", new[] { "ProductAttributeOptionsSetId" }); + DropIndex("dbo.ProductAttributeOptionsSet", new[] { "ProductAttributeId" }); + DropIndex("dbo.Customer", "IX_Customer_LastActivity"); + DropIndex("dbo.Customer", "IX_Customer_CreatedOn"); + DropIndex("dbo.Customer", "IX_Customer_LastIpAddress"); + DropColumn("dbo.SpecificationAttributeOption", "RangeFilterId"); + DropTable("dbo.ProductAttributeOption"); + DropTable("dbo.ProductAttributeOptionsSet"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.resx b/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.resx new file mode 100644 index 0000000000..df9f3e37a9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201703021146527_ProductAttributeOptionsSet.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/201703161013248_FacetTemplateHint.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.Designer.cs new file mode 100644 index 0000000000..7999911b93 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.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.1.3-40302")] + public sealed partial class FacetTemplateHint : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(FacetTemplateHint)); + + string IMigrationMetadata.Id + { + get { return "201703161013248_FacetTemplateHint"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.cs b/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.cs new file mode 100644 index 0000000000..c1d805592a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.cs @@ -0,0 +1,24 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class FacetTemplateHint : DbMigration + { + public override void Up() + { + AddColumn("dbo.SpecificationAttributeOption", "NumberValue", c => c.Decimal(nullable: false, precision: 18, scale: 4)); + AddColumn("dbo.SpecificationAttribute", "FacetTemplateHint", c => c.Int(nullable: false)); + AddColumn("dbo.ProductAttribute", "FacetTemplateHint", c => c.Int(nullable: false)); + DropColumn("dbo.SpecificationAttributeOption", "RangeFilterId"); + } + + public override void Down() + { + AddColumn("dbo.SpecificationAttributeOption", "RangeFilterId", c => c.Int(nullable: false)); + DropColumn("dbo.ProductAttribute", "FacetTemplateHint"); + DropColumn("dbo.SpecificationAttribute", "FacetTemplateHint"); + DropColumn("dbo.SpecificationAttributeOption", "NumberValue"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.resx b/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.resx new file mode 100644 index 0000000000..780c20cfb2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201703161013248_FacetTemplateHint.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/201704031946479_SpecificationAttributeAllowFiltering.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.Designer.cs new file mode 100644 index 0000000000..bcc661f051 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.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.1.3-40302")] + public sealed partial class SpecificationAttributeAllowFiltering : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SpecificationAttributeAllowFiltering)); + + string IMigrationMetadata.Id + { + get { return "201704031946479_SpecificationAttributeAllowFiltering"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.cs b/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.cs new file mode 100644 index 0000000000..a9cda504f6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.cs @@ -0,0 +1,24 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class SpecificationAttributeAllowFiltering : DbMigration + { + public override void Up() + { + AddColumn("dbo.SpecificationAttribute", "ShowOnProductPage", c => c.Boolean(nullable: false)); + AddColumn("dbo.SpecificationAttribute", "AllowFiltering", c => c.Boolean(nullable: false)); + AlterColumn("dbo.Product_SpecificationAttribute_Mapping", "AllowFiltering", c => c.Boolean()); + AlterColumn("dbo.Product_SpecificationAttribute_Mapping", "ShowOnProductPage", c => c.Boolean()); + } + + public override void Down() + { + AlterColumn("dbo.Product_SpecificationAttribute_Mapping", "ShowOnProductPage", c => c.Boolean(nullable: false)); + AlterColumn("dbo.Product_SpecificationAttribute_Mapping", "AllowFiltering", c => c.Boolean(nullable: false)); + DropColumn("dbo.SpecificationAttribute", "AllowFiltering"); + DropColumn("dbo.SpecificationAttribute", "ShowOnProductPage"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.resx b/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.resx new file mode 100644 index 0000000000..dc3959442c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201704031946479_SpecificationAttributeAllowFiltering.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/201705032238367_RenameColorSquaresRgb.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.Designer.cs new file mode 100644 index 0000000000..3ab42f2a66 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.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.1.3-40302")] + public sealed partial class RenameColorSquaresRgb : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(RenameColorSquaresRgb)); + + string IMigrationMetadata.Id + { + get { return "201705032238367_RenameColorSquaresRgb"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.cs b/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.cs new file mode 100644 index 0000000000..0a953a1a0b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.cs @@ -0,0 +1,63 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + using Setup; + + public partial class RenameColorSquaresRgb : DbMigration, IDataSeeder + { + private bool IsSqlServer + { + get + { + return HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer; + } + } + + public override void Up() + { + if (IsSqlServer) + { + RenameColumn("dbo.ProductAttributeOption", "ColorSquaresRgb", "Color"); + RenameColumn("dbo.ProductVariantAttributeValue", "ColorSquaresRgb", "Color"); + } + else + { + AddColumn("dbo.ProductAttributeOption", "Color", c => c.String(maxLength: 100)); + AddColumn("dbo.ProductVariantAttributeValue", "Color", c => c.String(maxLength: 100)); + } + } + + public override void Down() + { + if (IsSqlServer) + { + RenameColumn("dbo.ProductVariantAttributeValue", "Color", "ColorSquaresRgb"); + RenameColumn("dbo.ProductAttributeOption", "Color", "ColorSquaresRgb"); + } + else + { + DropColumn("dbo.ProductVariantAttributeValue", "Color"); + DropColumn("dbo.ProductAttributeOption", "Color"); + } + } + + public void Seed(SmartObjectContext context) + { + if (!IsSqlServer) + { + context.Execute("Update [dbo].[ProductAttributeOption] Set [Color] = [ColorSquaresRgb]"); + context.Execute("Update [dbo].[ProductVariantAttributeValue] Set [Color] = [ColorSquaresRgb]"); + + context.Execute("Alter Table [dbo].[ProductAttributeOption] Drop Column [ColorSquaresRgb]"); + context.Execute("Alter Table [dbo].[ProductVariantAttributeValue] Drop Column [ColorSquaresRgb]"); + } + } + + public bool RollbackOnFailure + { + get { return false; } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.resx b/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.resx new file mode 100644 index 0000000000..1394eaee45 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705032238367_RenameColorSquaresRgb.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/201705102339006_V3Final.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.Designer.cs new file mode 100644 index 0000000000..e155f7909b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.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.1.3-40302")] + public sealed partial class V3Final : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V3Final)); + + string IMigrationMetadata.Id + { + get { return "201705102339006_V3Final"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs new file mode 100644 index 0000000000..79b6bd0ffa --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs @@ -0,0 +1,1026 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity; + using System.Data.Entity.Migrations; + using System.Linq; + using Core.Caching; + using Core.Domain.Configuration; + using Core.Infrastructure; + using Setup; + using Core.Domain.Security; + using Core.Domain.Catalog; + + public partial class V3Final : 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); + + // Remove permission records + var permissionRecords = context.Set(); + var csRecord = permissionRecords.Where(x => x.SystemName.Equals("ManageContentSlider")).FirstOrDefault(); + var quRecord = permissionRecords.Where(x => x.SystemName.Equals("ManageQuantityUnits")).FirstOrDefault(); + + if (csRecord != null) + permissionRecords.Remove(csRecord); + + if (quRecord != null) + permissionRecords.Remove(quRecord); + + // Add new product template... + context.Set().AddOrUpdate(x => x.ViewPath, new ProductTemplate + { + Name = "Default Product Template", + ViewPath = "Product", + DisplayOrder = 10 + }); + + // ...and set it as default where applicable + var newProductTemplate = context.Set().FirstOrDefault(x => x.Name == "Default Product Template") + ?? context.Set().OrderByDescending(x => x.Id).FirstOrDefault(); + + if (newProductTemplate != null) + { + context.ExecuteSqlCommand("Update [Product] Set [ProductTemplateId] = {0}", true, null, newProductTemplate.Id); + } + + // ...finally delete old ones + var oldProductTemples = context.Set().Where(x => x.ViewPath == "ProductTemplate.Simple" || x.ViewPath == "ProductTemplate.Grouped").ToList(); + context.Set().RemoveRange(oldProductTemples); + + context.SaveChanges(); + } + + public void MigrateSettings(SmartObjectContext context) + { + // Change ProductSortingEnum.Position > Relevance + var settings = context.Set().Where(x => x.Name == "CatalogSettings.DefaultSortOrder" && x.Value.StartsWith("Position")).ToList(); + if (settings.Any()) + { + settings.Each(x => x.Value = "Relevance"); + EngineContext.Current.Resolve().Clear(); + } + + context.MigrateSettings(x => { + x.DeleteGroup("ContentSlider"); + }); + + // Change MediaSettings.ProductThumbPictureSize to 250 if smaller + var keys = new string[] { "MediaSettings.ProductThumbPictureSize", "MediaSettings.CategoryThumbPictureSize", "MediaSettings.ManufacturerThumbPictureSize", "MediaSettings.CartThumbPictureSize", "MediaSettings.MiniCartThumbPictureSize" }; + settings = context.Set().Where(x => keys.Contains(x.Name)).ToList(); + if (settings.Any()) + { + settings.Each(x => + { + var size = x.Value.Convert(); + if (size < 250) + { + x.Value = "250"; + } + }); + } + + // Change MediaSettings.ProductDetailsPictureSize to 600 if smaller + var setting = context.Set().FirstOrDefault(x => x.Name == "MediaSettings.ProductDetailsPictureSize" || x.Name == "MediaSettings.AssociatedProductPictureSize"); + if (setting != null && setting.Value.Convert() < 600) + { + setting.Value = "600"; + } + + // Change MediaSettings.VariantValueThumbPictureSize to 70 if smaller + setting = context.Set().FirstOrDefault(x => x.Name == "MediaSettings.VariantValueThumbPictureSize"); + if (setting != null && setting.Value.Convert() < 70) + { + setting.Value = "70"; + } + + // Change CatalogSettings.PageShareCode (16px > 32px) + setting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.PageShareCode"); + if (setting != null) + { + setting.Value = "
"; + } + + // Change ThemeSettings.DefaultTheme to 'Flex' + setting = context.Set().FirstOrDefault(x => x.Name == "ThemeSettings.DefaultTheme"); + if (setting != null) + { + setting.Value = "Flex"; + } + + // Change CatalogSettings.ShowProductsFromSubcategories (to true) + setting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.ShowProductsFromSubcategories"); + if (setting != null) + { + setting.Value = "True"; + } + + // Change CatalogSettings.RecentlyAddedProductsNumber (to 100) + setting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.RecentlyAddedProductsNumber"); + if (setting != null && setting.Value.Convert() < 100) + { + setting.Value = "100"; + } + + // Change shoppingcartsettings.crosssellsnumber (to 24) + setting = context.Set().FirstOrDefault(x => x.Name == "shoppingcartsettings.crosssellsnumber"); + if (setting != null && setting.Value.Convert() < 24) + { + setting.Value = "24"; + } + + // Change CatalogSettings.ShowProductsFromSubcategories (to true) + setting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.ShowProductsFromSubcategories"); + if (setting != null) + { + setting.Value = "True"; + } + + // Change CatalogSettings.ShowManufacturerPictures to true + setting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.ShowManufacturerPictures"); + if (setting != null) + { + setting.Value = "True"; + } + + context.SaveChanges(); + + context.MigrateSettings(x => + { + x.Add("MediaSettings.DefaultThumbnailAspectRatio", 1); + x.Delete("ShoppingCartSettings.MiniShoppingCartProductNumber"); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.BadgeText", + "Badge text", + "Badge-Text", + "Gets or sets the text of the badge which will be displayed next to the category link within menus.", + "Legt den Text der Badge fest, die innerhalb von Menus neben den Menueinträgen dargestellt wird."); + + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.BadgeStyle", + "Badge style", + "Badge-Style", + "Gets or sets the type of the badge which will be displayed next to the category link within menus.", + "Legt den Stil der Badge fest, die innerhalb von Menus neben den Menueinträgen dargestellt wird."); + + builder.AddOrUpdate("Admin.Header.ClearDbCache", + "Clear database cache", + "Datenbank Cache löschen"); + + builder.AddOrUpdate("Admin.System.Warnings.TaskScheduler.OK", + "The task scheduler can poll and execute tasks.", + "Der Task-Scheduler kann Hintergrund-Aufgaben planen und ausführen."); + + builder.AddOrUpdate("Admin.System.Warnings.TaskScheduler.Fail", + "The task scheduler cannot poll and execute tasks. Base URL: {0}, Status: {1}. Please specify a working base url in web.config, setting 'sm:TaskSchedulerBaseUrl'.", + "Der Task-Scheduler kann keine Hintergrund-Aufgaben planen und ausführen. Basis-URL: {0}, Status: {1}. Bitte legen Sie eine vom Webserver erreichbare Basis-URL in der web.config Datei fest, Einstellung: 'sm:TaskSchedulerBaseUrl'."); + + builder.AddOrUpdate("Products.NotFound", + "The product with ID {0} was not found.", + "Das Produkt mit der ID {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Products.Deleted", + "The product with ID {0} has been deleted.", + "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"); + + // Search + builder.Delete("PageTitle.Search"); + builder.Delete("Search.ResultFor"); + + builder.AddOrUpdate("Search", "Search", "Suchen"); + builder.AddOrUpdate("Search.Title", "Search", "Suche"); + builder.AddOrUpdate("Search.PageTitle", "Search result for {0}", "Suchergebnis für {0}"); + builder.AddOrUpdate("Search.PagingInfo", "{0} of {1}", "{0} von {1}"); + builder.AddOrUpdate("Search.DidYouMean", "Did you mean?", "Meinten Sie?"); + builder.AddOrUpdate("Search.Hits", "Hits", "Treffer"); + builder.AddOrUpdate("Search.NoResultsText", "Your search did not match any products.", "Ihre Suche ergab leider keine Produkttreffer."); + builder.AddOrUpdate("Search.FilterNoResultsText", "Your filter did not match any products.", "Ihr Filter ergab leider keine Produkttreffer."); + builder.AddOrUpdate("Search.NumHits", "{0} Hits", "{0} Treffer"); + builder.AddOrUpdate("Search.InstantSearch", "Instant Search", "Instantsuche"); + builder.AddOrUpdate("Search.ResultFiltering", "Result filtering", "Ergebnisfilterung"); + builder.AddOrUpdate("Search.FilterBy", "Filter by", "Filtern nach"); + builder.AddOrUpdate("Search.TermInCategory", "in {0}", "in {0}"); + builder.AddOrUpdate("Search.TermFromBrand", "from {0}", "von {0}"); + builder.AddOrUpdate("Search.SearchBox.Tooltip", "What are you looking for?", "Wonach suchen Sie?"); + builder.AddOrUpdate("Search.SearchTermMinimumLengthIsNCharacters", + "The minimum length for the search term is {0} characters.", + "Die Mindestlänge für den Suchbegriff beträgt {0} Zeichen."); + builder.AddOrUpdate("Search.TermCorrectedHint", + "Displaying results for {0}. Your search for {1} did not match any results.", + "Ergebnisse für {0} werden angezeigt. Ihre Suche nach {1} ergab leider keine Treffer."); + + builder.Delete("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.Position"); + + builder.Delete( + "Admin.Configuration.Settings.Catalog.ProductSearchAutoCompleteEnabled", + "Admin.Configuration.Settings.Catalog.ProductSearchAutoCompleteEnabled.Hint", + "Admin.Configuration.Settings.Catalog.ProductSearchAutoCompleteNumberOfProducts", + "Admin.Configuration.Settings.Catalog.ProductSearchAutoCompleteNumberOfProducts.Hint", + "Admin.Configuration.Settings.Catalog.SearchPageProductsPerPage", + "Admin.Configuration.Settings.Catalog.SearchPageProductsPerPage.Hint", + "Admin.Configuration.Settings.Catalog.ProductSearchAllowCustomersToSelectPageSize", + "Admin.Configuration.Settings.Catalog.ProductSearchAllowCustomersToSelectPageSize.Hint", + "Admin.Configuration.Settings.Catalog.ProductSearchPageSizeOptions", + "Admin.Configuration.Settings.Catalog.ProductSearchPageSizeOptions.Hint", + "Admin.Configuration.Settings.Catalog.ShowProductImagesInSearchAutoComplete", + "Admin.Configuration.Settings.Catalog.ShowProductImagesInSearchAutoComplete.Hint", + "Admin.Configuration.Settings.Catalog.SuppressSkuSearch", + "Admin.Configuration.Settings.Catalog.SuppressSkuSearch.Hint", + "Admin.Configuration.Settings.Catalog.SearchDescriptions", + "Admin.Configuration.Settings.Catalog.SearchDescriptions.Hint", + "Admin.Configuration.Settings.Catalog.ProductSearchSettings", + "Admin.Configuration.Settings.Catalog.ProductsByTagPageSize", + "Admin.Configuration.Settings.Catalog.ProductsByTagPageSize.Hint" + ); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.InstantSearchEnabled", + "Enable Instant Search", + "Instant-Suche aktivieren", + "Activates Instant Search (Search-As-You-Type). Search hits and suggestions are already displayed before user finishes typing the search term.", + "Aktiviert die Instant-Suche (Search-As-You-Type). Suchtreffer und -Vorschläge werden schon während der Eingabe des Suchbegriffs angezeigt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.ShowProductImagesInInstantSearch", + "Show product images", + "Produktbilder anzeigen", + "Specifies whether to display product images in instant search.", + "Legt fest, ob Produktbilder in der Instantsuche angezeigt werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", + "Number of products", + "Produktanzahl", + "Specifies the number of product hits displayed in instant search.", + "Legt die Anzahl der angezeigten Produkt-Treffer in der Instantsuche fest."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.InstantSearchTermMinLength", + "Minimum search term length", + "Minimale Suchbegrifflänge", + "Specifies the minimum length of a search term from which to show the result of instant search.", + "Legt die minimale Länge eines Suchbegriffs fest, ab dem das Ergebnis der Instantsuche angezeigt wird."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchFields", + "Search fields", + "Suchfelder", + "Specifies additional search fields. The product name is always searched.", + "Legt zusätzlich zu durchsuchende Felder fest. Der Produktname wird grundsätzlich immer durchsucht."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DefaultProductListPageSize", + "Number of products displayed per page", + "Anzahl pro Seite angezeigter Produkte", + "Specifies the number of products displayed per page in a product list.", + "Legt die Anzahl der pro Seite angezeigten Produkte in einer Produktliste fest."); + + builder.AddOrUpdate("Admin.Validation.ValueRange", + "The value must be between {0} and {1}.", + "Der Wert muss zwischen {0} und {1} liegen."); + + builder.AddOrUpdate("Admin.Validation.ValueGreaterThan", + "The value must be greater than {0}.", + "Der Wert muss größer als {0} sein."); + + builder.AddOrUpdate("Admin.Validation.InvalidPath", + "The path \"{0}\" is invalid. Please enter a valid path.", + "Der Pfad \"{0}\" ist ungültig. Bitte geben Sie einen gültigen Pfad ein."); + + builder.AddOrUpdate("Common.AdditionalShippingSurcharge", + "zzgl. {0} zusätzlicher Versandgebühr", + "Plus {0} shipping surcharge"); + + builder.DeleteFor("Admin.Configuration.ContentSlider"); + builder.DeleteFor("Admin.ContentManagement.ContentSlider"); + builder.DeleteFor("Admin.ContentSlider.Slide"); + builder.Delete("Admin.Themes.ContentSlider"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowShortDescriptionInGridStyleLists", + "Show short description in product lists", + "Zeige Kurzbeschreibung in Produktlisten", + "Specifies whether the product short description should be displayed in product lists", + "Legt fest, ob die Produkt-Kurzbeschreibung auch in Produktlisten angezeigt werden sollen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerInGridStyleLists", + "Show brand in product lists", + "Zeige Hersteller/Marke in Produktlisten", + "Specifies whether the brand name should be displayed in grid style product lists", + "Legt fest, ob der Markenname auch in Rasterstil Produktlisten angezeigt werden sollen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerLogoInLists", + "Show brand logo instead of name", + "Zeige Marken-Logo statt -Name", + "Specifies whether the brand logo should be displayed in line style product lists. Falls back to textual name if no logo has been uploaded.", + "Legt fest, ob das Marken-Logo in Produktlisten dargestellt werden soll (nicht anwendbar in Rasteransicht). Wenn kein Logo hochgeladen wurde, wird grundsätzlich der Name angezeigt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowProductOptionsInLists", + "Show variant names in product lists", + "Zeige Variantnamen in Produktlisten", + "Specifies whether variant names should be displayed in product lists", + "Legt fest, ob Variantnamen in Produktlisten angezeigt werden sollen"); + + builder.AddOrUpdate("Products.PlusOption", "Further option", "Weitere Option"); + builder.AddOrUpdate("Products.PlusOptions", "More options", "Weitere Optionen"); + + builder.AddOrUpdate("Products.Longdesc.More", "Show more", "Mehr anzeigen"); + builder.AddOrUpdate("Products.Longdesc.Less", "Show less", "Weniger anzeigen"); + + builder.AddOrUpdate("Products.Tags.ProductsTaggedWith", "Products tagged with {0}", "Produkte markiert mit {0}"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions", + "Page size options", + "Auswahlmöglichkeiten für Seitengröße", + "Comma-separated page size options that a customer can select in product lists.", + "Kommagetrennte Liste mit Optionen für Seitengröße, die ein Kunde in Produktlisten wählen kann."); + + builder.AddOrUpdate("Common.ListIsEmpty", "The list is empty.", "Die Liste ist leer."); + + builder.AddOrUpdate("Products.SortByX", "Sort by {0}", "Sortiere nach {0}"); + builder.AddOrUpdate("Products.SwitchToGrid", "Show", "Zur Rasteransicht wechseln"); + builder.AddOrUpdate("Products.SwitchToList", "Show", "Zur Listenansicht wechseln"); + builder.AddOrUpdate("Products.ToFilter", "Filter", "Filtern"); + builder.AddOrUpdate("Products.ToSort", "Sort", "Sortieren"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.CreatedOn", "Newest Arrivals", "Neu eingetroffen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.Initial", "Position", "Position"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.PriceAsc", "Price: Low to High", "Preis: aufsteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.PriceDesc", "Price: High to Low", "Preis: absteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.Relevance", "Relevance", "Beste Ergebnisse"); + + builder.AddOrUpdate("Pager.PageX", "Page {0}", "Seite {0}"); + builder.AddOrUpdate("Pager.XPerPage", "{0} per Page", "{0} pro Seite"); + builder.AddOrUpdate("Pager.PageXOfY", "Page {0} of {1}", "Seite {0} von {1}"); + builder.AddOrUpdate("Pager.PageXOfYShort", "{0} of {1}", "{0} von {1}"); + + builder.AddOrUpdate("Products.Price.OldPrice", "Regular", "Regulär"); + builder.AddOrUpdate("Products.Sku", "SKU", "Art.-Nr."); + builder.AddOrUpdate("Products.ChooseColorX", "Choose {0}", "{0} auswählen"); + + + builder.AddOrUpdate("Tax.LegalInfoShort", "Prices {0}, plus shipping", "Preise {0}, zzgl. Versandkosten"); + builder.AddOrUpdate("Tax.LegalInfoShort2", "Prices {0}, plus shipping", "Preise {0}, zzgl. Versandkosten"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Search.SearchMode.ExactMatch", "Is equal to (term)", "Ist gleich (Term)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Search.SearchMode.StartsWith", "Starts with (prefix)", "Beginnt mit (Prefix)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Search.SearchMode.Contains", "Contains (wildcard)", "Beinhaltet (Wildcard)"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", + "The wildcard mode can slow down the search for a large number of products.", + "Der Wildcard-Modus kann bei einer großen Anzahl an Produkten die Suche verlangsamen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchFieldsNote", + "The standard search supports the search fields Name, SKU and Short Description. For more fields, a search plugin like MegaSearch Plugin is required.", + "In der Standardsuche können die Felder Name, SKU und Kurzbeschreibung durchsucht werden. Für weitere Felder ist ein Such-Plugin wie bspw. dem MegaSearch Plugin notwendig."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchMode", + "Search mode", + "Suchmodus", + "Specifies the search mode. Please keep in mind that the search mode can - depending on catalog size - strongly affect search performance. 'Is equal to' is the fastest, 'Contains' the slowest.", + "Legt den Suchmodus fest. Bitte beachten Sie, dass der Suchmodus die Geschwindigkeit der Suche (abhängig von der Produktanzahl) beeinflusst. 'Ist gleich' ist am schnellsten, 'Beinhaltet' am langsamsten."); + + builder.AddOrUpdate("Admin.Configuration.DeliveryTimes.CannotDeleteAssignedProducts", + "The delivery time cannot be deleted. It has associated products or product variants.", + "Die Lieferzeit kann nicht gelöscht werden. Ihr sind Produkte oder Produktvarianten zugeordnet."); + + builder.AddOrUpdate("Media.Manufacturer.ImageLinkTitleFormat", "All products from {0}", "Alle Produkte von {0}"); + builder.AddOrUpdate("Manufacturers.List", "All Brands", "Alle Marken"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.GridStyleListColumnSpan", + "Products per row in grid style list", + "Anzahl Produkte pro Zeile in Rasteransicht", + "Sets the responsive behavior of the grid style product list. The wider the screen, the more products are arranged in a row.", + "Legt das responsive Verhalten der Produktliste in der Rasteransicht fest. Je breiter der Bildschirm, desto mehr Produkte werden in einer Zeile angeordnet."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.GridColumnSpan.Max2Cols", "Always 2 (mobile & desktop) -not recommended", "Immer 2 (Mobil & Desktop) - nicht empfohlen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.GridColumnSpan.Max3Cols", "2 (mobile) to 3 (desktop)", "2 (Mobil) bis 3 (Desktop)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.GridColumnSpan.Max4Cols", "2 (mobile) to 4 (desktop) - recommended", "2 (Mobil) bis 4 (Desktop) - empfohlen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.GridColumnSpan.Max5Cols", "2 (mobile) to 5 (desktop)", "2 (Mobil) bis 5 (Desktop)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.GridColumnSpan.Max6Cols", "2 (mobile) to 6 (desktop)", "2 (Mobil) bis 6 (Desktop)"); + + builder.AddOrUpdate("Catalog.Manufacturerall.Numbers", "#", "#"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.SortManufacturersAlphabetically", + "Sort manufacturers alphabetically", + "Hersteller alphabetisch sortieren", + "Specifies whether manufacturers on the manufacturer overview page will be displayed sorted alphabetically.", + "Legt fest ob Hersteller auf der Herstellerübersichtsseite alphabetisch sortiert dargestellt werden."); + + builder.AddOrUpdate("Common.NoImageAvail", "No image available", "Bild wird nachgereicht"); + + builder.AddOrUpdate("Products.Bundle.PriceWithoutDiscount.Note", "Instead of", "Statt"); + builder.AddOrUpdate("Products.Bundle.PriceWithDiscount.Note", "As bundle only", "Im Set nur"); + builder.AddOrUpdate("Products.Price", "Price", "Preis"); + builder.AddOrUpdate("Products.TierPrices", "Block pricing", "Staffelpreise"); + builder.AddOrUpdate("Products.ManufacturerPartNumber", "MPN", "MPN"); + builder.AddOrUpdate("Products.Details", "Description", "Beschreibung"); + builder.AddOrUpdate("Products.Specs", "Features", "Merkmale"); + builder.AddOrUpdate("Products.Availability.InStockWithQuantity", "{0} in stock", "{0} am Lager"); + builder.AddOrUpdate("Products.Availability.InStock", "In stock", "Vorrätig"); + builder.AddOrUpdate("Products.Availability.OutOfStock", "Out of stock", "Vergriffen"); + builder.AddOrUpdate("Products.NewProducts", "What's New", "Neu eingetroffen"); + + builder.AddOrUpdate("Reviews.Overview.First", "Be the first to review this item", "Geben Sie die erste Bewertung ab"); + builder.AddOrUpdate("Reviews.Overview.AddNew", "Write a review", "Bewertung schreiben"); + builder.AddOrUpdate("Reviews.Overview.ReadAll", "Read all reviews", "Alle Bewertungen lesen"); + builder.AddOrUpdate("Reviews.Empty", "There are no reviews yet", "Es liegen keine Bewertungen vor"); + builder.AddOrUpdate("Reviews.Fields.Rating", "Your rating?", "Ihre Bewertung?"); + builder.AddOrUpdate("Reviews.Fields.Title", "Headline for your review", "Titel Ihrer Bewertung"); + builder.AddOrUpdate("Reviews.Fields.ReviewText", "Your opinion on the product", "Ihre Meinung zum Produkt"); + builder.AddOrUpdate("Reviews.SubmitButton", "Submit review", "Bewertung absenden"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.FilterMinHitCount", + "Minimum hit count for filters", + "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."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.FilterMaxChoicesCount", + "Maximum number of filters", + "Maximale Anzahl an Filtern", + "Specifies the maximum number of filters per group.", + "Legt die maximale Anzahl an Filtern pro Gruppe fest."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.HitsDesc", + "Hit count: highest first", + "Trefferanzahl: Höchste zuerst"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.ValueAsc", + "Name: A to Z", + "Name: A bis Z"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.DisplayOrder", + "According to display order", + "Gemäß Reihenfolge"); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.ViewLink", + "Edit Options (Total: {0})", + "Optionen bearbeiten (Anzahl: {0})"); + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.EditAttributeDetails", + "Options for attribute '{0}'. Product: {1}", + "Optionen für Attribut '{0}'. Produkt: {1}"); + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values", "Options", "Optionen"); + builder.AddOrUpdate("Admin.Catalog.Attributes.CheckoutAttributes.Values", "Options", "Optionen"); + + + builder.AddOrUpdate("Common.CopyToClipboard.Failed", "Failed to copy.", "Kopieren ist fehlgeschlagen."); + + builder.AddOrUpdate("PDFPackagingSlip.Gtin", "EAN", "Gtin"); + + builder.AddOrUpdate("Common.Error.AliasAlreadyExists", + "An alias \"{0}\" already exists.", + "Ein Alias \"{0}\" existiert bereits."); + + builder.AddOrUpdate("Admin.Configuration.DeliveryTimes.Fields.IsDefault", + "Is default", + "Ist Standard", + "Specifies the default delivery time.", + "Bestimmt die Standard-Lieferzeit"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowDefaultDeliveryTime", + "Show default delivery time", + "Zeige Standard-Lieferzeit", + "Specifies whether to show the default delivery time if there is none assigned to a product.", + "Bestimmt ob die Standard-Lieferzeit für ein Produkt angezeigt wird, wenn dem Produkt keine Lieferzeit zugewiesen wurde."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.QuantityStep", + "Quantity step", + "Schrittweite", + "Specifies the incremental respectively decremental step on usage of +/-. Orderable quantities are limited to a multiple of this value.", + "Bestimmt den Wert, um den die Bestellmenge erhöht bzw. vermindert wird, wenn ein Kunde die +/- Steuerelemente benutzt. Die Bestellmenge ist auf ein Vielfaches dieses Wertes beschränkt."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.QuantiyControlType", + "Control type", + "Steuerelement", + "Specifies the control type to enter the quantity.", + "Bestimmt das Steuerelement für die Angabe der Bestellmenge."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.HideQuantityControl", + "Hide quantity control on product pages", + "Angabe der Bestellmenge auf Produktseiten nicht anbieten", + "Specifies whether to hide the quantity control on product pages.", + "Bestimmt ob eine Element zur Angabe der Bestellmenge auf Produktdetailseiten dargestellt wird."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerInProductDetail", + "Display manufacturer", + "Hersteller anzeigen.", + "Specifies whether the product manufacturer will be displayed on product detail pages.", + "Bestimmt ob der Hersteller eines Produktes auf Produktdetailseiten angezeigt wird."); + builder.AddOrUpdate("Admin.Catalog.Products.Fields.CustomsTariffNumber", + "Customs Tariff Number", + "Zolltarifnummer", + "Specifies the customs tariff number of the product.", + "Legt die Zolltarifnummer des Produktes fest."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.CountryOfOriginId", + "Country of Origin", + "Herkunftsland", + "Specifies the country of origin of the product.", + "Legt das Herkunftsland des Produktes fest."); + + builder.Delete( + "Products.ProductHasBeenAddedToTheWishlist.Link", + "Products.ProductHasBeenAddedToTheCart.Link"); + + builder.AddOrUpdate("ShoppingCart.AddToWishlist", "Add to wishlist", "Auf die Wunschliste"); + builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToCart", "The product {0} has been successfully added to your cart", "Das Produkt {0} wurde erfolgreich in den Warenkorb gelegt"); + builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToWishlist", "The product {0} has been added to your wishlist", "Das Produkt {0} wurde erfolgreich auf ihrer Wunschliste vermerkt"); + builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToCompare", "The product {0} has been successfully added to your compare list", "Das Produkt {0} wurde der Vergleichsliste erfolgreich hinzugefügt"); + builder.AddOrUpdate("ShoppingCart.Mini.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"); + builder.AddOrUpdate("ShoppingCart.Mini.EmptyCart.Info", + "You have not added any product to your cart yet. Use the icon to add a product to your cart.", + "Sie haben noch keine Produkte in ihren Warenkorb gelegt.
Benutzen Sie das Symbol, um ein Produkt in den Warenkorb zu legen."); + builder.AddOrUpdate("ShoppingCart.Mini.EmptyWishlist.Info", + "You have not added any product to your wishlist yet. Use the icon to add a product to your wishlist.", + "Sie haben noch keine Produkte auf ihrer Wunschliste vermerkt.
Benutzen Sie das Symbol, um ein Produkt in ihrer Wunschliste zu vermerken."); + builder.AddOrUpdate("ShoppingCart.Mini.EmptyCompare.Info", + "You have not added any product to your compare list yet. Use the icon to add a product to your compare list.", + "Sie haben noch keine Produkte in ihrer Vergleichsliste.
Benutzen Sie das Symbol, um ein Produkt in die Vergleichsliste aufzunehmen."); + + builder.AddOrUpdate("ShoppingCart.UnitPrice", "Price", "Preis"); + + builder.AddOrUpdate("ShoppingCart.DiscountCouponCode", "I have a discount code", "Ich habe einen Rabattcode"); + builder.AddOrUpdate("ShoppingCart.GiftCardCouponCode", "I have a gift card", "Ich habe einen Gutschein"); + builder.AddOrUpdate("ShoppingCart.EstimateShipping", "Estimate shipping", "Versandkosten schätzen"); + + builder.AddOrUpdate("PageTitle.Blog.Month", "Blog entries in {0}", "Blog Einträge des Monats {0}"); + builder.AddOrUpdate("PageTitle.Blog.Tag", "Blog entries for the tag {0}", "Blog-Einträge für das Stichwort {0}"); + builder.AddOrUpdate("Metadesc.Blog.Month", "Blog entries in {0}", "Blog Einträge des Monats {0}"); + builder.AddOrUpdate("Metadesc.Blog.Tag", "Blog entries for the tag {0}", "Blog-Einträge für das Stichwort {0}"); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.Picture", + "Picture", + "Bild", + "Choose a picture which will be displayed as the selector for the attribute.", + "Wählen Sie ein Bild, welches als Auswahlelement für das Attribut angezeigt werden soll."); + + builder.Delete( + "Admin.Configuration.Settings.ShoppingCart.MiniShoppingCartProductNumber", + "Admin.Configuration.Settings.ShoppingCart.MiniShoppingCartProductNumber.Hint"); + + builder.AddOrUpdate("ShoppingCart.MoveToWishlist", "Move to wishlist", "In die Wunschliste verschieben"); + builder.AddOrUpdate("Products.Compare.CompareNow", "Compare now", "Jetzt vergleichen"); + builder.AddOrUpdate("Common.Or", "Or", "Oder"); + + builder.AddOrUpdate("Common.Error.OptionAlreadyExists", + "The option \"{0}\" already exists.", + "Die Option \"{0}\" existiert bereits."); + + builder.AddOrUpdate("Common.Error.ChooseDifferentValue", + "Please choose a different value.", + "Bitte wählen Sie einen anderen Wert."); + + builder.AddOrUpdate("Common.Menu", "Menu", "Menü"); + + builder.Delete( + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Supported", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.NotSupported", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.CurrenlyEnabled", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.CurrenlyDisabled", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Disable", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Disabled", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Enable", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Enabled", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.SearchMode", + "Admin.Configuration.Settings.GeneralCommon.FullTextSettings.SearchMode.Hint", + "Enums.SmartStore.Core.Domain.Common.FulltextSearchMode.ExactMatch", + "Enums.SmartStore.Core.Domain.Common.FulltextSearchMode.And", + "Enums.SmartStore.Core.Domain.Common.FulltextSearchMode.Or" + ); + + builder.AddOrUpdate("Common.Options.Count", "Number options", "Anzahl Optionen"); + 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."); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.NumberOfCopiedOptions", + "{0} option(s) have been copied.", + "Es wurden {0} Option(en) kopiert."); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.CopyOptions", + "Copy set options", + "Set Optionen übernehmen"); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.CopyOptionsHint", + "Would you like to copy all options from set \"{0}\"?", + "Möchten Sie alle Optionen von Set \"{0}\" übernehmen?"); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.AskExistingValues", + "What should be done with already existing options?", + "Was soll mit den bereits vorhandenen Optionen geschehen?"); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.MergeExistingValues", + "Merge all options", + "Alle Optionen zusammenführen"); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.DeleteExistingValues", + "Delete existing options", + "Vorhandene Optionen löschen"); + + builder.AddOrUpdate("Offcanvas.Menu.Categories", "Categories", "Sortiment"); + builder.AddOrUpdate("Offcanvas.Menu.Brands", "Brands", "Marken"); + builder.AddOrUpdate("Offcanvas.Menu.Service", "Service", "Service"); + builder.AddOrUpdate("Offcanvas.Menu.ShowCurrentCat", "Show {0}", "{0} anzeigen"); + + var aliasHintEn = "Seo-compliant URL alias for search filters (optional)."; + var aliasHintDe = "SEO-konformer URL-Alias für Suchfilter (optional)."; + + 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); + + builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.Alias.Hint", aliasHintEn, aliasHintDe); + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.Alias.Hint", aliasHintEn, aliasHintDe); + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.Alias.Hint", aliasHintEn, aliasHintDe); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.CommonFacet.Alias", "Alias", "Alias", aliasHintEn, aliasHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.OptionsSets", "Option sets", "Options-Sets"); + + builder.Delete( + "Products.Filter.Remove", + "Products.Filter.ShowAll", + "Products.Filter.SelectMultiple", + "Products.Filter.NoneFound", + "Products.Filter.Contains", + "Products.Filter.StartsWith", + "Products.Filter.EndsWith", + "Filtering.FilterResults", + "Admin.Configuration.Settings.Catalog.FilterEnabled", + "Admin.Configuration.Settings.Catalog.FilterEnabled.Hint", + "Admin.Configuration.Settings.Catalog.MaxFilterItemsToDisplay", + "Admin.Configuration.Settings.Catalog.MaxFilterItemsToDisplay.Hint", + "Admin.Configuration.Settings.Catalog.ExpandAllFilterCriteria", + "Admin.Configuration.Settings.Catalog.ExpandAllFilterCriteria.Hint", + "Admin.Configuration.Settings.Catalog.SortFilterResultsByMatches", + "Admin.Configuration.Settings.Catalog.SortFilterResultsByMatches.Hint" + ); + + builder.AddOrUpdate("Search.Facet.Category", "Category", "Kategorie"); + builder.AddOrUpdate("Search.Facet.Manufacturer", "Brand", "Marke"); + builder.AddOrUpdate("Search.Facet.Price", "Price", "Preis"); + builder.AddOrUpdate("Search.Facet.Rating", "Rating", "Bewertung"); + builder.AddOrUpdate("Search.Facet.DeliveryTime", "Delivery Time", "Lieferzeit"); + builder.AddOrUpdate("Search.Facet.Availability", "Availability", "Verfügbarkeit"); + builder.AddOrUpdate("Search.Facet.NewArrivals", "New Arrivals", "Neuheiten"); + + builder.AddOrUpdate("Search.Facet.RangeMin", "from {0}", "ab {0}"); + builder.AddOrUpdate("Search.Facet.RangeMax", "up to {0}", "bis {0}"); + builder.AddOrUpdate("Search.Facet.RangeBetween", "{0} - {1}", "{0} - {1}"); + + builder.AddOrUpdate("Search.Facet.FindPlaceholder", "Find {0}...", "{0} suchen..."); + builder.AddOrUpdate("Search.Facet.SelectedCount", "{0} selected", "{0} ausgewählt"); + builder.AddOrUpdate("Search.Facet.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"); + builder.AddOrUpdate("Search.Facet.1StarAndMore", "1 star & more", "1 Stern & mehr"); + builder.AddOrUpdate("Search.Facet.XStarsAndMore", "{0} stars & more", "{0} Sterne & mehr"); + builder.AddOrUpdate("Search.Facet.StarsAndMore", "& more", "& mehr"); + builder.AddOrUpdate("Search.Facet.LastDays", "Last {0} days", "Letzten {0} Tage"); + builder.AddOrUpdate("Search.Facet.IncludeOutOfStock", "Include Out of Stock", "Nicht verfügbare Artikel einschließen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.ForceSslForAllPages", + "Always use SSL", + "Immer SSL verwenden", + "Specifies whether to SSL secure all request.", + "Legt fest, dass alle Anfragen SSL gesichert werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.Checkboxes", + "Checkboxes", + "Kontrollkästchen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.Custom", + "Boxes (color & image)", + "Kästchen (Farbe & Bild)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.NumericRange", + "Numeric range", + "Numerischer Bereich"); + + var megaSearchPlusHintEn = "This setting is only effective by using the 'MegaSearchPlus' plugin. Changes will take effect after next update of the search index."; + var megaSearchPlusHintDe = "Diese Einstellung ist nur unter Verwendung des 'MegaSearchPlus' Plugins wirksam. Änderungen werden nach der nächsten Aktualisierung des Suchindex wirksam."; + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.FacetSorting", + "Sorting of search filters", + "Sortierung der Suchfilter", + "Specifies the sorting of the search filters. " + megaSearchPlusHintEn, + "Legt die Sortierung der Suchfilter fest. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.FacetTemplateHint", + "Search filter presentation", + "Darstellung der Suchfilter", + "Specifies the presentation of search filters. " + megaSearchPlusHintEn, + "Legt die Darstellung der Suchfilter fest. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.AllowFiltering", + "Allow filtering", + "Filtern ermöglichen", + "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, + "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden können. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Products.SpecificationAttributes.Fields.AllowFiltering", + "Allow filtering", + "Filtern zulassen", + "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, + "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden können. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.AllowFiltering", + "Allow filtering", + "Filtern zulassen", + "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, + "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden können. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.FacetTemplateHint", + "Search filter UI type", + "Suchfilter Darstellung", + "Specifies the search filter UI type. " + megaSearchPlusHintEn, + "Legt die Darstellung der Suchfilter fest. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Options.Fields.NumberValue", + "Numeric value", + "Numerischer Wert", + "Specifies a numeric value to enbale range filtering (e.g. light red to dark red). \"Numeric range\" must be specified as search filter presentation for the attribute. " + megaSearchPlusHintEn, + "Legt einen numerischen Wert fest, um eine Bereichsfilterung zu ermöglichen (z.B. hellrot bis dunkelrot). Für das Attribut muss \"Numerischer Bereich\" als Suchfilterdarstellung festgelegt sein. " + megaSearchPlusHintDe); + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.ShowOnProductPage", + "Show on product page", + "Auf der Produktseite anzeigen", + "Check the box to display the attribute on the product detail page.", + "Legt fest, ob das Attribut auf der Produktdetailseite angezeigt werden soll."); + + + + builder.AddOrUpdate("Account.Administration", "Admin", "Admin"); + builder.AddOrUpdate("Account.PasswordRecovery", "Reset password", "Passwort zurücksetzen"); + + + builder.AddOrUpdate("Common.Shopbar.BasketPartOne", "Shopping", "Waren"); + builder.AddOrUpdate("Common.Shopbar.BasketPartTwo", "Basket", "Korb"); + + builder.AddOrUpdate("Common.From", "From", "Von"); + builder.AddOrUpdate("Common.To", "To", "Bis"); + builder.AddOrUpdate("Common.Any", "Any", "Beliebig"); + + builder.Delete( + "Admin.Catalog.Attributes.SpecificationAttributes.Bundled.Description", + "Admin.Catalog.Attributes.SpecificationAttributes.Bundled.AllowFiltering", + "Admin.Catalog.Attributes.SpecificationAttributes.Bundled.DisallowFiltering", + "Admin.Catalog.Attributes.SpecificationAttributes.Bundled.ShowOnProductPage", + "Admin.Catalog.Attributes.SpecificationAttributes.Bundled.ShowNotOnProductPage"); + + builder.AddOrUpdate("Footer.Info", "Informations", "Informationen"); + + builder.AddOrUpdate("Admin.ContentManagement.Polls.Fields.SystemKeyword", + "System keyword", + "System-Schlüsselwort", + "The system keyword specifies the place in your shop where the poll will be displayed. Available system keywords are: MyAccountMenu, Blog", + "Das System-Schlüsselwort bestimmt den Platz im Shop, an welchem die Umfrage dargestellt wird. Verfügbare System-Schlüsselwörter sind: MyAccountMenu, Blog"); + + builder.Delete("Admin.Configuration.Settings.Catalog.ManufacturersBlockItemsToDisplay"); + + builder.AddOrUpdate("Homepage.TopBrands", "Top Brands", "Top-Marken"); + builder.AddOrUpdate("Homepage.Brands.ShowAll", "Show all", "Alle anzeigen"); + + builder.AddOrUpdate("Account.Fields.ZipPostalCode", "Zip code", "PLZ"); + builder.AddOrUpdate("Account.CustomerReturnRequests.Reason", "Return reason", "Rücksendegrund"); + builder.AddOrUpdate("Account.CustomerReturnRequests.Action", "Return action", "Rücksendeaktion"); + builder.AddOrUpdate("Account.CustomerReturnRequests.Date", "Date Requested", "Anfragedatum"); + builder.AddOrUpdate("Account.CustomerReturnRequests.Item", "Item", "Artikel"); + + builder.AddOrUpdate("Account.CreateAccount", "Create account", "Konto erstellen"); + builder.AddOrUpdate("Account.Register.Button", "Register", "Registrieren"); + + builder.AddOrUpdate("Account.Login.Welcome", "Sign In", "Anmeldung"); + builder.AddOrUpdate("Account.Login.ReturningCustomer", "I am already registered", "Ich bin bereits registriert"); + + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturersOnHomepage", + "Display manufacturers on homepage", + "Zeige Hersteller auf Startseite", + "Specifies whether manufacturers will be displayed on the homepage.", + "Legt fest, ob Hersteller auf der Startseite angezeigt werden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturersInOffCanvas", + "Display manufacturers in offcanvas menu", + "Zeige Hersteller in OffCanvas-Menu", + "Specifies whether manufacturers will be displayed in offcanvas menu.", + "Legt fest, ob Hersteller im OffCanvas-Menu angezeigt werden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ManufacturerItemsToDisplayOnHomepage", + "Amount of manufacturers to display on homepage", + "Anzahl der anzuzeigenden Hersteller auf der Homepage", + "Specifies the amount of manufacturers to display on homepage.", + "Bestimmt die Anzahl der anzuzeigenden Hersteller auf der Homepage."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerPictures", + "Display manufacturer pictures in lists", + "Herstellerbilder in Listen anzeigen", + "Specifies whether manufacturer pictures will be displayed in lists. This setting effects all partial manufacturer lists in the shop (e.g. homepage, offcanvas menu).", + "Bestimmt ob Herstellerbilder in Listen angezeigt werden sollen. Diese Einstellung betrifft alle partiellen Herstellerlisten im Shop (z.B. Homepage, OffCanvas-Menu)."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ManufacturerItemsToDisplayInOffCanvasMenu", + "Amount of manufacturers to display in offcanvas menu", + "Anzahl der anzuzeigenden Hersteller im OffCanvas-Menu", + "Specifies the amount of manufacturers to display in offcanvas menu.", + "Bestimmt die Anzahl der anzuzeigenden Hersteller im OffCanvas-Menu."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.PageShareCode", + "Share button widget code", + "Share-Button Widget-Code", + @"Specifies the code to render the share button widget. By going to addthis.com you can create your own widget code and paste it here. + This way you can configure the display type of the widget as well as get statistic insight.", + @"Legt den Code des Share-Button Widgets fest. Gehen Sie zu addthis.com um Ihren eigenen Widget-Code zu erhalten und fügen Sie diesen hier ein. + Auf diese Weise können Sie die Darstellung des Widgets selbst bestimmen, sowie Statistiken einsehen."); + + builder.AddOrUpdate("Order.CannotCancel") + .Value("de", "Die Bestellung kann nicht storniert werden."); + builder.AddOrUpdate("Order.CannotCapture") + .Value("de", "Die Bestellung nicht gebucht werden."); + builder.AddOrUpdate("Order.CannotMarkCompleted") + .Value("de", "Die Bestellung kann nicht abgeschlossen werden."); + builder.AddOrUpdate("Order.CannotMarkPaid") + .Value("de", "Die Bestellung kann nicht als bezahlt markiert werden."); + builder.AddOrUpdate("Order.CannotPartialRefund") + .Value("de", "Eine Teilrückerstattung ist für diese Bestellung nicht möglich."); + builder.AddOrUpdate("Order.CannotRefund") + .Value("de", "Eine Rückerstattung ist für diese Bestellung nicht möglich."); + builder.AddOrUpdate("Order.CannotVoid") + .Value("de", "Eine Stornierung dieser Bestellung ist nicht möglich."); + builder.AddOrUpdate("Order.CompletePayment.Hint") + .Value("de", "Die Bestellung wurde noch nicht bezahlt. Um die Zahlung nun vorzunehmen, klicken Sie die Schaltfläche 'Zahlung veranlassen'"); + builder.AddOrUpdate("Order.getpdfinvoice") + .Value("de", "Bestellung als PDF"); + builder.AddOrUpdate("Order.NotFound") + .Value("de", "Die Bestellung {0} konnte nicht gefunden werden."); + builder.AddOrUpdate("Order.Order#") + .Value("de", "Bestellnr."); + builder.AddOrUpdate("Order.OrderDate") + .Value("de", "Bestellt am"); + builder.AddOrUpdate("Order.OrderDetails") + .Value("de", "Bestelldetails"); + builder.AddOrUpdate("Order.OrderStatus") + .Value("de", "Bestellstatus"); + builder.AddOrUpdate("Order.Shipments.Order#") + .Value("de", "Bestellnr."); + builder.AddOrUpdate("PDFInvoice.Order#") + .Value("de", "Bestellnr."); + builder.AddOrUpdate("PDFPackagingSlip.Order") + .Value("de", "Bestellung"); + builder.AddOrUpdate("Account.CustomerOrders") + .Value("de", "Bestellungen"); + builder.AddOrUpdate("Account.CustomerOrders.NoOrders") + .Value("de", "Keine Bestellungen"); + builder.AddOrUpdate("Account.CustomerOrders.NotYourOrder") + .Value("de", "Diese Bestellung konnte Ihnen nicht zugeordnet werden."); + builder.AddOrUpdate("Account.CustomerOrders.RecurringOrders.InitialOrder") + .Value("de", "Ursprüngliche Bestellung"); + builder.AddOrUpdate("Account.CustomerOrders.RecurringOrders.ViewInitialOrder") + .Value("de", "Bestellungsansicht (ID - {0})"); + + builder.AddOrUpdate("Order.Product(s).Item", "Item", "Artikel"); + builder.AddOrUpdate("Order.Product(s).Total", "Total", "Gesamt"); + builder.AddOrUpdate("Order.Product(s).SKU", "SKU", "Art.-Nr."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowPopularProductTagsOnHomepage", + "Show popular product tags on homepage", + "Zeige Produkt-Tags auf der Startseite", + "Specifies whether to show popular product tags on the homepage.", + "Bestimmt ob beliebte Produkt-Tags auf der Startseite angezeigt werden."); + + builder.AddOrUpdate("Search.IndexingRequiredNotification", + "This is the default search. For advanced search, indexing is required. Now start indexing or open configuration.", + "Hierbei handelt es sich um die Standardsuche. Für die erweiterte Suche ist eine Indexierung erforderlich. Indexierung jetzt starten oder Konfiguration aufrufen."); + + builder.Delete("ShoppingCart.UpdateCartItem", "ShoppingCart.UpdateCart"); + builder.AddOrUpdate("ShoppingCart.SKU", "SKU", "Art.-Nr."); + + builder.AddOrUpdate("Products.ProductsHaveBeenAddedToTheCart", + "The selected products have successfully been added to the cart.", + "Die von Ihnen gewählten Produkte wurden in den Warenkorb gelegt."); + + builder.AddOrUpdate("Forum.TopicSubject", + "Topic subject", + "Thema"); + + builder.AddOrUpdate("ShoppingCart.AlternativePaymentButtons", "- OR -", "- ODER -"); + + builder.AddOrUpdate("Admin.Configuration.Themes.Notifications.ConfigureError", + "SASS CSS Parser Error: Your changes were not saved because your configuration would lead to an error in the shop. For details see report.", + "SASS CSS Parser Fehler: Ihre Änderungen wurden nicht gespeichert, da Ihre Konfiguration zu einem Fehler im Shop führen würde. Details siehe Fehlerbericht."); + builder.AddOrUpdate("Admin.Configuration.Themes.Validation.ErrorReportTitle", + "SASS parser error report", + "SASS Parser Fehlerbericht"); + + builder.Delete("Enums.SmartStore.Core.Domain.Catalog.AttributeControlType.ColorSquares"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.AttributeControlType.Boxes", + "Boxes (color & image)", + "Kästchen (Farbe & Bild)"); + + builder.Delete( + "Admin.Themes.Grid", + "Admin.Themes.Shopbar", + "Admin.Themes.ContentSlider", + "Admin.Themes.Tables" + // "Admin.Themes.Footer" ??? + ); + + builder.AddOrUpdate("Admin.Themes.Colors", "Colors", "Farben"); + builder.AddOrUpdate("Admin.Themes.States", "Messages", "Meldungen"); + builder.AddOrUpdate("Admin.Themes.Components", "Components", "Komponenten"); + + builder.AddOrUpdate("Order.CompletePayment.AdminNote", + "The payment is pending. The buyer can make the payment by clicking on the Complete payment button on the order details page.", + "Die Zahlung ist ausstehend. Der Käufer kann die Zahlung durchführen, indem er auf der Bestelldetailseite den Button Zahlung veranlassen klickt."); + + builder.AddOrUpdate("Admin.ThemeVar.CostepPogressColor", + "Specifies the color of the checkout progress bar.", + "Legt die Farbe der Fortschrittsanzeige des Checkoutprozesses fest."); + + builder.AddOrUpdate("Admin.Theme.GoogleFonts.Hint", + @"

To embed Google Fonts please not the following advices.

+
    +
  • + Go to https://fonts.google.com/ and choose the font-families you want to use in your store. +
  • +
  • + The Html code that will be provided to you to embed the choosen fonts into your website looks like this: +
    <link href=""https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Ubuntu"" rel=""stylesheet"">
    +
  • +
  • + Copy the bold marked part of the link and enter it into one of the threee fields within the theme configurator that's intended for this purpose. +
  • +
  • + Now you can use the fonts by entering the name of a font-family (e.g. Roboto) into one of the input fields which are + provided to configure font-families (e.g. $font-family-sans-serif). +
  • +
", + @"

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

+
    +
  • + Gehen Sie zu https://fonts.google.com/ und wählen Sie die Schriftarten, die Sie in Ihrem Shop verwenden möchten. +
  • +
  • + Als Html-Code für Ihre Webseite, wird Ihnen ein Link in folgender Form angeboten: +
    <link href=""https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Ubuntu"" rel=""stylesheet"">
    +
  • +
  • + Fügen Sie den fett markierten Teil des Links in eins der drei Felder des Theme-Konfigurators ein, die für Google Fonts vorgesehen sind. +
  • +
  • + 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). +
  • +
"); + + builder.AddOrUpdate("PrivateMessages.NoInboxItems.Title", + "Your inbox is empty", + "Ihr Posteingang ist leer"); + builder.AddOrUpdate("PrivateMessages.NoInboxItems.Info", + "There are no items to be displayed in your email inbox.", + "Es befinden sich keine anzuzeigenden Nachrichten in Ihrem Posteingang."); + builder.AddOrUpdate("PrivateMessages.NoSentItems.Title", + "No sent items", + "Keine versendeten Nachrichten"); + builder.AddOrUpdate("PrivateMessages.NoSentItems.Info", + "There are no sent items to be displayed.", + "Sie haben noch keine Nachrichten versendet."); + + builder.AddOrUpdate("Admin.ContentManagement.Homepage", "Homepage", "Startseite"); + builder.AddOrUpdate("Products.SavingBadgeLabel", "- {0} %", "- {0} %"); + builder.AddOrUpdate("Admin.Themes.Sections", "Sections", "Sektionen"); + + builder.AddOrUpdate("Admin.ThemeVar.Boxed", + "Specifies whether the site will strech over the complete avaliable space.", + "Legt fest, ob sich die Seite über den kompletten verfügabren Platz streckt."); + builder.AddOrUpdate("Admin.ThemeVar.ArtActiveBgColor", + "Specifies the background color for product boxes when hovering over them.", + "Legt die Hintergrundfarbe von Produktboxen für den Hover-Effekt fest."); + builder.AddOrUpdate("Admin.ThemeVar.ArtActiveBorderColor", + "Specifies the border color for product boxes when hovering over them.", + "Legt die Rahmenfarbe von Produktboxen für den Hover-Effekt fest."); + builder.AddOrUpdate("Content.CopyrightNotice", + "Copyright © {0} {1}. All rights reserved.", + "Copyright © {0} {1}. Alle Rechte vorbehalten."); + + builder.AddOrUpdate("Common.CannotCopyFolderIntoItself", + "Cannot copy a folder into itself.", + "Ein Ordner kann nicht in sich selbst kopiert werden."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.resx b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.resx new file mode 100644 index 0000000000..1394eaee45 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.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 + + + H4sIAAAAAAAEAOy923IcO7Io9u4I/4NCT8eOfaQlrRnHPhNr+QRJUUuMLYkcNiXtPS+MYjdIYlRd1VMXXsbhL/ODP8m/YAB1QwGJO6q6m9MvUrOQSACJRCKRSGT+f//P//vb/3xap68eUFHiPPv99bs3v7x+hbJlvsLZ3e+v6+r2v//76//5f/6v/8tvp6v106vvHdyvFI7UzMrfX99X1eYvb9+Wy3u0Tso3a7ws8jK/rd4s8/XbZJW/ff/LL//j7bt3bxFB8ZrgevXqt8s6q/AasT/Inyd5tkSbqk7SL/kKpWX7nZQsGNZXX5M1KjfJEv3+erFOimpR5QV68yGpktevjlKckG4sUHr7+lWSZXmVVKSTf/lWokVV5NndYkM+JOnV8wYRuNskLVHb+b8M4Lbj+OU9HcfboWKHalmXVb52RPju15Ywb8XqXuR93ROOkO6UkLh6pqNm5Pv99VW+wcvXr8SW/nKSFhRqRNoTRl8ChrM3rF7Z/PdvrwSgf+uZ4v2b/+PNL29++bdXJ3Va1QX6PUN1VSTpv726qG9SvPwP9HyV/0TZ71mdpnxPSV9J2egD+XRR5BtUVM+X6Lbt/9nq9au343pvxYp9Na5OM7izrPr1/etXX0njyU2KekbgCMFG9QfKUJFUaHWRVBUqMooDMVJKrQttLZ7LCq3p765Nwn9kHb1+9SV5+oyyu+r+99fk5+tXH/ETWnVf2n58yzBZdqRSVdTI1NRZtkzrFTrLFpg0mWy6Bo/zPEVJBgzTgK+8SMryMS9WpKBCSzL8UJQdwslpcYWrdHqKH+er58kb+YKqhHA0JVs5S2MfULks8KYRODO0N89cfcZrsixWVzlb0GUoJ1+ibIWKo/IHXt2hKhRbg+VveTY9HZqmfhTJhmywFRFiUt9t6i/u88fRvIWN/JgwNyoiyJcC5wWTynr5biE8rpK7yHPx29th99XvycnTCdls7vLi2WdnTp7ecBgOm7O6LcO2/KdffrGaZEf2+oDLTZo8n1OWd2FUa/5ZoKpiY3HmHSISbvFdXTDoNy2eAwd5c9D7aTjoe5LWMXYKx2YZrczU9eLZz/kySfE/0apr04N7WxwN80oID2ysbquZDrepBVSsJLurkztHFgHw0KlDhD5/FHm9mV9A9+1vq+m51vfX5AHfMQZSzOTrV5coZQDlPd409hR5ZV0P4B+LfH2Zp9CC7qGuF3ldLOn4ciPoVVIw9dpPpvTdChQlLZ6DBFG3ZdgI3020XNqZaamu3YmnaJ/U/keNFig/ITh0rUc4uH1Mk7uzNRnsR5wiA7n/bDdawxG3SkPPY5EP3Wwtlffh50RfFVwrM5nobibjEpVMxpVqASpAqmWoCrCXjSMxqoTuhK6/diagjqKgCTgPEtYs60K1q47W2zm6dK1v6QhzVtLVdZHWdziThIip6lVeLyHhE0+rChcKoG5lFCFeQuECFWtc0sV5iZbMqO8sEBZoWVN73RsR10EQqNuKdJnkevi3uch6/+c/T9H2YA2dvGXl4j1hvI0KuqzgbV3k4WuhyrCE9ZDSGjaABy1iHpWPwbCtXr7hER1Wr/fqnWgFfSwQWhBW3bD2wpTnq+Tp9AmtN8G3XgRRq4hTPMIU6KseLSv8EHz5dFY2Uq1h/jBcUeWjrVASJcPEgkk8cVjKMT/tIifr310g0Wol+/cghNRtxTpLbFMVaX0iJr8wj2Z0oHfm59knsj4umEofhu0oTfPHP2pUVuRc8j2vghEG2ESkeyKy7j4QxvxWLTtM9M8rvDbWPc1WnjVDbU1+xzYqacBj2qhAVulGpZAGpxX7pPZRVj4ScabsVFN+3YjRcbe4IlmkC+XBMrxBFiTJGxQHea6RUYRK+ynLv9brG1Sc31IJVoYNYAqjbrN8wpYYtPahJejSJ0IuZtDRKH0C1DW/GMedVYCBskEFGywneMRB0oJHFE1mvDpOStR2gFK3U3Q7Hzrj6mzI5LxGLbaAqWYfYlsTpwSZIKKYHw67hLqtjkZ/1Lhvtfnteu1ZknZNN5AxriBPySynk7di4ZUet6GPebFOqtANu8O2SNJq8q4frdY4O8nXa85jeLqXDGU0G9PR7S1OMVkuodSOY3H6gFIU4R1FZ7g6Wi7zGnDhnsJ2FYePPidldbY5Wq3IEU33nMHWYcQg8QpEJeV5Bp4nnZ1Nyupzfocz3wMqqc+4iIhqJQpvhaAlqeJuohP91xzYoAbIpdLuD4C46q3HOE3JNPdzr+umCAv0dQyi7rAA59prUdPTdbuFuR40GrnfIoykZSsBIRU77MaqR600CMMQamKbb6Z0PT59ogpNkh7V1T1VaZYMSHfK0dUAp8GqgjQndrVcJ4ioAfX6Ii8reGx9MTgQuVTqNQDi1cXmrae6j6xc3clxMdxLAca1m+zMD/eQFYGdG5dI/RKKXbt0icgZg7DIP5iJFuzaCATsIgwhdVUB5t7lx6RYXeQ4q8pPmCChN+5gvyU4Re/VcMAYNMCuI+muOq32GgkYEH8CjFoAioCuInBxn7P6J+QQe0aUMrjvIhRIfiWQRHs1ZJBZpyeoxzuk9TrP3rQIDmd6dVvk8Fd3wQFexJvqj7goq0i2aLM+PktDJitGnFbImtkk2fTv0U/oibOQHwsZ7wQrRL494Gwpn8UNLXIveicbVitr3s3V0PvJG/ob3lDlL0kNjxMiXZPf5xlqbnOmlxHJ00wthRkQ1EezZg2Be3unOfQww44uFElaiFjurHzwi1TbOQFS7uIIQNnRMVTYLUJHLmdl4wMu0JLqm29aHAd9Q92WYcOc6J0Xc3YpW0tOFM+ZMpo35GP+GVEinpVzvAK7ui8Qsm3w1wgNEkGLCrwUGvN0DKpv/k6W2lX+PQm2We/CW7C5PJAuybGezAHB3XHtF1Td5wqb2BjmeqjciDc8tvCZoYHjorGK84Gd3wgUwxrtFcDuCALInQehgs65Y3r4PINpEQhTd9iHNGJJvw9NFbplxtBY8eTS2V1GaH1yT1fCJFKJEyxzyCNRm3QQYX7re6zsBiiYI0yH5a1uS2VkiOvTOJW2enNToAdsssrFueLeBT3I65zruZ9Li1+76wc5pPVXvhH80lpchzWvWfMtqUIXvY17ie3rdpMbztFmQ1gvfPFFdVT5tllNYrTq75hiu0CobtOUvhJey/o4ze96DzbnJU1rl284HLvhW9x25go9Te+WRwdPLcrxvJg7jCBLcbS+HgAHdoLKJVYCgYLZqOlKAA9RBIe9QN1WrAeKse4sHZuNFBjax7DZMntwrKVuZ2vxnfB+pp7KeF7FR3qV3E0fFXs7DxotA3/bmjrMje1a4O84I4scg2yiG0Fun4KtKF67oWj81G6ZUd7D9lihN7FSIbhdjyHCQhQV+apeVpfkNI4efc5xSZWQHr0Z4dkNxa/t0q5skPpWGsLNoqReJhV3q+dHlE8o3dzW6X+h8opwShoF2dfcB5f6/V8z/fDjP55br3tI7tkfBCA/+AOhnF+r8lhaWmTkcH6qsRir6lwL61kxImUN+UWrXbWwx60d/UOlz+G8YJSGtLnwZ3UFaZOFnmYRiWNI2e+4xAT6LFvhB7yqkzR9DtVDtnMBtrjPiSY8o574kfRvzvZmfefYcS1ab9IITxTjBpjp8MS7hzwcaOI8xWXH/O6hFduhop32G+1pUa+jHfUjYezQMSVKGHRwH+Mh7T2cjpa7Fjx78bOeftElWX2bLKkKUpB9tDJ66cZp9o8K6xZ4nEbOyj/wbXWSFMGXPR2eGNoKffSFC3Re3ROKN9tJhNxmDOeg/BgeakcRajXVjenLSqIbHa1WQh+Cx3RWfsgfszRPwu/JWzyhM/ctS5sF3iEMHuOXzi3+/FbC6RlSqUVz+rTBTVKoD8mziNMOBXv5zlDEYPtPSblIiNaEYs3qGJvjAxXSGxoS5eiuQIhXHH07M0I2i9XkrLykobiLCA7RPaKT52WKmk6Fyjge4wUqcB68+nqcbO9niAPXyhlzKT/NaJUIAT5iRuwl8hTTlZekHcbGGbC3X6MlXlPb1EVBfrVZn//99asFjR1P9k+P7kcL4HJWnpbB5OTyIYYyDlFx6L1k9kCWJkHX+BwGn9yqfPnzr3XS2jqCRHZzXGMYjx4STOrilMMa6B4G9tR7w8JZxJF/zh+bUbcxVoKdB/MK3z4zg8DHvOj6eIzI6SsM8XGy/MkSntLE5sFxiehpkGI8a2hJTiD9oTdYo2CHfjJLeF2v40xSgzF5ioexw7Ko0CYGpmd6/VLkLDV8v+9Sq3TX0KjcVW/BKyTgifKcCa1arBjNoKwTWUA7eFw/H9dVNRhXAmQLhf6By/sUl1UcpK3wSxFZvGRfG9mvvC9/yemEocPLYPvaCEn0Hfg8XU3bQHswO2HX0BO1sdgQPEnqOBB7nL1fB7289/DQ4HG1fh6emDpz3mlWoaKMwl+t2B5hRhMzRSvYZ22THL6uMGrWZPCGR3QIVFZHFRGdN3WFTvL1Dc7aa82ITEj6TGQei91HXYhTHH5i+IHw3f10S3F8jouO/gdeTYj907S06XeaUHnSIwoTJvFubOI9LokT93KH3OTlAeIHVDzTyo7Wo04fJCqYfNFss22Qw3aBb2+NxvZfo4TUbF7YnN+eF/gOZ44dph5P7XYZxU7S4/uCkrIuEKWhhgJR8qL2bR6tef/V0D2hR0t/jFHbkbbOViliV5AGk2Gc+5CmvQtU0IhhsSxVI6SUGrFx8qHOnBGrg682e3i/p4NeTV3pdQt9lQ+XG4MnkxpK8l7SgDrHCh2vap1X1rUEK7lhiSAq1zIJztW5jJe62j6PAeUO8+XK3o6APP3gGmZUR/aTwK5b9lV68alAVc5uSnhXnulOgI1h1eDL15lfNe6IIohqABKcZ8f5y2pt33lATf8hMNUYQFjPcVxghkc7hBZG03sBQtVxEcyzz72zRTz3Va1vZ2h/2Un/to372x8Ptf2Hq2jGo6+gGp+hlud4m2c1GoE6gpPlKVesFKc8jKc0/U4UX6I8Q+d1bfc19TQTZFFLNUs2VT2nSkTtNHCH0boO0Xtc/HlIy4NjQJkJ+XIlF46AnAPmCT4Ims6KoHJ3xxDKDgtgrl3mbWVAd/tiiDOkQokXZIgg3/lOw/UKutLq4v2vg/+8ui2DN7ltuAT3ex82MzE8gr6V9Ii3JMON4PvcdUzGGN142DXlakxw9jDYxhPW1ouvJOe8TZ7xAQS9LYASpski63Qzw/xw2SoL5dEBEyWeq7+RyfpwlRuOYrK9gK+gM0EMcNK2pAV23Zx6HOazmdyuUEc3nhGoxZDG8N6jGuw9U5iHzMOQLEnWdpYWVbue1a+U+zbhGsA4IED1UEDosGBPrTUh4H1eh+KgYKjbMigYEz1Xo6/HIrVsuv6Z76XRcV4RPp21xWR1Z7pUiNjSonoenlH5esXhZIb0cu3Cj/WA7/BALlKaTfqCN5bDb2vxdLzopC8nF/ifIhtbuPL1sQOvcrKlomUloupVZ7senI9dQScLhseO/ZdJdqf1QIwzw5Ffusb3xtnhd4C75tMRz1tll71DbhOi+H3H6PHLJMlYYt9MexwMzbfTwCnS2tbdCGE49GR/H9gBcSEnhTI51KQIEBZvqOuB81niC1rh5E1b/3CQ0IivhkTHOEuK4QFK+5fNQjJ5za7RyPMf2sKmOKqMPCDtXh2OvBrtsnCh/CNOUaY/Ev0a6b30V/QYujmclVdFkpU4xqPKmAKdLVfKyVCMSluhxiOB745amTQG5G6OgHL53ggC8rxvdXI1kKUxDGF0NRBEt5dkHpPQUzzzSA4yWt0WWVaJj2jWLkLXgFv+e7EQd+uwKduG35rqqD3ficJLAfUWfIorfpV8dBTWcf3BTH3VhAe0F9LcNUvAuuXRHFatuq3t2ORntTlynHAw0u6WkfZgVt05s+rB0rj3lsYYtuzYxkRvxwyzSRF25Iih1I099GWVDiqXlCQQKEhFAhwgojhHcvgOKpNWhDByRTiiiGS/rFO0eC4rtDboZZFyWmzQ5E6HbcLmSMn8+thVcdANaSKv8j6aKlm1jhrJgKY/BE+fkIRrNHmmzNPnA56t4W42zHpQ7JZb9cRxnk6fiKDiDVMzXL4N7uvTucop90itY53f3jN6RBmQBpVHdNht1G0ZNgLL1+zOHtZpXnxCT9+TtJ6/9VZH/5zTPWfql/wRUyyX7RW/9jTpasIenuqGW7EHXIcVp24rkiF79LA6FFm0uJDLmOEq1E9ktpzRwug5NnMCC5wSFuRfZwY6q+EVurqv1zdZgoNdy9q8JLtj6HmJFhq1OaVjioZHLANCCLWu+U1CExxCXc0cKEJT19XkIkSzmDYChuLOyhgxI+JFG9eW1QDsux7lxs001TH1DgH1QQ1RtzUQLTg8cEf0aIjYuSA4R3tJ1I7mHiamgZqXhduRpcZ1ayGHQ1ZyhOdsAqbDOlW3FUnDj/VU5az8SLSeekibskV9TB1qrOdQi2hRA7A6XFT/Q7H2ZMAJ9nmf+Fam/sbc4SO51gDYDvJhcvnAk/tfQkaMudUyMtu4kj482+gPxTpUV5hAfoTEmLPpf0xZAoccC5cqMN6DfJlcvsCEb272ouQnaXRdKJarzTvPdpTgU88teAKbF3Ps0H+KBW4ZMNB2YDou8BitHp0tCXRYHOmiRRUkF/VD9RaLOrQHqegqzoLTQcXxyokcyCJKTPd4ZvcmBv3o6jjcq2i+4KwzyCy3MK5W4i9MwINk0rerwDPQx6O6JMx9cEwgxaPL74PkPsjYQD9Ik1IcV0V3jbmVLFG1yIuKw8VkyrjAB2v3hOcTHvwYBtTjUu9NRSd34D1lC8JS3EVCBG6IKeAquQs/9xMkB6HoLRRjPdMzaVnR4sErNCAwZnwIb2pirofzrAb5gZfVbS1+1pO7eP1RYZ1bV6SHmZxx9YLMtjH5VqRXkzGzELrm9NNjc0zhZ/Byc8zYZ3Cn2M2MXUdlie8ysoq6p7BzpO7dSp66s5Il5Q53NIxj7x5sBP+5TiOcNwwyL16Kc6apn9fV+S1Dyg4T7h6lwem0dNlMDJm2bKuqbLvW9Se4o5sua4zHYH2t/LbZVXRtGxKv2Fb1GbYxXYu1ljgeRMBrJh7RQfdTt7WV10x2rv4xXxO9nKdLUcNxiYs6/hHssPbUbUVSnFo00e7UaEx58mm9mT6y/FnZPoQNfqTC7UtZVeQpxbaTIcvcNRqfdHCWm7i3wiLynM94QLurbR3XEeqvoyyGKiJm16lOaf1YjWul5DXSQFvfNgGgHkkMC1/EzeSwi1jsIvNk1djOJeCcb0LjXrjFUwXnu2QTV117vbZA+msGAN5GxNtUU0k1q7pRhdnQUESxNiA9CDhv0RPLhh+oREdRlaZcSgqFyWkZBkoSh6FLfeAXoIkAmsqWEkWHYQK5El2mHOSJ/VIfpjbcrX4HVLE/xbmTihT2mwUumpwkzT3i6u9kEa3RhLHzfrAb0BkaCnuKPZkuyk6PMUwrn3H2kwvOt5VYPB568C5sYHb7uM0WGNNy3HqXxzYfM7SHzUwn+UC6vYidLM65/rCRAe0cNrJ/oY1MtjXvitHa0ohvZ/n22s4+5I9Zmicr7/RTHYLDJqVZty2N/qhx32rz2zXmW4k6XN+K4EB7AKrJdqGuralSJNJrTzIZFO3kY7HIVhinodMnMqZyDuP/IS9iW9U/L2LH4crEiCCAtAXAUEEy/gqjovXR9j6f9DgOcl7dViQ1CI4k76qv+2cPiOMW7PgqwD90FzdQcGH2rHs9Bh1WJgwhLU0FWEyn3qGJHgrq5oXCzUWGiGHruEQPGD1+Qunmtk4zVJbhdg4JZTSx8oq+e+AuiLupahWE1zYLuOld6Ar8kZTtAOM5w486qDvHSAS+FqpKJxdDDdVZxVQtiAGJUlKe5GvPhEK09hsOxW7wWNsZU0K7OHpd1xh6mt6ZkVLaKzqskts7jCCjcxN7PQAObA2VS0wMAgWzrGdyhJ5fDwkRDEanJLurIWXe1dM30hp0j5VQxFiOro5UTHTN3Wq0MPyLiiwHegYEj4SGg3S28qzZpOlsJERwmoSjzabIH9CqxXcCPM50DW2UV/GRRs64GTXrwSHRrd/IlHtsJ0uVeyzbEgeo8QY7KgR31zGE61mJ259h7ykvLUD0gdKqCl5aALXcFVmSHtXVPd3TmjAnl2hJ+Nbn9NTlCX6jQ3xQGTRCqKVgqMpwuuYy3Exq+KWz3IzuFmuDOMRtsr1jnLHlc8rLjPvmaepouSTH1HkaJH8+4BUqpsyharSMgYJTJ0iuh5qDJLWqIG0BdrWCTlwf86JeX+Slj4mA1S3f9CgOIlTd1lW+wctYZukYjzTnP8ycXRytVgWzgE7sCLMPqcK08qVfUqAwkUslyQGAuGqPDAVjW0MXeUCok0O5ppscULg8azsTJNAYjoNEU7fFqLQzEo3OVgyXrEV983e01EnHP00T1uJrsxDKsO5/x+QEFmjASMqK9iTYua3FE2uKO3yN3HUzRL2MDaERj8odYVwMy1oBxmtPMHWwBQJ7x37qutYAhG8BgdL/IPjVbTEC/VHk9WbiAPzvI8VBFb3pZjRKfm35OlAmx9kcqASNcg55iVvEv1ai4mENq6X5NQ8kSHOuDJbmPEC4NG87ESTSGY6DXNcImRcvjV/kGo98/6gXGfA9kpO4EO+PlPLES1y0c+ssKZrAq81/ByGhbosRyBgaO9KFA20r+NlHVIfgUDzHOE0JsVpDaLCxgizBjQadBXkXhC3qaB2Jg+0ieaa3yVGRfUHVfb6a8iZJwTEndVGgbPl8QmrO0GjT2GVSDQqw3nv8373XwlXy1G6oMQxv3xNzxP2IYmVR31REJKZn2TK9omhjudrrGjt9mqexK9oYmZsldWeaa4SjRucZaSt15hlh29isIyMNOSxl98ZGwpHsIpiqCEn6EaGpaapueWoCq1uemtotft39Z0QJNzmTdmt92laYWJmsCaJ419kKrVzTiDg385gUq4scZ1X5AxWIsF24T+/JPVr+zOvhMfucR2mp8VkSb3SqRywf9qPbW5ziJDzkSX8+2ExOA+ZUTQ81BP1JgYj8OiG8NdadvFmKYKIYpp9I2uVZ1HCJNtM5ICflT7RSTcmkIzx5eHg/S0OnTxtcMEPClzwbkkPN1OZ/oWR6evLrq0kJ8gHd4OBH+RyqoyXbNz/l6WoG/pAbnokxuYaPk+znLAdgoc1ZRAzf5tnJnM2x5ypDQJA5mjy7SWZQLtrdlCmA/RPWqdd9TQ4EBf4nkzQsFkeypD8H1WD2pmdZMqrGL1HJpZCZUMJvqCF9XoLLjc402kV90+vo8w75oi6W90mJ5jTgXyTY9+lgZwFpjunTz0vbHD2fE4GzqZvbktmtxh9QiiLEutvd60n+JHyJ6NUbly/B6triU0LDLbVWnK951efBDiUafdqyqa7uMelfQj6z10qfkmx1/uBxslLeo46vgMD7VLZGr0XA4U4VKpfcMEAgV4c/rVNi0wLkjzguUXTN2wuxMzx9K5M79AmXNGcfHFUKALxub4i50FJKKOmuWgMK3VrrBvEHvmWnROMgIMDrbyVa/cDVvTQYM7Q0KIsqroNjtehDUA1/s3eiUv+FIqmzYrlXz4jUUGRf6osVPRuK4J5x5a49u0QrhNZoxUvI00a9BzrKQxmZwggsDcZcw3V4dIdVv0TuSmWyj0ukjgrFPr3aWEljCVKUdgKAQuiJUGHxR0FB6JH4tEFTggLz4A2jbqu/Yw00HzdSLdQGHaT0GXdb+x12qGHYZLuPchxHLbSrvtDKlCkUBKuOjyWX10qHtQVfLzcI22GZq9vq6BW6Qkc7aAxkbeKEiW4tJxIoHTXttd2hhkHR7T5K61IP7SpQBEVrDqXdakCwThckcMKFzEGwqNvqjHKcVIBiANpJpxheeBP7QZyVXWePlhV+SCKYujqEJ3m9mclifkmIsaGBumcxCfatzRM9Z4Eyeo6dY2RNU/MM6ws5bLEQXBO3Q5OSd9zBbJPbNuZOZIBy35KtzE7SBm47GliSasdzraojj0gBqtyVVfBBWzLXw6DnLYcAqTaHcUKk4KcnUfT8i62mBxI13wxXLEb61P7BfUOTuwPPMZpZRjK1+3Jn22h0xKlJNm5tatptxYd2Xt/ZLjdKhBi3Z2WHLJoa/5kskGxI0OR4BKLSuslKZ88hhod6dbZKEVGzkum9JBoBf8KC8MXib6WidFSW+ZK6PK86ZQW+9YioJak0P5NWFW7fdbhoBK9rgItIa4VUk6hkwH0hJyqRCjVdu4iRqKS/uAzUKymKg16pbiuKNtjMU7BEct+M2TvPfHD22MkzrUES2F3sg8sNuPj3WmzAxXpQSGsA32ENavgu0jv+huAxcBwnaZINub7874ImNt3OZUCbSDJoHdkgbxfIrU0HJ4kNLXD8G6fIDkE2o4l43dS58XjIws6jpvchOsg/dVtRdJCrIln+JBSfyYucvQCOe7pjPIN8fdM/oBQ/oOLZs/rsuo+1c5246BW+d64uf2pHUB7iepABcgdHAEonwDFUUBwpHmUMoXQwuptXJKNTFE86n0R6cezlxsUQcx2o1iu8WjzPK4Qds0v0jxp55YhoDQQjNId1oFkHMaKVRVsEsY5LcS6fLlFS5tnHvGi4aX4zSMu/iJm9o1wQhHVA/zLLbmrF9HzTeXtUye1t+/Ji6ouV1Rpn5nfDf/olSraRkWyLE7Ruh97v+Z6oOZooztIQBHDuBMECD5s5U85OyCYU5k8hYjrsbLr1H2Fnu0iKVr9xvCFs7vM8KvJTHMPDMpoFMo5byHbiN5EliQrCQ9QtYjKLZRylYx9E8cDcitPFWEpd8/D8GUMJBpw01LBR3xhLDUF7iRLI3G9vi6zufldqBbjmVcGYuxzl0leK3+a/CUqoDrugui2DY7VtgmJXiy16qsin9Wb6CCfUAfofNS4i5CynNjQK3zJ8LLxn5VXydPqEOGr4oiKITggb3OXFc7SN+CTPqiJPY+ga8fIdnJXM0wuFEmyy7ASSEGJP42CrMwx7DYjEQVzb1pFs09YVg8zVcCsRZTrDdxDs6rYkik2cHGuinYKp5EervxO+4a0n0ZXz5j5vhobOSoKJLHu0jOClGiBQ7SXX/DJLVDmdhZ3n1cKyLihft7Gcgp+1KxAepJa6LZFkLzmQhThWheES5KFruTJvyrSrAxg3LStGXWfxFthhZWmY+HmZomZrDlwNFNEFKnAeHH+CedIwfIGui4uKTLvS/WVLZ4tIsRPPMlzhJN1lScZ30UqKXY9rqEXXCNAor8bQrhY05f4/t1iWg585ynMvscyYnnz6nN95SGRS6476F3FYDtJY3RZHpl26xIkX23o3BJNAZnApczDXEvywejVgklzSwUa9jeAbgi4ioHJtb+NcaEtkjCFOKPxBpKjbaqJCkx495oUuQPW7aQw1BvPQRNl6TzMK7KhjWfNx2FZ42AL1pvf87jN6QGl4rs+8qMyvgaydqxyb/0jAZwvms+mjvQYvNE+FwuQOc4e+FTq/jXd/juTjdouKAhWzNBbV5YJKB+0jlYkM6Z+qamNMXvAuBrm+lcZ4XbZ7UBQlSaUcaZWieMoQyyTO59nw2FKaXOdvJFSH/UXdFk+n4MhJsQ5YbAbDzWYbvNxRU5B2JUr8C65LNZS0SjWgQWv2omBRZPp93XfBjvEcVqtmtcbwxqU8FGulDrElghXU+ubvaKl1/P/zZF5W85tyqMNVEsEpqjWfHz83CcEiIuwjYobinEiG8mwMytGxWLkeww+CVAMmSVIdrKvJig/LYu49D63s+wBk6jkHGWbD6lLxekj/vm45/DwIf3Vb7ak22L0wzoWb5+2f2gityeXSs8c1kMdFKpRNtxJE2EOk5T1a1Sm6SsqfHmxPq5VveCQHple3ZTgi25ouXJmbsIvOfBTLYpJnp08bypH6d6nv4jx5bG4ZlK38+w4Zn0HVd3OenRZFuJLzlWh8l7XP89vPCXsrWlSedU+zlW+r9XJJ+MS3XZ5s0zHYWfkJr8jCDp0g8ucdXRUXiMhxKeypXV2ztTnSoC/zx1Za98PGWUI9Ik7yjLoeoGz5/IWhY22N1xzcAQ+B2uQ/RStfRW6ZEoU/90odsqAeFgTdmx7JYUdTt9WQPlSLa7Bsx9u8OzpQ7Sp0HGdEn00jqpF83xTOAC2PXo9BeXcACAJwCADBglTLb0XIKszf9PUPC/AlL8BFWt/N32q012RJdleTrdltBuxzYFHGwMuQJ7LUwSvP3oiYDotq6kVFRvRHkdeb+ZmbtDx/o6NEgPPZnj1uEaxX39U9WqPvSYEpKh/rCK1fvhmhOaw7dVuMUBE4d5ZXjWGr4X2c89qU3M9w++hu7F1189+B2yfnQ9f4L1r3qal0vDKNZTmjx3ODw1mcFfwpL7Uh6SJZXD7nd/kFXtIVsDuxFj5V6/Q4X3Eq0HTxmPKsIuulC2j8FVWPefFz8tm9KDARTM9sCZ+0dq3weFgM5+nT8p6cChDNhOWNWhP0R9kIHAiIjvBaW4uLCGQClkMDGWu4RzWSZ8Y8MgFcMaQRlH4sY9CwQEd9t5z30g+4QEv68utNh+Swo6rbMt6vTWNAbCbGkPf3z5MEY7VPtvfvvtvJ55wiCCerjRn2Y16syaSzFqZtLzQKkCi06AIr77ca3mNKF/awqIHiGJlsO81WZGaja1hux5svCQtsF3jKabEcRLO6rRdgp57wqN2+5u4eazgzY1u/fDNCdOBHdVsjQjXvKM1sNdlrMruUr17Bwq158K81qtHqlPBTelRVyfLeM1BL66tQvgERHnhS3RZHsGAHdNITMglUdxsxFT03CTMigbqqPskQXmY6M8lHDFzPG9QMLnNu58ViY74IOmNM9Oj5C9GzDB5vU7WMVjhpecQ0AWr3dqxwYABlxHUDPpzh1VDSGV4D6mqI4Efu0PlxNdMgeGjLwYyquA6KQ+kwplEt05C4r5Yj4msEWVpG/Yyycx32K43kL3BeBEe3p+w0v1M0bTWSO7bpUdn8g7vKZxnaJdqkz1HGZ9XOLGM6OZm8iePlcvI2zA8QI+ll9JZo+juimKasBRFuVwUOjj9I0HhlvWo2v+WSpkoOVvNRtvqSZHWSpv0sRHi91O/M8AumyDqCGPvOXquwHRBPctOIrsfA4EBGMDodZwwYpNqMu+Wv2/B4DsqNYZnqjlt/nuRGp7X/m06Zlo2bPRzmH+FFXoiPYFwvI0oyk3EI5GzrKEtDrLWJWm7cZxZl6izmZeJ9QLcJWdVkV2VrgdRyQ2p/8Z2sNwm+8wl/08urDsdBVqnbMkiLqTxvjTrmRA1H0jm3eZ8adP1svf7aRXSF1mRP8Xo80C9DAdVhNXqvxonstORU2ah2TVQAbYrbOAEDX9YZM9aDoJjHu8hOKkGnReUZ8Z3H1cxQ+31Q7V+Nta1l5Vf0WH5GdKkHBu7rRSaM8SA51W3BFAuO5Lelo1wceRLX9jWhD8sXlJSEaZusWkGuriNMh/WiWS96TWOisN9bjjp+SUk3tdOrs/+l6zL5QJZvVvrtMNJK6ZEdFsthsbykxXK23uQFTRt8i70e4I7qHxbHri2Oj3m6ihY43LVtwhG0zTguyXEwxXkh+RNvwjpylfxEYRia5wLnWfg5kyySjxilK/rXDK8xO644ybNbfFcXY+/JqWwPp09E2PDuihO+tkzrdda/epi4tUtUEnl6lt3qfETiNNWGoCTIaRDKeM8tx/Exgavj0RZzPQYfLo/VUNL1sQY0LNLnc7b0f+5CubN73vmGQ3XYVDXLOsqbl4Y/9CFd3k3ztNHquc1E+zN7F/5UbUlrZzT/lJQ6z/Y/xXv3eOYYF7Kp1fVp6j2DNUY3cGkzN519sqWf5creK+aJCssPaJPmvomFRRQHiaZuq92VQkXadhZ1JP16RqVmYMoY5wsLt5lIsTXMXjJxGjKGgYkSB6aqNldFkpVrzEJbx5gKCOf4jRYTSjCYx0G3sUIZXmrFmZNFfdMc6ydvyfq6OxIjsPYsMrtEHFyca2S6HvED+sIFqAjwHvPxQdMEe2lNe8DJStyYr3vg4VylgpFOVUrAMKfcpzD75Kj+QfFQt7XD9smJwg1QsU1/taSaXm5bvoGPEnsrf8CEsFt9dn9Wtptit3gD3X4iGW/DzFgyDxHemeOMSP6k2+IcxtKOexoT7SxW0xlV/vPb2xIF+sczt7EwFMdJtbxf4H8G3gNckEXeRBHdHac6mluCJY1y0R+jPWL7G94cFcv7GI5BtNYQXTpcFxuUI/jZV5A+Jj7yMipu0Qz0Ix1LaaBXQylUyfgG+uNk+fMsI+tl+TPQBfGECMU0v3ujwHhQNNVteXjIgVvUql6GS6pI2Ui3kfdXwXpg9l8TrLQCjRXcY46yCXMaSV/HPJAW1HocHXyQOKGe3rcJCyxcBLwA6WQJhO4gSNRtbefU+B2jx0hmvl3zBSOMiO7y4jkCL4uoDnx84OPZ+LgV7hHYWMB04OIDF8/GxUxRIhPRKUHeTDxGdOBhdVv9qeJdpNPJ+zA80+/4RV6WRAdPw7lMRHXgs13lMzV31GuON/5aJwyMuokVedrcjJ+VH9PkruzxerMLgD0axxDJThZM+kxt/BxVxqT8gtY3qOhsEhucZXSNsaRSv7/+RaL8CPwDmYZV/pj18O9kCje01ND3Y7JE1SIvmlj8/oRdoKRY3r9h6Mo3PNYtEvQTrkoactmWogzqiIN/Z5iBkWRs6wAPk+1modPvPmHq3xZ1KnjUW5yPk3u0/HmTP1ErvN2MNJYe2/n4Wq9p7stL6rwcNB+m0MnBAkgI06VqaYtzdZzmN7azRO+wyb6HaH4UZDtZjcIcMkka36ngCRr5watb2uZugen91gWL7WU3TV9IR/CGdJemX6EDHFW22j2OyjJfYkbC7hKSpY1pTjyXqGSWz+suW67Q/9Ns9aqxiGprDfbTwVEOqkAOWGxEhJhkD//99f8uDd+2wf7WimuwG4LQyLvXouZ3nn1A9KLx1RG7GKcWrHKZrGS9iVB0Nf7SLhoam4uoICVhDyKgZc0SZ0syb6nLUAQklgoq7WTfnFjyAW1QRnVLlzm06QefW1nuT9+sQEwT7X57yzGrBQ/jf7LTKeubJQODVZTcy0M7sy7c1P7xrXYcczGtdt72gmMvUNHuQpdomRer/kqMjrJUcq2+GsS5Yg0XxjW0BjAvD2BoyYVYeZqaV/QICiRFTvdph+GPEO7VUgW7PsPqBOdgPxYk6flRVj6i4prxiY4pODgVnzUgrtzGIwb4DWLg3eA1oOMzcRswFzYtU/it8driHrM3uU1Sq2uiexEdbFmh1Qn1m2Mhz1VcYq4KceS4lgtXWrQHbQOs0KQkuVCMnPoQcyglB9TrDr2y0xA0SBce0IksYAv2lNj+qtWOYIa1q50jm/bbKltbxK1DqpEZBTiIDVsQFwYUsdqz3i9v3siGHS8WUvRhBuZR0HSf2GYsekzTPF4tcVlojBtgJK2UjM9OYH9mZCqQ1jbtjypujcF638rBY17FATIoxFqDJ6g9bwGYAcayY1qfsR/jlL7N6RowdnMMH50KAnp7Usiry4MaLEJHVg0uvab+ihV09GhhfcgiNaMxKOyeAmUaxQwSyzRfVvsh57u+FXl1nOZ39B7DbOCRICG+7IBcGFJGvFfGHmX3Z2BB5ZzshdGH9v4kX7N3TT3j6LhEBFZxYAvnyoQSeoAPVQy+G3yoGsFMrKiaH5vmuzrbM0Fi9mpjnJhWaS4EgEFjZAPnZImEUAOcOE6gO9WxQNebOWyMGjrbNC+mZd4OZzUeft1YOp5QMgAIDnLXCNKJyeA2IKs3jHz78k4/hDl4UztPVmbwpsrOMGbrP2zLNOJzvSkYU3jiJ7ex+4w5HsIWGHM8T1aMObzM3Y4VpX15ZpSVIiB4Vm5hnA7JIl57yRhv71V1Yo6zrYKu+yDVPuCySSR7tCGTQvNCtaPR3ezpKkFM1cG7MJW2Dcj6Yse4DqThXycb1xYEDJGCh3MhB4h/G+tM15EZ1pqOzvu53vgRuSy5Ub3pVt24GehoZc/RQXRqt1kXEnVVpqNO34K9zhWBJt2PS/SPGheoCa9j7DRUy4UyUZVF2/4BdAXgzJPoJeusSDeD0LMikU0/uvrbPkR1t+Hnt+cFvsOZ6RQlwmuOUR7nJwn7NjwUDH2Z7ySkorVND4SqW2czIp7wAyqeWVQiExfwwJEZbIQaEml8PydnMag3M/IXRGcr4cXV2zZnHdfZKkVnFVofVVWBb+oKNYExr4cSE8PZ4NDwobK6B4NadUWt4nBj3lUDk8sI51sLLixgdS/U19qdBdIOxdJeqqpntRCCOF9obw+NqKaxbIOv4Vm05+VtG1flAbkz8nws7MO8MVULdVe2wnt7aMxv2+9Nyr1Z08AEUgUNt/nY+JXNOJhid0ZQKkcxH5cq58vqmNXW2RkutZSJIvzEPLrHW7lqDFtg0P0VoqO7gpGZ2sBByooahvW92DE26Why3xkWNo5oPl42zqdNV/h6O8XZlsIXqjMTP++xINaNY0sMvL8CebFBS3yLm3hTvcXDloH1tTWsDFf0YGpDD/aQve1GNB+j283xPrA8PJLzJhi/giNV7OeBC3xCrkHj9KLcozvQW02rZbn9pRIw3BkWTgBv2PQOxrCjG4mWwf1ku568W9tytN2yXmvw0t/+igsf+9Z3LBu+8V9/DZ5tr8JuW75K7jRhwGTYyJfrPGa1CkaKI0b5anB+TwqcZFU/LSf5+gZnDNDJ9cAWj4ZwGhQeNLXu0LZ9GVw7Op9ccJ1Tm57tkgeEbnyWBzoLFDvB8Xt8vnMY1m4sjT086VmMqov9/y3DQauCx7MTS2PUIWB9jAa+zc0A6uhucDw0pzY94+vtGu/77gAeYj8CQ78kAb87Un2PRblwzioXqD9lmI11Djg0bA5U9+B0q06ouX6HjXIeA5xvKbjMvcOy2Bnzm2jP0PCsG4NqMNmvlfB1ouuGxWpRL9ldXTcWA97a6rHgCZ81NGDZ9mpSbZzWW40RwXbUqZexw1iPbvua1ovYW8TBsYxB1ypudWRNLTKHVcLwRFgq+v6ol41p1e7s6rEa8PZWkhV/OKwqEcW2F5eTIcrW2uRzZ7NTdqMtG4f22wL0IX/M0jxZ2YUCBKHBIAQtoFN4BhD51qIBarszA3tpaW3T/i7FA7xeJDQFYc8WJgEzBo8svQTk0CWogn3jyy64LzNKL5jSNh0Y19wah11hVJBR09QefEowFRPA4BCH9ZAuPKZA75jAjPHZtnVA/VBmYFH9VNl0gK+3AwxqulaRICdgy328LFH2flYm3N8LkUv0gNGj7a3eGFqz9zaAHjuw0MI+saJ2BPNt2/Ac7R1LfkLp5rZOM5qqZMxUVhykrG5kWq6mN/+qW1czNLxkdoytjQObm8+N8+zA+E3FrbH/V/RYsuAGxhwkEiTE1B2QCxPLiPcqB4my+zNwpXJObNreeg4S2vsubUXPODouEYFVHOiRgwRED/ChisF3gw9VI5iJFVXzY9N8V2f72ePs8pHD4NHzp20p7/jpU4WKLEmP6uqekrd5MCLkQlfSxqo2RCpdRRfy2XVgr5KuOQ1phvXuNMcutpGtCYCPeVGvWeokI4PLoBA391AurAugduLTKNZgdSdm4Cw1cfeHja7yDV5a8tEYVslIDMyZkwTkW2IluBdz8RJM4P1hpmv27x9FXm/0nMQBKtnImYN4pAD7cH3buT1T1f+5GA+YD5umh1q7IMQarrEQMs2QpxBfDWYV8+0o3wFdn1fgjebDmu92QP3i+MWsJXEDjq+CcchV3Afy9Y6woGIMs+pw8vzYNM8qbI0Vz4uVfSZ1CBhiRQbnwoYgYvsE6pH0N10vZmAkHXVtmh/X3DJHGc8DY7CIXLSfNg+477Nx3V6eGLqcPN/K5A59wqQ3xXOf6EftSKmrpcvqxFfwyX0FN6hJ07R7XGo1lBmY1moObfqx9bRO4EgayefET80ynot7m9YA1gVl9o7y7WgQ22La0bzZdIJV2O7mzu679DwqwCm3d9cLdBHv/rCgoudz7fDyXOwTs5nc5yTICRhuH13mlL2fle320FXuD3xbnSTF6vqCdPk+KdHqB67uBw5SsYuhHsSWXRUXrjQ1oxKLEPfHe1hh2asZeM9yGqw4EcSwdcYcKRE9C5n4BaylY0pfrVHfIMCeqlWwfRlqNZQZeVo7hzb96OrsFg9/45eYGyOPqs7GzeNW90cRtR/MtpganE+bzowqbldt/ZpXyOaMNMApVVYK4qyycnj3hzUVPZ9LWZXnYvfPSJfokSyfi5wgKLv1Y7S96ypBbAjAuzCktrm9stLbjGQGbrWZv72w4EMDsVMEjDWtxR54/nFpyG/ByM26pEi5xxvmfq4n0hgMTGzSQjglMRlj3Z/tBe74DOsVnofd31y6fjdn5o5VTHwxgtYxnastDm4Aytmh4OrdYUFwCDNyIjhHNu33CLbrXkC7sbH2WBGgIzobiJjtfVbimd20PZlLd1bR2JanNjvguXKJqrrILtE/amTzMgIGh9UBDtJNcwab2DOdWTeGWbRl3TzthZ48dNpS7qkqRH+0F1MAOmkmOWv2JCmavWzIXa/RT5R1YC1lDO6mqaibUt8fcmOYbLOw6NksaohxKmx6MdTaooosjMS4bShrTM6E+7l/GIexDX7dy11EGoXJrUJVYXJO3UdfC9MgtsGme+h5cZGn6fe8IqNoH1jTD0dZ+agRqZo6YDgiAdwpDJGmKYhbh87vHMNaDGUGnrWYOyu27WttT0m/R8ufeS3GxJY+q5V2SwSgEg/WdVLpbVuHtAdpjDvH7a7Dm4H1XefbSskQK2/RmrKsC0KKu4vkmVkZzzJM8ZrudTS1YNvKuIKbeUXXmP3FRpSTmVVnZjGXWMyAVT+4ejvDhd0VnsQ2tjyiQmDDm15355bNA9xqWhrbF8quo9sC+5vm26ZLYt2trQY6rQ9k7j/nd9fcb8ozygWgqQPxPAfiwue6ViCbotD5neNsi/HMwMwWc2fTC6HqTrCv0c4GAU/EsPtpWNONYGbe3EtzmhUXmrjPket8uW0nshpsidH2lsFYPJFFfVMuC9wkdLSLsgZWUYaM4aGdQ8fATW0p9Jq2MzMwmpn4e8F2ZLwPSYW+oJI65V9/LPK1ke80deCA8Dy4Wxh4dUPzs51Fb+YwoZqJb9MLvt6uMN9V7sp6Q41JGY9rZutsJ/dlfqaTyW7Th6HW9s4Ut7c4JV/QtcmnRoIETxMdkNNZQsI8e+wrZRfmOAmoCGt1Nt2y0+DRMhUiQdNBaU6lEDh8Lk3drycV6B2DqW//qKAfxyynU908uehxtN72XD6qvKDps/A6KZ5Pn5b3SXaHLslSO6kL0sTyWe37YaoJOoHQSk6eH8ZWQNZt+z6NKLTu0wxsaD0LNn3RoNkNBmV/uHHmqEp8lhyj3zIvgp2ZmwlBgjtw36j+1tjurzWq0ep0neD0qKqS5T270vmINTu3ugrEdiC0CxtqmnPNmrvtzdw8lBmY2Dx9VmdkvMXNHB6CVfZwc9UZmXg3kozb921r3LnX6ce5IV03A1vqA7OqKhg405Mfx00AXDjq884dkEwjmZdnwfmy6QJfbxc4lVt8PIu5yTeeMPNJVb5VgJ01K2anuFk/oq2JYmBObfrCVdsae5+tN3lRkb7dMmVneY9WdYqukvKnkq/VVSCGHkG7MLKmGehVP9/zaY5b5g7NwIBm4tt0oq2Hsztac2vMd/rkzHzqKnAeRU/m0zSzHeYzd2gG5jMTf++YjzSU5o3PZscmep6QK6gZb4B15z2gHUgP1TH49rdu01Bm41n1rNlZp1iVrbHqcbL8eZaRM9vyp5vHj6kixLqKOi4cbGx2r1whbUczAzPbzqdNV7Z+ua4ajOnhsaHezDy9j6+RLceyRYbe+bfJp6RO9UzqVKQGKjrVZp0U1fnN39GyokXoiUz+kq2zJMvyimH5y7cSnaQF5ZPy99dVUcsaB0W9QBWfA658/ar5zvFXm3JPYlmhevJ0klToLi8wArH05c9GXOQXfYwLoWmLjCg+58skxf9Eq3YG4U6JUOaufU6yuzq5g7G1ZXadQ4uqYC+OS8Z8yu4JcEbkF6hY47LEXXZwCLEIY0TKOxJACMeeHKYe5mkK9op8t6rcPLJWoejeulsOSTccI5LW8wekSe8sZeoINTwqVk1TZrFi2hBIX1B1n4NTPoYwIyRSBJFV8UAkLNizEYA1sZm4yiodzVsQI8rjNL+jKS8hXF2ZmZsaaQ6yUretGlB0KZUgHEMWNRN9dKLTWm5e4GVVFyCOtsiIgr+2gfCM78XsqKvr1gjC3Lskq28TBguuW77ceuJoVDZcoLWCLwEwM2qU4gdUPF/hNThsvtyWikOgKQ0h+fBdrmj75/ofcVopxKuhjm2jWnYfw1hwfQNv4g0AzBb1YoOW+BYvmWLVD1nTCFzBLHTBaudMVwVlsAbeszH7ZmyJd5WAitxQaovoe1LgJBuiSpzk6xucJSrimGsZG/5rnbAv3zIMiga+3HcUDl23bcIGtz/Slh0JgA36Adq3IetGfGeARTxxmIY2qo1pC2i9oUDx33tKmQ5VGBXkkAtrYH2hEc1X9FiqNo6uzIjk9IkI+CxJj+rqnh5lG3GgPmPo4I2N9QnNIcxclnkbNMqDLZ9Q3owoUWGw68UfRV5vlL1gpUZELIwJhKONCWOp8HCJbuAdGM7XasAOpNGBscOZkCyx6xDa0U+1ELjkdDZoaKIWJZomXY4BjZygAaYXmMjB4jyoUmiHyPSWSFQUG8fQN46Wi4AMD3MUoNrYt3GYQbh/YjhI0ylMDFwFnsbkOGOuaJU7jiqCmpG2ULwYFTcp4gE5tmGD22w+GeIvgBYUPiyGPSpm89SjawK2GM10YK9seiM9w1ZK/vFFgVGD4R88wjrL+HGpiWrdszeQXsM7QtNq5O7owZU48lwwzmSq1iq4F2kGNN8KDZq+0LzxoAwRJUsrEkQYs153j9aI6ZU3sD11BGBhrMth80r78MRonGMvIRTGp/5xiU0nviRM4ir70pabGb0RIGpz5gjA4iAH+NPBJzrQL9IevQGpWcvmnFFBrXrkEWy0Kq43Cb4DhU9XZmERZKLkCq03qUJQCCBW55HPqCLnA5OIhCEt+pyUdYF+IHx3D5JxBGCL7gMm3FAqeirCGJGOXOwgjIJLo2n5PWdL3eobii1OemNfFvh0J3oeWSHVDFfwMTJZ/+EbXfAyQHUr72Bs1nI/AGdr8H/WIRZhrC1uGpwCiIXaR8FWmnuSMYR54EVelqRiqkEpwkhIudty/a3q9XAny9XRXK8OFcTLfT4AlqZe7xfSD1x53yv5D9g20TmB8E0M99KiM8eYWLaE5G/FzVSEoQ3jAysp6Sde5puoB2OfmHTixfq1cHEuk89QQz1IfUWIjIBngIaIBvwAIYWxhhMzT1Mt640BNEPh4UDKNB4JOmqMUEzNRUP4+cYnAh46D6LvOQepGn7vU2EgAo8KIANISQ8SjN0VrokMJLJwSfYZzltCpopFLfXozJUh2kmeFxr6WbQALathxOFk5b02rnvvEoCSIKBmaBA8SC/BsURHLhDnxBTqwv9oaCOCqEcgQEL04DyINKQQEc1EBMHJR02KMaB5HOOpDSbLGB1AHD3XeVCod4XmOiqTB4BSD0YGhgjDua1pCAPgAqiiJHIIQY5xmnLZAXVUEUAthjOuEYE+AsKZiNT6uA0vKzRUkmDNoxKr6Og0eORZkEtCrNECY9Cr8+PTaoEykHogEixEGs6zUEMTGdXEWiFt8CRfs9dFg4MjTA8JTj8OETyYYUCkAH2UpPbRmhsnwnEwEkh1huA0Si8ADirRvZejToOGkAF0Edwww2kz8rO87v0tAerAkJohgRVAConuoDpCwVihE4cCXQQqddYnM5Wgt0Ta8QiPiCJRSXgkJGONsom1ZkkdF0kwmr1FAAVlD+cvqtumRFTTskvnJnN9tNmkGK2ucr6fMlG08OpR6apBxOIc0jW00mKFtnXlFHhQjjeZ69gIhFOPCQKHKCS4CmuoBGKcm6uE7tow1riKCxeMasZkrzFiaM/TzUoUQg5C0IaGPbTLKLtKMSnX45xWrvcNww8jNCQDK1iMEKoXgXAgWoB28DhjaRGdreb89rzAdzjTqBESqHHHF2toFAkrDULCN7GBqWt2/BBGTaARnHk0PHgwaUbIIDYaP/aJRRvlI5tr/pGPkmRW1Y2Dt8GiIbDucZGZ8laNq2Xi6MFU/FlpO2E+JCirOBBgXNOK4o4kFlqYdquRW3ciow8BpyTdrETrDznDAzklzWRY47CkKhqKWR7LlJinPmaIDZuZTAK1H5WZwTzJNSt3jQ4847eSSqKp6xgHqayqIaP96c3YyAynEKgPZj4Ewd0GaubHMELOypbwE1QLQhoqGoesr68hrvJ5rZnMhjanJbjuTfG16iUw4D/ggUbjCeCODfQ90L+v1rkieHQAuoa2Y4ro60T/UNx18Wix+XK3nsIzLjNtR6znVMFUAUflznOXPadXn5R5MPPZloMOPifzuNQyqul/LKpoXvfb2hasURgpYItJQ2l9jAPzJFh3YT47hq5L5v3bpnYQVcw7+ZRzMqsSpevIOJSF13SMUARRhce0pYkZdQGYHSE0yFRT5LFC/JeFx1rwovO8XA+EIrkWizTktapuJIENFg3Z4eArZspbNauehSk0VFGjUvfUflJ0SFxppMHlMEEek6Nr2GKKNJwRXxzZLBxzXW/BYbNkokqqbS0WMPTQtaLQYS70eJzJpEXnMEddXAz3idL3QD1pRi4JOLBZqlSOepOtcmR3ZptbzekiXxmdNGFAzeU/BA/6EgxxuXS+BCC6+Rw1rxfJepOiIVCYmn0ESPOcjysEs5CADjpYqkjuQZ8+6Nk41TVAHwWkekBwBYg+fFg2DYUUCGd4OTm0rDk/yEA2Q9GcEZzJMus54BI9YPRocaASAI0rYAwf7DoPY52RRJ9Qurmt04y+hxkVGGmmrmk5XCWCuFRVN6NZm6pmPMjdxVzUPmuRgdSjk2AhenFRIDWEklFN/KyFNtg9FRmCUcL0kOD04xDBg7kIRArQR0nqkId1xlACCkj1cOAKEV7VzR4qQBdsVPvKzq6ieshW9SGKGsKpaqhs1+TEL/P6GKxa6gJQ6nHJwBDd+MiwGiIByOagCIsmayaJAGYYxhhaSZQuzq2JKgK6OchyzQe5VdCEhzGMgANVUCMxk4FHAtBgFLQ3JnO0MYO1nNHAWM1jM5Y4PNHggokhUTRIaPChl3Vig4OzWevcWGKIDg6dikNgAnvQhsUNtng8D8KpBwOBQ7TpQktr6AKimvjBfNOmTp4KEKbu66SoNQ3mkp1QHO7rIaeM+kUOXEFj7tLV073IEYN9WzzPgdvQPM+ZjJJt1HRLMjbQjuNr+GVCAjYNANSDmdl3CbKDjpJeIohh/QyQylVoPqeKmOYhgcZ6JAPZdF5jNXImxDy2oi7a/vUF6fF9UqLVD1zdc9HzZdKYqqgHZ6gJkY3LFKChmgmxip1ineqhJAjXQyIDNQ3hCuaBgvV09HOQTPo2AFIq5ygWJb/xE2pNznEtx/GOKk9J2HFDk8s9msvCIPo5EIOcGiCVEq9NrGGSeBymKUkAJN/QqqNaePWQdNUgSilyh2iIpm1hYj0Watu4Qs2V3AbruWwc8frOlWeITGaJVpJQgFCPawyoCnMJPpDX4ZlyYfLJZ66HlDZqIowBzWMYwetIYlbRYJTQWwIVlb2Pzl3IUaNFQQQ0nYEF+KDztIhrYqPCKNOQQZqDkDoBAVWApcI4H5JW6oBIJxbaQ+fNLKSEtbgrsmAkx9unmdlJTDNliM+gA9eJEGUtWDZJ6bG08kmNfK74ClIfdOtSDewwSN3qDKTfXGtUalhjn1DCOoxLY60IpNhMfi55mn7PK5ZUgV2X8vniAecWDbjG1URdK9yNRYNbEY1dEdjdZ0cAs9NdAwnxgB3Ctq5GvluiAImsSsGn209s24MWOZBJMILeMk6vd32W4QonqeYIpaugUzg09WBlRkoOqNVndOinPc2DyQ+v5cSFZmIq69oPXIXChsS2p37LFgGqGyfVJ7D8kILxWkrHKNNcB64etKYWGGp+lIRSQ0wdXkjHlDJSRiWfTj8C4ewGptOKvCg1ly5kooklLUw0MI59zjFLGUTNrkcwtMEbAqykdLEQErKZXC1g5BNTbpwn9fpjka91pNOBa7Q1dS342YWQ3VXrz6xGPS/prnIHwnHA1mMb6kQmGod4YpL1mXSvNUYUGUgjYEVYUFxzyX11wlrCNbHBpM/ha3zDo4DUbTxQBXgvS22cbRUIZ3Bppo/K6BMZvE6K59On5X2S3aFLMk1DRl7gkG+spDmTm+rC6ZxyU5B6M16QmkNO4rikZH9Y03AMbTnIUaUYVBsjnJpcYBLk648YXqMaaPXo1JUgcqnSN2vIp2lg4heKcMum964WtVwHa3oJG42qc7+R5XpxPc6HraXrGNZqkKMqBhpaU26MFKCXkAN8Io4cZSW3ZUi+kiub8CSYkh35dsAn7eop8yDuKEv39WJ5j1Z1iq6S8idEVQ20epjqShAdxbziGvppEEOX01x5nDdbLpTTQKsHqK4EP8myppwG8WyUG5KvX190WdNVdANgTYOTq6hpNsoVbyQbgBmSgtrZ8MmfBueB15pMjHXUYzVVBTPPKbPaa4hqbGjqJH2K9jV3kaYq7oPV3ExGJWrce8rf3jb16d1fgjNU9GW/vaVCY520H357S0CWaFPVSfolX6G07Aq+JOwitRxqtl9eLTbJkl5j/ffF61dP6zQrf399X1Wbv7x9WzLU5Zs1XhZ5md9Wb5b5+m2yyt++/+WX//H23bu36wbH2+XICPGb0Nu+pUa3E0ppxpAV+oiLsvqQVMlNUpJ5OVmtJbAFOeJU5zd/R8uKXYI+CQzwW0/krsE2ykXz9kqeRApNTe4dOP3dngRpU+w09Yb26Q34tGyg4UcyLCqn2AgRN9eKeqTmYpmkSXHRJp7vlIQVGXme1uts+FtkPnXtxXNZoTX9PcbCf7fHdpYt03qFCHNjUjvZCD2TSh0wlxdJWT7mxYoUVIgmthaQQwD2+LvKY6TDV3tMV7hKBWK2n+xxHOer5zGK5os9hi+oSv4DPT82Rige07jEDeMH1EsrGemo0A0vQDPusz2uz3hNWGt1lXdGEB6jVGiP9xJlK1QclT/wiklmHq1YZo+1qfG3PBOGzn93xfajSDattweEdFTsintxnz8CMyUVuuI9zukdvLigxTKHtVzgvCDSVFjL/VfHtXyV3AHLmX2VMf32VhDv4g7yVtpChA1d3JDstqvkSZvv0WHX6jFJJkmbvUtXe5odTN67XHetD7jcpMlz6+rCYxqX7Mxskw/UTStsolskHpOsrLmrE8ycq8Yo2k8OihIlgjiQ/uPOsMbnnHQe/xOt2u6HigMRn49QsMAxDec0fRBxDF8dFIs2KJWIi//ugI0SBBElrI1aMsIolHlgVSB0xwWsm1HB7nB9HzMsiNcVwdBsWFxZdVdlYtfjkzptMiRDbN0X2uP9luF/1GiBcnpAH2MViuxxfkyTu7M16Q+9Y5OHDhQ7qPZVKujz9MP2jxwX9U2Ky3tRK+Y+v2AFp5Eyi6pgzugls7tF2McEjL5bmRHNNGs+7h7U9V5eTuMSd4zAriEUuZh9qPfZRVqzJMdjew9f4oLxKq+X0rriPu/MKrhAxRqXJR5C94WsABGbB/ebUezqbhfXzDlkR+VxDV93hoNMwTrtuUfnI2fBOfrqu8o1HwuEugefgsoxKnEwKCVPp09ovRGMc9xnJ1zt9t08cRAQjsrssTL3egFb981FyjYrq3G+HAtavmTqFbwtyZ2naaC0Jhh8JDRYbR/0kVgyvr3agJikL9qOFk4t5OfZJyIGL5ij3qiDQpnDek3T/PEP9tD/Kv+eV+LSlYvnODeorWiEzQmDo2/VUjSl8SUuNp4ViI//Pt9pbovypnuLGyp14DfIlrJHVXkaCURbFDF03+aUPF/r9Q0qzm+/N+GlRqjGRS/4zC69NQ9lRP4tuic76lFMx5TNMoBYcyiJP3EGGpMjLvl9fvvfVLp9O3P/W4B+390tz0TrrlkRC//dQWnd9C+oRl0aPrsowEebTZE/yHaG4bvDOAtE9rLVeSZtc+MSBzPtZqXAOC6ZnUtF5jxO87s2LYYHX2prT8STTXNX1NVsPFV8gYMvEBkCjRgu9or/vvVZ0mbksRHW+voTSeqmUUlMD5/n9fpqRi8zDv/dAVtSSWaL7ps9ljad0X8hcnyoEuGqRCp0xvs1V6Pty3aLu7kET6GMrkU1Kc837Ss4fyh08ORKynY0ghcX933r88ilWPKYOm3tafcSWbiMS7a3O3XZqMRx8t937ogSxxQeoCbPrR//UWOFhtyUOOiNJc0PJR6Yh68OhpvmdeDIZtN82obXdlfnY16sE1klkErdMS+StIKxNiUOJr/VGmedJBpb+0YlTpei8MXEqMChh13YB5GQo4K5LyU+oBRJ7wb6j+6XG/3TYOh+oy/c1iXl54ScDeATrVC0zXMo7crn/A5noBFXLnXD3MWKUiKXAHZmrxqCooTsVYpoLxZblbLmNDsVkYF1lcgPS/jv857G2OMyeTFyn914UUY1fJ131yQbxCbJRO+F7qMLHiLgCsm/lvvsdDFUIfLtAWdLwM1aKHToo/QO5MTxDUi7EN6JO2331RnTexDTexdMf8MbavpJUtnJUihy0FPu8ww11xWCmsIXOKyf5AnCxn2eY9/Z1kmDrYFQ5/t2JfkcNFQ1p5HesmxzlWvsnrpsEwACV9hDkStO2IFHLHPYWx7zz6iqUHFWAj7OcqkD5vsCIR1uoNzpkhIVeAliFssc5HbN3lZf5d8TQREel+yb4/MLcxDoGP0Lqu7zQEfSMS6f92MGBLsqo5Svnz1fPsfnzrO7jAZzu6cBJMRbzHHR7nAmr9sFMiaPyocv9fWnMvHGU6IjbMM3NwV6wMBBbFyyb+J8S8zd3dGG8XWHxfP+G646DTfH9QndjVAaTFVsjaoloEUORQ44WyeQtu6JbEeEIRxkQV6ZG1ECuXih3wlEab68HJ/QQyiVmO8a9+4of9GF3orgQeDvMDCzfwAdicI7oCtyuQEryMjYe3YWAwB0ulHA2LfyHZf4JkVn2Qo/4FWdpKkg90GAWR8o3LMAhYp1L5c62NrqNFUilgq3edPYMRFaE31Nvh8Eirf9wKKro1Y+YYjDBmPgLaY7de8CmcsRqF2JEO5KVuP1tqjXsIbFFXupVwr0MIR775kTHEwfEMJrDOpGlEAeNsKjpXB1NS7ZvnKy+FkLHaQfHNZHktW3yZLGzCjIjlZBFyAqGPtW/qjEx/DNFxcPhi77u+i8MHx36E9bB1IaxDIXb9h/1LhA59U9KnodTPCLhSCcWxjUDRj/qNxh/dZEbpGFv6SKxtFqJWAT17IR2mV2u5wD4uwO3x2MLm0dcWb57w7+Y1naLE8uLcLIkwwod1l/T92DKwV+GMKdGqdPG1wwY9iH5LmEKSPCuLfC3FMYBmhtqaEctJukXCRE2UIwywDFLj4dfE3pUl8qdeo1dTk8uisQknVTudTNv7GvKHvJAsUu67JPeyguTK7ARX61lU6elyn6jLK76l6UYBCEbwsXqMC5NI8qGI9WmILB0EiSGIJw8ti7x5vTLCHnP0kojopccKrDQ4hlTj4imK7kJO1qN1c2ks+IAmp7Pp9n5Wkp0ZZ9cjEm9uFDRTYTipx0Mmp4zh7IiiWVm0tHEbsSyMWMmS9//rVOmOlGtGOOipwvPFj9o4cEp8kNTiX0aii/luBBwBAO84AzDXa51OE0kD82Y2+dOaXLB6Dc6ZSEb5+ZveNjXnT9O0bkbCqdlNSADhcWyfInC3pMY/FLj/7EQsfztjpfgXTwtk9toG6TmULI1OJ1vYbnHYZwbSF5MrUgQti30NVZVEgI1TkuccXIshQUeSoH74HKHXQjvEJdz1oUgnoEATjyEVq1GLC4VQPFTlKI7sPH9fNxXVWSm4VU6oz5By7vU1xWGvQiiANlGtmbIrL8L8i5VLYUwhAOlyfkdMiq4qX4HmxU4mKPlVA54zhPVwCa4auzdfiE3lhDduGmwGFP3qAlTlKgd+MSP4z99eQVXgOXl1pIvxbbC0xjeyKcA4e15tbTrEJFCTEaBOCkBVBBPMKCIPbRAjpZBCzb0wE6nUyvMGrWoSAZhSIn/QaV1VFVFfimrtBJvr7BGTvvA+MwAjuNhcjEJlXg0WaTYvHsBALY4/+B8N29mKai/eZAHeDc637S/YFXIpL2kwO9gPF8ch5Pv0foxYsGzKMtnWBRAm3T0zGqW1mkd4G7FKVFM1b8gIpnOomSPVEoc9eRv2VYutkXy1z3ovIqKfDtLXSVAgI4u16e354X+A5nChdMvtjlFFeidisGjE5yqQfmLygp6wJRuiqwjyA8Wjhayy5jUqEHXvpDi5sHcMBfZ6sUsYto2XIrFbrivUAFDWgAG/wUIJ5tUBrom+ghvEeRN2ZDsndoR8KD7YxLVq90BPlkdVg8nLLUVafxyqL/hvkedT0G75GEMqfbEsIpS0IiyeFGKHLvqQoxVO6OHRI/YtnLcWFtb5NLsqVt8kx+/wWVO+2jIFY/bN0sMDcOdnBR8esYwtF7pLkvJZoO5D3CF+6M1IuU4C0gu9v+pXajvo8yluGri+Ic9/HXcV4R3VWJFSh2UbRWd5AKNHx2xLWonkWfP/67i/EYJ5LBmH1yMYI2XKjyIYXKD86YelyN+7Xq7lUudcCMmaubhHL47NJLwnL4n6K9uv/q6XxaXuULctpfVjB+E6x7/8+hSzep0NGIf5lk0qPPUcHWHaknNifuj5vpLhqv4pvo9sMcdpvUafUdo8cvkv4qFe6MKthKz8A3SQ0SnzdJqprTqIFtc8c4S8ScS0KRyz3UGsk378PXfbw5WKCcZkDMJH13VODi0fUVCT4u7ScnL7MiyUosOWmOCrYpAr6gFU4ohwPPlsWynREAfMfCpACPyUMU6KtPIw9ovwVJzb7szOy03gpxpPQIl/8D0rlldsTY6jGPEHsXQoJ/LhS40jlMPitdW31XDUCxzTY8FVQGCBXMwQihx3UwFeyfqeBwRH7pR+RYZpst3xW3V1xNpJ4Y18YcwoAbZC2WidTntmnVtZofNm4gl3WKVLHMLcCdXGPhm9xRgcNdRxNjVBGSTS51sai2z7Rg1ECxy+VuWRHhzQQtn2dZfqinhvNp7UL5bBqC8GoheaYM0gWtVLUiQPm01E0AuN1qwPxmiQkD3fT0AA4OA09VkcgnY+7z7ohkzs8wUBZzmHyEsLb6rh5pSN28+ISevidpLXlcjIqcVZvPOSmUJTZftE116axsTfKiKbH/vDM83oq+xpuOutJFsQIN6PwNQTocu28Lat0TYYxSobsHM+y77KMQwXrP7njChQuiCeOZ4bRCRf+IRdiP5VKH0wxeoav7en2TSXk0hCJ7nG1guTG2/uOWzrwv+qy6K0K958GGJSPLeAF7DJFvRDnNDjB0ABbZ6kyBSqtcNxApwxhf4IGPqU5KpH2pi9JyUaDGECjHHhkV7RqfR3I4HWPz8Ts1Yth9vUXlWufnVHdWfiQitx7ig4l8JRW/4BuydozxLsoAhP5Mu41rs5iMy/dfd9F1YOBQBmYP+G/xkj0+4LTbCKwMo/Znalt8u8/e8Ega+55kpTXAOt4pNiqY9M5PLHN12+38PRSeu6PiF7yodJMVmGZGg9kn64wTumlWFNwHO/6fP1Nc+BOS+Kfj5vU5YJIdFez46phiXURbEftjoN9NDp1yd5hmP/uYLFG1yItKwjkuccTYuWR9wqKRFijemRXbzgxLrhNBIyR4/NU/sPK2V+N25+V7UuAkA+MuRZkvDX7/eXRCOpHmERhxPzwW/jwx+2PE2Xtpka72Nc7MUVniuwytesdUcbMHyp28FfcmQtNZyWLqCow9fN3OmX7QZ/9zLdzxCUUOcmqC6M5MMzqvq/NbhoJpdFA0VRlkZ3Y/nnXC9jkek8eOpq++bd1EvdYnySh8cOOZWLubRKWLp8ftoyG2raQ0PkHl9thp1BPyScp1wH93YeAuPY/IwcN3j+2KC/itvIAWYF6wAVec8ChrLsJim32VxTAn7d5WNY3JKP5mtf8mI/Hyg8DFXUsD3girSodsV9W4OHvXbnHJBBwSjTtm1m8A3jTN8Bhy32R9zIe47EkC8ErB2Xh2tPp7XVZytjep0MEUxkxWKsRy6TxOf/PtbexWElI3RwUOBkqc/VRm+JYKp3TO363jIiPnJGfG5l452sFRgW5S6Sr2QSFaIbCDXD3I1YNc/ReQq0Oq3hAZ2uewdZeX6qrTyMauvT9qLF4BjUocHtuUfa7db4VwKyKWufdTQhmID4r1J5a5yMqsQk18e1FicgVOz9OAKHs+IfZOn0j7pWRT4T67yMZDuL4dDdfH5f0KEWA9Gg8Jpqm7+5cDYMAEjyAJuvAY/iEx4r0ddvES2RIrE4FShr9n77B4MLK66jR8/DnJ7mpA2PDfHS6i5FhpznHS2MtqyXsTztCj3MLqVNixmy8OvBrxtfKu509p4rrl6yZ6lHxXMRQ54NxsivwBrdq6J7KbEgzhcGDNK3MjSiCX48k0MeDivzh/2XEVt7QjUGW1yJL0qK7uSaOt3/4lWjJahuwSOsweO4cbuml2k07LUGkfjkGf1lIUi/aT2xGEUuVsRWlyi0UjCFTujr01pZgaAcDs2zqnE3uV/0TCMuS/O2I7WhKdvVThHJU6acYPeIUKVYw8qHxnVvvHvKjXF3kZeIfco/FYx5q60yzaq3yDlyKK/uO2Fr+c4Mk1t9PZxdFqRTZlYS/kPm9zs9678DCMLxlbRFgbDI/v4lBUnmZ1sBZFFP3Hra0OSgLIBj8qcDiiNDmEhNNJ99FBKe+k51gL77863DJgchIW7heaTy6H27KiDcuH2+G7OzbVTELl7thZtEYQb1NykFluMisJF1a+cmpWEfVHkdcbUE71JfvsUPm133tEydJ93oaQoksTVJ9GBQeBZckzh6h+E6ltTAREUNsYHl9xqKg8jUzcPQn2srl7KuvpllZN+xQxZME00+m+VhT1plkmrDHoTfiowBGf7NbBfd7etWick1ebJ6G1IYg45VKXi68m/L4CNVDsOC+LKqlqCa9Q5N5fGK1c6mBCbHIewIilQme8TS4FpX1SBeTOcSd1UaBs+XwiJV+FIVxaaOpdEqEsYuZL3Pt8lTy1+xFkXlBDufgPglEouM+ufF3fVHmVpGfZMiUdg9hbhPBs4fTJ1EIP4d7CFa3fp6nRjQWGDGxROzYY0rXFViRoxiZCeLagGYsI4dkCqSuvPRjCUz4ROY+pjpmkHxECSWYBHqNtkJgW4DHaBslsAe5gSW2qCJrp8NWRP2Cu8+E0ONOCUOTaO7qMof413+2xXaLbOluhFRR/RixzwfqYFKuLHGdV+QMViMyt6ICjAHHY5+7R8mdeD08tlOc8PWRAi3LgFgWI++6tcueCyh2ceW5vcYqBXKCjAg+NfKPQyDfO7kv0XECELjn4EnFwQlgEUjH0kA79J7UAd+v+qxsmWQUdvjpiAsbsN8IvSfkTrfTUVMG49fnk4eG93OPmqxum06cNLpjTy5c8E4OAgQC++P8LJQCVxXI/Dv6AC7SsPqAbLPrLqYBcTFB9taMl20E+5SlgjlJBhbQEcZAayqul4yT7KR+1QABv/PJiBQH88J+dqFHTMi+sbdZFJea+3Av72U0imkfFQvd9gekkrb8mvEOMIRxWWk10xgL/ky1T9uYjWULB0HVw4a3JTKqHDG/xEpVSRCoTrIt03NAnnhp6whAhLUAjUkM5eUL0Wp5mQBowFyfzYnmflEhphQUBXM5VGPYTHxW4W/yakxds7+vK3LHSAxdZ0pu6avZorZXOupLLhU+cRNr7cC3DH6Mu0TrBmRSmSAFi38anhL7Paw/rX/OqD3o+bkcD5iD3lku0qa7uMelxQj4zP+FPSbY6f5AOAXrQnbmC6g7530pyXvuEyyo8VxWA0idhlR2aaS6s4qb3ZnIVvBBx3J68l/yWmOsPfMvObBGZC0Dpw1x2aKZhrq5tEQv/3UFql2j1A1f3IJNJhW54gVQc3Od/AcaNw6sB/DnbQ+NWBeS4BUg0qYZy537o3k8sc9iZAQuxu2X4rOx6wIJvJ0B8FADAfezkMLyBzmdQuYu2tcQbGlxA1mOFIg+cwCspscxBF0cZPWnI6jb33RUb0MFRgYNVEpWllNql/+jCTQPZmcIJhbqVAF6wVO0lRgSXJ8+H75q6E7o+0QYV7kpD0fz65EXE6BLx4jfQ6PMsYAN4Gy2XemAG75rlUhdKqvrr21d1P337CF5ye1xld2egZnsFB60A8W0DJIMCxEFlMF7Nhl7JTpFDowvgBEQhEIpcNqquqlLtAQBcvHOXKBvCY8nB16Rih74T+fkDyKTDf3dwu2SZ1OkuIzhcct+d5esJfWULSdimYLe2Z6KIBJ7QezS+2zNcd8rtOXwrZX0ePwTIncUqc3TMu8sn0HufL37BmiJvFY5jN5Ix+piNrLBMw6lx/cubIQhSqf3miuWYlGRSyK1xkZvdCTrN89/nP8vt3QqiN1dNOKWQVdNh8UlAq6y625L8qkiWP8nAoItSscwBK3V9hPSUUYHjbSaCr13FMqe7SZYaD0QrFf4LrJ5wYwWPKWAVzWmy6NsE7t677x4GEHhtOtuP9ybe8iWq6iKjyaxQaKCfESovtUVbfyI2ivSwLjYDxVWk4tm9LlFS5tnHvGhmS7S1C4UueNm0I3Z2F00cUqE3XrX3iBbQfd7gEJJyqQunJre3zcFXYNbhu4PVZ7XGGehWOC5xoTS3fOHnigqQ/XQn2tqmn7M/T4jwjbHxj7F5bf4mFLstuS+SolVd5Ne1fImrfQ7COC5xUXYGGkNX0lD51g7tO57fuRsVYWFU0HQaUiBuGGIbu+1B3gHPwYIEnoTOQ+JZ4JhG5NF/Bftt4uaFsMvZcZlFILlJUSsqYNxqKJd+XyVPp09IIsOowOnS8oSsnbu8eJZiEI6LPETfjuYKVtOW3b4hkbDdV5cLub0MlyNJhwg54WCcMUTXrHngpNYljQMCmFM+HvKkhUmFrVmxlnVBH4O2Tydi3cZBWP1u5CwxTbPuxOblc7lcfng44Mx1cbktApvNJteflyn6jLI76aU5X+CI7wIVOJfccIQix7stVlsMb8IXOJnjIieyiac5xXq9d5bhCicpuMDFshe8ztkEkILP+V3YEucQeaxube1pFjbXJHj+kIu3ZXWCX8i7W7H3mTkp5mgMyqK3BDEpjGEiYzB75dymdRLE8bhoztPEaUYNFUJ/+o87w0LBcs1Pns0ox0hTn9EDSiWnXu67kzW+qECfrXGJPUaakQ9EOCpw2Lg3cLqXjU+6l7i3A2QkUgrh/qPLkeYWFQUqJFyjgm1a2glv3YnH5+6bPZZPVbWBQkzw353cFoEHbcPXnRFJLEg6H1ckQsB2Hp1v3HY9jon2Nq5N+X2WXLqtpR0rO1C0HFz7pspdFOzVSivvwzh+jMuD3U0IdvtS/2ORr1XcLZa5cKYK57jEaW1HyTUVIUNceYkS4B4vcbxZa80Mx89NSC3piZZY7IW7f5GsRM9BvGCJ0cccDTz2dWh8DnzquhPZJOCUAF6pAGIZyiDDndpsty3PseU9WtUpukrKn4FeYxwmH48xbfVpuCb8MH9EuFzKCM4+uQiYPDt92lBGlUMsC2UOwl8Kv+saetfVUKHZvjfn2WlRiIJ/VOAwa2QTu6xlccx/dziRJczrtKgkfOMSN4yn2QrE13137F/NMg3DPeTKHPsozwj32WUL/oRXKzFD8vDVyUvwjrL6BSqWwEW7UOiOF7SmSIUO9ob88Tsq5FXLf98ZSX+0TGMkZe/ReNmBlXWnEfBN2yKO4asrJnnD4L+7n7Ev81QZib4rc1mIZ6tUuilsvu0MG34rorBhj8aDDTV1/7XYcJHWQkzT5stWHPsUiRn0CRm2FWQPZajAy0juxyI2n6B7RhS7ztn/gZ6b/JcjTMNXJ0wSEpf6QOhI57CRriarLfHx1T1ao+9JgalSH8bEI1QeHGyoPw37skaFU1Lzac5D5L8Qw7WO2UF2BvrTx8AA19tVy4J0h+l4d7koU/AAz393wEadCOU7Wu6zw+2lnFr8k3Ne8fwuv8BLmswAuL7ni7b57OFTtU6P85W0P/LfXbyZsoowdhfM4iuqHvPip+jcBMM4ObuTBffMVkuXKlN+tgfDOLdy+rS8J/odYkkK9I2pQHdGtLWdCvUv78bm845DWXVXhZwu/6tf3lc5TqRHkEi62D/npFDKZDQqcj3vf8yLdVJVWEwkIZfO9x5KuUDrmxSX9+LuwX3epmDdpSewylHnNCXIabYiMyrMi1C0MyKMsUn7KYKS1oL56mrK6rt+lI1spNkPzX6UczbQTYVH5eOloq8/DfvMlTqdOn8qw/5KhTvDHn+tUY1WLKD9UVUly/vwt1EgSg92scQzDdtwjYuIhCI3iZHcIaqmyCwiFbqwuPiUq/niwLxYvu/ovjnsrFKQY9fwxuGa6xe8RvIV+/DVARNa4aSdFZE2YtkuLudoizhs6c4m5wucF1iM4zJ8dXNHlJ0QXTHIrDx8dXFiFF0X3WrLvei+uXjvb9JnsSP9R2c8cpdGBQ6HDSHp7IlTotnjpXCeYB/mdwal9iahI+zLNg9dC7Kmr9iLfMGq2H92wwV0jfvsoMAzXWAJ5tETy5x6uPqSZHWSps9SJ7mSnRHy/FDDpDyPyUPM66tPdByUkzI5p2NqzRuyJBoVuFnOZcO50+6VF6JbFfvi9lookwY0fHVRJ8tSfpk5fHX1VFyU4nwNn53G9wHdJnVaEam2ItyIk7SUBguB7My6PUnWmwTfBT6P6rD4GKKVVXfVEP2Sd9k9DZnV+mNeoTURlaEuNgIyD542YthV1ib6ZrN/Nm8hRAYAivdx2cRzRJtG+ZvqnsZfqVRhHGxg7yBzDVDsg/u9Hvf7ENy/6nH/qsa9JVH3FT2Wn1FVoSLe62YYp4fgs0U0kfwDW5dfPOvg5lX63Z59zXjY3o97pC8oKesCNdEPQzd9DpXXlq+tv6sb/hRhby7pZYHkV4Gd7HJ7F0Cynf8PmDRZBgtkEZs/Q2pQHHjyhfPk2XqTFzQq9y0O9dgeofLgRkP9XWXFj3kKZnDnv7vdaEJR8Pjvrp4jEL5xiatpLMIj3p9YeJrRfHG4JEp+Shmcf7q5RDNPrPNMPOnw352eiXzEKF3Rv4TzmFDkzg0neXaL7+oCuH9XgDjM6FNVJPIVOPfZxaWYIugcrQRP4lGRy/VbWafVWXYr3eQN3x34rgkLQPpAAwNI6qtUujOCevGcLeO40g2IfDzpdLUnujmJ5ki3yOtiiaT3XNznbTnlMT/7p0pGNypwHemnpLyHhtp8d3U3PpMinw6fXXEtqkLhttyVuGI8zvMUwtd8d9EssyV4Th4V7IxYOH2iStMHtEnzCOHBRWw+d6tGFNNIiVZvlF979J/nVApjqUlxN79hViCVUC7d71vc0BdvNPrlVZFk5RqzaDkQzVQwYa2Y23BVIptDsexwKZY5XdU0Jxzpsqb77HpFAt8n+V8msZrgjdK4ZNtXOZS38QP6Ir1ZGhU4rUXJdaH7tmP7VhS7wwiV9451sDsw8hIxUCEx5a5U6Gqok3vp91iB/PmAyeiU7yGAcpc731YUtqwgXP0KhVuwmQSfXTWzTyZW0si5z05zROWqZKjgv7vPeGPekM0UUPm21Kvz29sSCVtN983xZh+4z3fyf0iq5f0C/1PgYe6zwwyQ5cRCHYzp3n/d9vZ5kq83LJamTotQArneoP4Nb46K5b10IyuXOmBOUZKJkWr6jzuzZR8ny59nGZn15c94bgUKpB7buDWmaTb0WEGlLyJmNo6c/WbfAhdTb6XbhEXuKCK5/QEYfe5drdDsquL5HaNH+SQ5fH3Bt6Vdlt043CRi8/KMNqE4cNHOcVEry+MwkYDMKwmDAcOBhXaOhS4RnapVO3WhOUF5XF4ZQfUIJjP4NxrRO4Wm9M4L23sFtvcvmp9OirwsFyhNo3CUiM1nYzOieLlcNTUPHJVlvsTMUUTem1DRXjM0IY2v+fDR5Lys2YgMNaVtR4DnwQHuW8lmCU1z181FN8B4VjwjIYeYiJK571Vwh6+S4g5BK8Wqwzwux87+9hbkB3uWWdxj9mcTheb6EpVVgWk2+BNqzGneXWvcUSxqS04oozoNoBSlDpgCc1uBfCM0EIFrLLocyDkN9eZmmo5hrzlbnC6IoQwtRytsYLrsk2baylgDp79HGGHigc6FTXSLZ2sTfYxTejnRJwe1mG2ximrKXeZ6jDOQpgKymNMuoA5jTOeOneTZCtP5fHVWfq3T9PfXt0laitcJptEHMw/RltmNwPXRZpNi+qKxNXgYNhV9PZGNOujOmGLBTroGAueqRx2Bm7TdDNw3WmLNLU/kIfHWVEeuEKqqGIMH82KOUTs7zR/jnoaxCI9r+2zSn2ydOGSopWIOxQHVitod9p1mib6TYdzQopmbEbpmOytncqc/xULgCovpAGNzXpURhx5SvQhq3bkos00wbU3htLZcqGqo1E1HSwWMfhePGi/FInGJHpNidZHjrCo/YdIPoqZ8K9HqB67uW8urzhxurCwbwKUqFnxhbChwBsa4IvCJucO7eEoxkSGewOlsNS5HXKlOjDOugDSQj0RsMSWOiHsXGcg8fjMLdSZ5+pwrwRkqRJDe5t9+6f8uuw9t2F7qDZ6WQz3q9rlOGEHKTbJkFr0V+oiLsqKcdpOUqAF5/eqi9ZXsvG9bA+w/0pO0SWLeARDFHd+isrrKf6Ls99fvf3n3/vUrloeYhgFKb1+/elqnWfmXJZvGJMvyig3999f3VbX5y9u3JWuxfLPGyyIv89vqzTJfv01W+VuC69e37969Rav1W7F6i9YKyy//o8NSlqtRsF7upqplk6t8g5djnvrtP5DEDB2TXKLbVyp++u2tWPE3gCdp27+/xpSkbDmzbHrsirRxoKZQiPXy9SvKdtQTuGe9t1r0vGdz00z2kBTL+6T4b+vk6X/j8VWFnPJL6m22TOsVOssWmKBNNh3SG+pv6ti1s7J7pUMKKrRk6c390Q1PfiKM8wpXaRyKNYHEIiD6gqqkjVJRRkM4CtYfCWc82klxyfy54xJlRFAdlT/wim5tAZgaDH/LszhjbND9KJJNmztL0Td7XIv7/HE0B/6jPM6pEhS4LvvQ5JyYc8TBhkPPze4U5y+/9VI/eYINunsr+yGp//rVl+TpM8ruqvvfX//pl1+ckY4dUmyn1HoWiDZUSbECXu4MvPeYgTY3p2El2GkHnb959HlkOcrwP1GvSr+QGR3CSHCNiIrpX87IZvP0++v/i1X6y6uz/7yW6HFNn5jkdbb6t1dsLf3l1btX/7dzd/js1NE79CefDrHcdEPaZJH34/TsV9qzQEHW93SqTr6P1knvFW+/XFs+eiGr1Ch33/nMRUujkzqlV1QGue6M/luG/1GjBcqblJM65K564cc0uTtbk653D3O16P/8iyv+yyoN0RAjqvlcfkh/JBOrOM2abuLWXKISsGDt7boL2I76mr38/MVj++noOYni1SGPqICdlTRb0kVa3+Es4OB3Vl7l9VLN9gHHItEN9YVwqpVFLNDCNma4P//ZGXV/Hg1DbD3X6ovKFzXPwfPysUCos/KHbDRXydPpE1pvguxRBEm7YTWBfsD9ykaIdEHCQyy6zWJg/BOAx29JhQi5PE1fCMNvfQuOLVn7yMwRjK1R1ENqXz3PPuU0vs1dEJ8fpWn++EeNyors39/zKgiZn9YK2YSSgl4FIvY4v8FD49tWmM6rG71Ps1UkTN5nBCcZcJSVj+LV+d5KAjogdynQ1NoRCfC1Xt+g4vyWro0yhKknPtL1nl7tFc7LYCA+7IcbEw01gxjpbDNyjbE7ytmdl442myJ/CNsIxmFM1PLNzvzDgoh7IXNm0xfGn02WnaahmlnRMEN5i+lid52HPgap1kTmzHJtHp+4SFU+D7HwfsyLdVK5XBCpcS2StIrdz6PVGmcn+XrN3ZuH+beUUc5kR7e3OMWEy8NIF34i+4BYFLMRCrX89pDQ7YmvS4470aFP2+UwFvqclJVmn3HY7K5HqISevXPvmX57celYj8nrhrGsPud3OIulyBN8jK+JxFeidKV6h9BjfMCjRDddR0KgUnlsqAP5jrp1R8Zg3x9rRQJ8M723egTZmerOo3SnfAeZV67Vacp25UVDBqozXpjovXmSxfGRbIMTeBxYuopBi5egRxfUJzpbetjfhOohPTnh/O2CCNou9Hcxkb2PguxveHORl1WSQtflfsbB+zxDjcUhzupNniJiCzhs2p8PocgoeyvWJ3ECYSbbst3zg22/ZZRbpMe8yTx7Vk7gO3J1XyBkj/9XV/xkiaACLwXcXobrJkHDVf49CTqIbNGHZBt273HEoH+Vte/l+hz5sUQsHjm7ywj1Tu7pq71JOIRXTF4Ig0ynKW5xb7q5KdADBk8yocaIfXCsO07zO6oTvhAW3fpNvt1B1s7KYvMKzl5vak2+Qa6i3Q1Qi+uEt1/6SOGveRUb5RB8JHCn2c07fd2LRt1mvXOPG0M7G9EBeo5j4gUUwWhvxWw7GtpC6EVXQbrA3r30wa3DMH7HJSbQRMDjB7yqkzR9DuGNSZTixT1L3xl3pX0k0LFxRr8r7BinjYMfNtXxnMo6HLFOFwcpPdI8OhdpomijxygKyGVCX7wu6nUk7SMKvg7ZFdEyU2Gwgf2LhbK39xwtd+X50eJnHX2JJENQRLLDVLBJ2f6K3KMLf1QYOE9O2OBZ+Qe+rU6SIui02eEI39kv0T9qXKDz6h4VF6OwkL7BKBi+QUnQC1ZyXHcXVjWdnAovqdJwtFoJTQZ1/6z8kD9maZ6EGQNaHGFT8y1Lm+XboQsa2Zfuyub8VsLn5XbaIjl92uCCrZIPybMKo5XJsEXIHB4YwnDu/pSUi4SmO4wxq2NMHpegQv2QW1AyMOrNd3RXIMRrfT7jGiG6Qk+x3M0u0bIuisAboB7JyfMyRY3YCJN3PL4LVOA8cJn2GNnmz9AGLawzdnPW58QNkWWxHnMRIctCwyVph62xyPencLTE6ySlUdPIr5KFP3v37+Q8S18Dk23So+tRfBXPytMyiIRc4JwwJiGqDjVaZg9kiRFkzYVU4LGKpvf8a520poEASd6cphi+o4cEk7o45XAGmMPBPnrtXjiLNt7P+WMz1tazLmwaiPaPb5/ZCfxjXnT9O0bkQBWClqZwZRGqaJDDQFdberhTZqwNmBS2fZGZwet6HWNiGnzJUyx8HY5FhTbheFjAyiJPKZ4gnQSvUNezFmWwswVatRgxiq9vk7VMgY/r5+O6qnLVY35buUChf+DyPsVlFY6wFVgpIouP7D4jk5CXkZkcKhgqvAwyVY0QRN8fz9PVtA20Z6kTeuU4VRuLDcGTpN4Dsbod4trob4qu8DrGHQ+Pu705ioS5s8SdZhUqymBebEX0CCuamIFaMT5rm+RQdIVRs3aDNjaiIaCyOqqqAt/UFTrJ1zc4Y8e6SZmV9L9LOVG2KSdCRvED4bv76Zbv+CwWHf0PvJoQ+6dpadPvSrFlTo84rsCJdaESx5Vm4oddu/K2FRw5fkDFM51Vd/vSuHaIdalTT79lWLzmtejHuHaQrz9rrLxKCnx7q7od4D103R+iNS5v57fnBb7DmbfP3IAgZLzHSYladSLY+tPj+oKSsi4QnQ0t8dxj4/VNHK15d6DY22LfDP0xbsrDzHxcZ6sUNVGXAVtn6P1Og/4CFWcVWsewvY0QUjLExLe4zxtzHtmlJvCu7bSZF+JOY3Qy8Ym819Eo/LLjW0mndUkGF+hm0adzk7BFV5S6pvwliKWtdBedBNuLy5LsIZs849+IeFkuJCyxHKS7SWLeBGwjDGPUAQ9VVRwuS+xfex2i3RuRUkc0V8RW3BLZ6ek4r4gSFh1rsroDFQB/bIvqefDM8jPU48RovnXWcNulEMeX7+Ax1510mUdsnMvBC8x8oDwCmnUVQw4d1KFygf+p4lxn98HyKl+QE/yyEhF7BP/tUJyPL6tixcpip4rLJLsz3Jp48EdEp9e4VsEddC3cC6tRLBPZLlufbpM6rb5j9PjFL/CA/TOLRnC9EPWsHc0xzpIhoDWh2g374LdvkQnkL5oVYinMqu4h6cd2cw8EC5TTTA2ZSfH81ce19it6DBEhZ+VVkWQlFv3pLHbifiFec0iC0t/ol3Zwl3weTX5BK5y0uTfdlZVx7QkiN/ENvBDJQrOSmgVKhAdvL0wggy/UbBTqrmLQOplOn9+Fp+j8q40Xwi+T2FeiH2U5uh8O83EP84cDuKFv+3cAP5xPX9r5NMgQ4nxn2V7OrF9OzPtuZB6eJX3NUMVEJO1lnaLIedwXGzT5fWIbJA+OtORqkOue1MRAdonKishUJvj4lFdKrHb3lB3SC+WrTq+p4hAnz5QdmncxsZF3FFZsYoHY2x0miMKnT1WR8Ie9KYxuvH/YCxFpRsX9z+56+0me5sUn9ASmQgxF3u7STZ7MyO5Y0WLula0l2FpvdLU2ND5I1AHphfDhlg0OrU+XbyeE6kFdifQgcTmlQ6Hav2mO0EOWt4cTRB7CKWHz/lVD0JGIPmK7uq/XNxkXK90HURsHavvHsxd2fPIXyT2DNPzyQiT0MD5f+djUDRKOPWkDH/R2aJiKEobrrCRCtTEXTXKcbtnrhTnkbXm/V7gd2TxX6GuGZa8rPxIxVw9Bd+YXva4c+AKvLbbMhTxFPa6DR7X/1biRvR2+xUtGnV6eH/gyBl/CtG2MPx5Rm7TYwtQBemvTaFnqRz32zobdVb76PDOdFcE+4L6GnC+E/eEhxuK8nUlgbPZWd3+OGesU1rwVHVnwAu8AAhn8hbD2vwb32IrTeELe7sFOskTVIi8qDo/P8BiezoPkE3aJ4++q5lwldy+Y8x1dhFyJ9z0pcJKBYUheCFEniCkMB/SdJ05xoBPSlKFl/IO12GD3jtXi7nI+WXCBfYphcFSW+C4jK7BzP5sg6NshFIlgYwjPWL7lc/Fw8/Of6zj5baOF5GRK0nldnd8ylGyQU6R74/nhheyhU7hAaG4e4/go7LX/wwSq3QvhxVAB1/3f0+X6S8I8twYJeM3PctADK7Et717HMs3QaATkExfzOjgAe5MYIej4Opw++ui3YTeOymU6A5+8N06L60p+aUt4khgU8cxG0TNnRbPOxNp9ZrTOCOb3coH+ZZQir/P5VveLwMl9IROr5tzw6eBxBd7rzSRDRZaO9PDNx2E69nAbm8zq72Qa+fw28cMM///tXVtv3LiS/iuDeVwszuzM7gEOFjkLOI49MZCLx3YyOPvSULrptmC11CupHXt+/VLUjRSLd1I352UmbhaLVcVPxXsV2V0aoSHLi1bhBhpyOuY+nfsQp4+ecoKa7yS4rrvqE8JVucahitZ+EbSVqNDnwmy6KegPl/nKXObIX4N6+fnK3XSXpm8dLrlV5/dT3DV0SuP/O6GYsLyPkSrbqOmLgaJLMPgld3qYAbBx8pstP59Rsqo9KVSHTvb1QgaMTGXF7OIZy1b42qj4EdzqdQe36hLbrMQ1TnwWKn+yrTeHEz+v13mIwdT2cULuoovPvEzakMYOrVjRG+APUbo/WTmPvqbb+REUGslum528P/XDqgoz7oeTl3ee88wEUMdvyg7VcsrpXe7Z8ZhnT2jX8DqXXDTS20jPSt8sPQZm8vrY9pVGQ9N219V8Nk+j5OxUPlQusr7hfoO22GArceHtoGw/nDu68IsD9ZzeefVR9dYVtdD0yLbZ3vDM/XOFrbvsEfn5Qgi7s+0WFYU/pvjPJ7x6z52iVml/dJdZfjpcV1lY1/GF3WXHeGv+eTXV3B5NT/1xa2Ub0VvLX5/tdnjs9J8sZGnRK8j3QdCxkg+EKGQO0abawj+Qqh/dt7qbDBfyaZHFrdhPtSc2yFcF7ObHeNHrNHuOirKSwvE0oOEi6HJLbiSwnONKaJkeaE3O5/c8Ox0tPVBT1/sLZfcsi54XX5+aUcfpW/bhUKpvD5pU/XArQS7NLNM9kc9yJT5qEe5hbXDzuIunjdzaeOsALdEFfjZst7NR8ZPfKpjuQGyy9UMTlbtZG1vE+RvUdzthbIJOW0vDMXA5JCQ1b3Fbp6Eklmr54NUE/fbIqo4f7jmgfIvO81Oeo3T7AqVYtmRcM7zBTkbz8PUf1l/lXfTcjEruK+yvkSAGgr0zw4v2En8LyVW6TbCowe4UMo1dPI/T2F3VWJdEYSQNmUbH0bTxDeNo2DQ2qma4IYOP1bwxxo1hxx9XY0OUXCIU2qbilkMbWNxyaGs3/P0khCA4CQ5El9Do+q0Q1xGsCTxTP+HZzi5wjpYb9D3Kd9cZHuCKP1GOMLTcro6cP6DtY3bqr3n7XjVyDXgL1NFOAgR3lEwvmdzfx0nsnH6um/UfvehI7tBUy5gql06OsB85x/3PzlOsuh1zqWr76YhKJG8TU04/xwSDxSPaiUznLOn509Nv3phdPB/jnKycPmZpH3vKI99/ociP7jQu38XYuZXvEMGhPSQpNmdbMh68z5Kdp77imXsEAsX8bZQ+eltKDfh6+8Rovlfnvlk2Kb98s736FnkakBoPTSYFzf0/P9/ECU/28vgv8qWR+/vRlg3PHIS9N7iJGrhBBRU3yNEbHat3hv6NwzP2KDVed3ZzIv+iX5+qugXyvZ16HcW+bvK2K8V6OePHpg3Laq2CP8Ljqd4yDLLXFTpL6XwPNOjlwg06RHEqDrOrFbYyql5sNYvbT1nZxYR2ut693aJjefcQY0kj/DO5L/o+Snefn0wmucbJUL8UeNHwPsY4WE0Kl8mzoZKq5q031dxuetl/hdrQ+T2+J6uIFUKnVc288/qaTv33pUC7P+PywRJCg+rOoviMsz8tWFcC0HaeRPWyVeIzER+Xk8jW1O6nUIH3D6+KVlQS+TdyjEHRMsNrvqPHlcgNVvZYPX72NgXsOPp7jHKL0mrC7kvCmp0/8T6ioqCyPDhH3Wx7hEz6HPecR3B+3Ye9Eu/X6eP1gsyE07Xrad/K+3hgXgXcJo/MQ59jdg0FP7YcQ5tRNAl9zNqul+rxOrTJ2NZC2y7YWaD/M8A2AI/zA+6romXlZWL0AUM87cMWCeeEWqMv9vp/hs0XQnIBv4vKyM/OY5Met3rh6QejZsM+nqOsJaLMhAM0saIKDrrei1zRy9qjl7lPHult03Xt8kx+nbk2qtOtWMLhbZREaR/ex2r6VgS/ruNzEeb3lIHe/9g47rZtggQHq06IyNnsOr68CT35XR5tH+N07/FwkVymCzspISeEyNcRZpu8yxO7MUah9gNY0S5Gp5LVw5G6pofzKX7fOPjugcHcozzlaZXlBq0mQIqHd1ieOm7y+Y+PfacbFBVZepnlNVb8TNIbxCGy/tVY79sw1b3bYPSAaRBgzjH3X3R/Xy18/LA72x3iVHDFzDnNBeMofDw3m8s1FoMBMiN3iM6jfFWDpLu3vI7yZuLgtPNU7wvZHbPSdV2OVulOdj9endz/T3zsEe4VBv46UV4F4/cXdDfM6Lk4N8e9oFmJn4OOzg2zzwML0znmmyRL2Yq++Yh98Lwq7qLni2dEaWrDBjM5x925z/IXx4sr4+XUNLaUe5br8JFBuK98TWmcOOUsxl+eRdiIXDbe50e2Ir8fs8GuyfaUV4/Rmrvp6zq1GWpn/vXwHLxf857lOR6r9UrgcP6yTVDtppx6oGJzjfI4E99P0JtDVIcBhJvTwZ5uFoPRBn3o3ZCpGGlcxlFiefjD1p796whicfzTh2y/ki+N0giYz2r0IMdg2cGMdZ/LzhGS1a8rgWX9PrLJ6yGdxP5qMYlVzoxtglFepBWxgS/V7uL1eBusyQf0hBKLZEvZfkOq/vtPV8UXciv8v3+6rNq0CvKX5aXg5or01EKLe5U6yevbhKNO2oDfLNIGBHS2eu+39wjIU8l+3n+3OTe6R3mO8hC8ve6tYlDv+esq+t8DqQ5+EI6r/PdleYSfwg98r6n5vhTwgyKzDBhmYZTp4AYr8aO0Sl6fy0w+/4ISaRgv1pzjqo8xcbvOySX8dqRYCTDdT3Yv8+xgD0O2tmNeE3sx6Lpu8YgDZUXxmNWouEGR40FPsw/x9qWO0OOJWfekc+638bvIfCvxAtbBs/3E8Pawq2W6taZ/o2f7gHanBN1FxeNKels5n/u7xQz0DH8P8gWP1eQ9Sy+ejxXEgAuAwzmoMXuy/2IzmSf4YGft//C0A6HR+ic8FNyc0k1T3SlDPMbW8XN6keduTrwRyTbNvEgjizVqlc2GbNvbytIxIP+6SJ2lwSz8y/KrpWVOJE2nh8uuRCAaNt6+yqvifbzbIacAXvjPfeU1rlG+peYTFhcOW046u0822t5k37+inHZvefb9qf1F4Prr8HSV5/C1J52sKsFwraDbsqbmEeRORrvUuMkSm2snTG2nidcVnrklIWZNX/IfgBoRULfJae+dqZdLahaB0PWjiVX9G2/XdgnVB9awCnUyOt+gwIytZszDvtpgRh72vZmQeG7bIsY7Ydo4vXtAB/Q1yuOK1UpASnQyQZdWtk9D36jDE0TI8CDOmGs4sBDOKwFJkLFOdUhoNX4WifFtAIBLdbdPeUBqgeH3dPZabyuPD9k+u463VZDyedyKf18ekrfZjhr63J4BZWmJgds+s/+Eyu9Z/ui7b67z+BDlL+TTaRPN2Tylgrg4PusiLC+esZbpHpGo5q7ywcwMxNR/e9Bwfy2e0OJESJYnkeVtfrs7ZKbExl18yCoGBkbRvyFzmeUH3GVUYHlP7K3f3ACf5+lbEhcPUzxh8HpmBgzQPtMFvMuqFAEX6Q53pusQaDYH+xiRR74rcUBz38AINp9m0h6upDM1s/D6uRkpibhpGDBCu8/+OKET2pHA02dlGW0fVvRShtLNfBrGVHa7poLVivaoGoTpnrWL7MAnhnNenFzGNlvjdS2XAzs64ijW5Vuc4mmv1fJD+UXa3Fn/iMdQ+NTalTHaxVEDCnO7s7UDRN6jgL8SP4BXVFnuGDeiujvn/SJHxdT0hohefD7vot5lIQS9QcfkxUxaA7YhJD7nMia6cny73fpmqXMx0makqHaM/OwX+VwE3WK3cJfHjs9PMRMvkcnqqdTWMhEXW9tt2oHS3ccoPUVJ0nWax2NnWtKVjBNg+hV2uP+7+QZPs3GgnqOwvHU3q73Le53loks8ensSBe4FQ2U1J79FoXzyaMO4Po+4LRL9zwRU+x26j05JWaWbrhCIawX47s6jwzGK92t5rQN9FpaHyvCgZ8dMa6Qbf78vfCCi5u7dHTpgp7Waex5BFoh44lYPgvXFfD5qqOsb1NlP43xcW5LMl8b6OHzNnXg+/Zbar8Aui5mxe16/eeT1n4a8tB3JJ/S9+ICqD3eFb0xh5by+NhVMRrVu+H8vEk64emmxaXb/2cvtjvMndyfgdz0IHHD4Nttv/o7fP6KoOOWoSaewku9DNd7aRCYJG/fkpsJF6IP4UAHwGgy9w59LWqzIzf6A0agwujoc8eIft3kfr+b2bBAMXWaJIPuvO2ts+oqlj/sDPvi4X9V8jI8uItxFj8ilfn1H5nPqNpPHKL6MUbKr/vJ/P7Tt9PMsvY/3pzyCrgBYLdEunss88pbb8TxLToe0va7jg+MNKk5JeZXec4dAdik86ifkWLrqEblFoqFB/RD3LW9f0u0ruPGkY+3eEpu3LzWX3uJ08KY73Pc2ZyDZKd8i24c0rHg1L7l4bAAtr7e+XM35Ky+vrqpAVedhjtwYfy7D6Pqbva5AVfcZJ2nifVTI7+78l+UV3CunR9c1j9sy9+XRa4Zvs0x0nKLlx3EfBQ4sc/FcTXbfoWOSrSk8dDN7t0oxcy+90zXddNvDLNTzPKOHjfscW+vA1Oa5jM55qQVfjVdX5s+uyvJ4l0dpcYhJ/Bl3q0IcnS6A4W+j3jpQXsK0sOnt6Vu9pvTN2OCEyqbfCHutOGy2ovs4Gaq+hPgJfaRe71ge8RtdEjAcin7su/jad7F5FlF939W/Gs29f+DaTweM3XyePcXYKiEfJ1wVjetrIOpy3uthR2m6RT6IG4wXX5Nn/GflSn3t/rTYqPeVvG0DeZ5Mfb6/L5DTnTNy9O/C4G1Ubh9u47+cpgjX+CMkIQVmcR2iClxFYj2aTQHsrvL+b3w8w+xcj4ETFKV9JBWPI+zbaPt4leLe2T6u8DqCj5yx02YmnTy29hihbKsbRfcRiQKRv7YLdlYRh2L03XSxNYdj1Dal5o8+Xm8fN27vRxevt4tvUEKsWvf0Snq4G65/9TJb+M2FS2g3nGdFcYuS5EcP+ulBfeeI8mbXtY5MSodRtemFIb8N3y1gPbpdoI66K8GGDefHPAsvM+VOJ7vpclM9yP2GKt95nO7rmBnn1UrWLhcvy0i70+sGrfobaNFwa2nIwDHhYq+KYTd3NcNEDKtB1OZes+jbloVurzZNWfUq05bd5+Lak7T4ZhL0NUP0JB6CyVZTu2Sx6cqWh/YH2rRl1ZdMY2aWpKq6fZW0/IZw6quG7E16o2GMHqXbW2qvcjqYiTGoHrJ3Haazph3bNLXUPqXFt9rVDNSTDfd2DyGyugcq7hsZOW7MbiY8rimFArTyW8lQVw45I3Jd6ZhOi5xXODOYG81/RXOdJcnXrApyOmmmDQ1juH5kWNGztPhucxBD1w3RCdX7zDYD/Vrt3+h3F5d8uF7LBxs1Q52MllqR9nAnXJVomIVW83ltXTMEON4m2f61gMNXX1Y2u84KizPfvmbA2dENeorR9/coOd6fktRym2ERHcsobD25aas7ifJnVDQWD3AJgxF07b051U0Kf2NH3U/e3E31Mrq/JWd1pbtG5r9QcYetmHhg9Skz5CTC+llRZNuY9GzTQh1zv35Xc4MK8gRo0+boGoD/It39VE1f+yRerUS3KLn/W//jx1NSxsck3mIR/vnzrz8PP5nPaZ3f96czcoew2qsqttGONwdWYyeUAZCclQckYGX7N65J/BmjvI6Ldp6lRZlH2Nz8Nx+n2/gYJUN7DAg13UOlacdyWPIOHVFancLJ9NZpl868xrffNTPoAZU93vxCgUoDa/Ff5KyayLYgoNFi8yhjS9cBMUanReCLO8Ok19zF5hb4VKhuHtZmepkvHAV60hNqmXwsYRBAciYZAZj6J/aC9vWO6GcA1rso36PhIrEHhhAIso5/hSA1BsjUAFXslI4FzixJljE4V5KyICM/LH4IJmosY9TtNlQ3vNCmPRUQJbWMnATtz2HGSN1e9ICWRhGtURCTT4aXwd0jvITBS5ltiZorWLF6psZyYHp0WDQKuuCbZGKxOoogmBuYYAT0adykEzSsc3VuJphUTciAXhf086tAoQkKJoOf+A7mWLgroxJdV69f0y3awHdQZ4UxWl4WW2zJ4jHFqGOApcmg1F7A9Aii//jb337leq7n1F6rpTl1vy0dAOCd4Zl3vQS07t/w7MBg/omOCAlGuMmA0V196p7Vq6bWbQ1wT2mkQWZ4bR8SJbCfaRUeAVXSRwqCJuXX7yZClmKCbOAgVosrkz6eAFbixytjo+ptnFRxb+CHO1aYUgxfRn5vuWjQaos1/vRoIDFY03IDKzHTwasRGhSlK1vN4NVqZDJ4TYar9lrgMg44WmkZGfofF3/Q0ami09b0hx0xeTvFpLcWwochoruPLTAaqBoB2GOM9rcgYBCrGgYQrTY6TQ1zlE+DieaZVAMNUH67Hgx1GsYIzAoyKApzKmbQw65gYvXRabGhnQ2cwGeUXGdCvfha4ARZaD5w6u9ZTzOLbt9ye/RLqsVU+0qfmfx2Py7dp8BBCETdP7E36d4onx2xyau0o434GufsbV2mH/sfR3EuXOQHSJbA2OpUHgFc8kgXgjbl7+Unh5nq6NzEXawYZkZdPgXMJBFRRoIZE75ivAGNCVTCLOSYgqUPbOJ4LIL25je40SosanwTQgwmWM04Z4y5OY51LOoUw52VM3kF6DNGwlQIVISRmgyFzUpzUW4P2r3gylbj7Ex2Kubo5zqEKVzc9HtS0+NrxF0pG3hdy8JyjYqu9h836P9OcY6q2CHiE/85+S5KYFAcpnw1PozWysSPTb2f3l6M/Xz/OY/38TCnSogLsgZucHkXZE2czcD0k0MBO4H4CeUvd1XWFOFnThMx3zdTMHNEiFWdHha0bFNj4u0p3SWoivx1VpZ5/O1Uojpl2qYvUc13KEqgg+nSMc/lhJrJheSIQ86SRDYOilGxrjoy9LXnA90Gq4s5Mrb9YJY3RbfDOdOdM4KZV4ApR8sfKJE3OhN8dKdDgiD98zrwGwgNIWtFx31DjbQm7Q3xbHC1mGFtQlCN76zMbsPMw1UxO/iSLBTzPLQBlICQtsIjG0gznWbpCrPC3mJc2gzANr5rM78PMQ/3dntE2/g+3pKibm27HLDB8kNyiShXAkCBekuAIiz6Z5LOfKOjFx2xQI0HPSCEiooi0VVDypYwTJQEewi5hlCRKavTPsxgps5VX9vQwHmVvtkZbJM7bJkGU2OeSkylDqM8l1lELzP4JIkqXclsgVLJYIYwYZBZAFx6F4sEfQp05muAmG6HT4kyOJ/fuED7GuVxlJadbz3PDt/ilBBOfiNAIhsELSn5mu4RyBTVkWJOVwxk+FvM2nz2QB1/3HXF6NRLdQ14/nGKSD6hL2ksxihDRGOBLVilexQbaNbQo8WeG/6W6xN1ILla77cklzdYZhe3qFuNqHclOUKg18feiRTrJZWOJgsJy1H3HyVKGkB0NvuOwz10XfVComNUFGvIOBJ6DZEUBMdWGO4FnxrNosF+0W53fvOAKRyuy4RgNt52qMTXKDl1IJVrGAYX4yKXqKsjZkMYEsNWeAoD5VpbAzwPGUwN68kX8iO+fJpoSb6cdfe77HuaZNFuumCmrQTsZnr34yrCmXbq6LQ1p3imm9vocEwQLL9tJ87OSxh1z4gegjX+ZFi4i1GO9auSUwkzmbqmpSWYCDK76aRnZKF+XUXO2V4fncZo6WYAq/lv8k4DohE3cs3wM/XW7Sf0vSCPEBcRvb+VlpGh/3Hx0fs7VXTamjx6f5dfhs21vqCcWMoBdsSc6mPgC1TLZJibPn3NAGyqYLg2M6hXBLqxJlXOwKsqTAe+i+cS5WmUnJ3Kh4pjfa34Bm2zfLeMREoyDRi55ISL94BS9UwAORkWL7P8dCAJl3wDT7yR0LXJcKJ+XTwuel2WA4K77Bhvx0YBaZSHQfPzOnBQK7McIGzIf3/Ps9NRiAKKhOu85udRBiLSIC9CIOiIDBMQPFoN9XLNwYUAcpv3WEi8TOB09PtyXI9DyGcw+RCJ7dJ1ISE0+uzFsF9Hnb8QsSYD0ed8FyQpsWzuQtpkmDS/LD0Xca2GTkOswSfu/UUsmscFzZjTXH3UTD7DbSNefymiPXofY2nylw0cqnumkc1pyUF5WILVxDZn1NJpd/Lg5iDWgG/F3Ee8Aojp+5Sp8EUknHbgI2etc0ZUJyUvQcBD1tGw0yuyJMDM/1rHNLAZ8VqHGXCmvtbxe3xfnkf5bnN9yrcPUYF2f8blg0AH+25U3D9spWCY9T+G8yRjhb7vdNHCBNgVk0OEmevACtl2aSBfA0kOyjPCpMcIAZ7QZjzvaSvOC2tf6E9hprOhOUFttDmSNc6YHp122vQpK9H859mVlLwE9a/LxlCvyPzn2TfoO0b7dYYZFK1zWsT+JCA4Iw5Yvvi9S0irRexkQjjzOggqpuPzgctobsgWK0y3THfv8PYhPlapIWc9krVCsiF2ux+XDaBOj/kPY62oZMcIltu21wIjh9twYAvChDg26VhPINLelugqTHtIW4lx9HtGrxiifhzT/zww+oSzlfKUp1V+YhTgrnGg+TAl8mBqw5SsYA5M67OI2W/3gGd8r2KEzeX5FiPgDaw/5dw2I4KcR3lJZVsNmRlYAZOhRIMpybBwPRl8Od102pxBxl4OQosYpeYAszHHKit0TT5ccdia/xn8HIA14om8Fa6mPpg/f0Dbx+w0DIXH/Sz2YBwl48r40nHeNoNqyUULGexOYc8wgBRoqOXuhlUnXPZtTznWeH8dvZCtx6s0rvh62oGU7U6zDQ/Wb8PCZW8rcvpotUn1xGzw0R5kyDXy1c/BNg5ApaSihT0hsQOIX1CaHJgM606GzwoGT7gnPmT7DfXvqiPFOw0DOmbHYVg2CiCpVkXShNq3kNksDO5opXSaG4g4C6gtYuU5HarGXG+awmnypaZ//ISLATmEzjogsxiokGe+t6dvxTaP68QVI4f/oNvmn1OzpYuHBa/TIkCC9XuKSvQRFdUFzs1lnh3GQwnb+GA3jC1aPD4GCum0SHfGXAByl/2Ax0zg0XfFdLPa+/s4wb+gzUihGboGWUb9r0s/n+1V0VrdTHzv42ybDAIbVlooPQMhmjjCZif6YHWTjBK+kDdTIDh1+pjMRqq60x2mlVleBSePD1H+cvG8fYjSPbrBX8T5KcdNbF8k8GoIWGi1P+p7GSICeyJW/xIIE5BeYfBQ66HTkKQD5gEN8scPTEyACcbyk4HhjxM6od3FIYqTs7KMtg/kBOoylow/5vlSggw9oOSDbE8gxeLTsMB6aa2F4gmHJBhqk+Vumhd+xs7qZI+hWaR4osTf1Eps5dGpGCIm+jZTMMqkmRJehLdAKBObKjjKtJqj5ZsDtig/JVTFrWNfxeBoCoPJfBtVczL4XR2OWV5i2e7xYL253T6g3SlBd1HxKH5ZRhMxk2umQH+azsjAcByUhHkoJtQ5DF5YnXQabCSM030l44RJMqaHCiPDIK/F+qDC6rQ4qOCGkqy+ZAjq4N6vwZKpsOIDAtGFYSZPxn3vBWyUXnq7C0S+yYD2Nto+XqV4fbB9DHpqHgRmAuEZkYQ0iz89E2mm0/Tkh2gi3M3/Ecn8QDfikxIXzE39suQ6S5KvWYmH9ub4ruqwnim5BtL5Pfx7WY27d9lmWE/pEpu6cOq6tszgRsCwfQb7XKHGrpqTa2s1GAFtcssbtTkFvqofztLiu2QUpUiGvdr+PIpTc8KYLzcmMNeMsNWLOGk+4vPsQBYFmg6MqjK276KbHqYk7n5fkccSmtqouZFhBCdVts0qHTCztSGSPPklo5zTI+OnlW26eXyS7Q3dEVVlbHdEN83M1+nfV+SOhKY2am5kGFX/5hN1DXqRy1bW/zjOKtAcSZ7cEWyeeeCnlW3Ci9tkjXmDnmL0/T1KjvenJK2i+Oiu9QT1R1/zieQA9j0AohW5ML0eMWp7ShwyBapdroZK2OeTIcrvxhVkjBmCiak8D2RZubVJfZk+mNfjtRbkqhaw/24Jp+XttZtjaNwd9gtcp3ypPipcA+XtyVK2Q5dxXpTvojL6FhX8kXVV6xaV3YMukue4/pnqzOb36jz+EP3z5923DPd19C3pq3AOZ8A4ej6PSrQnIUh49nQp2AhNoGgK/6vaSASa6UqgJrpCBfsP2TZK4r/Qru1xoCGABmoSIFM1HqX7E7muy7fZFYFNdaU66qHbMid7sUV2yrdgayCZUEmOUiHFNcoPcVFgjLeb3JwEPAnUOk+laJl9B8a1yhZDLbIUKj2zJIF0Iz+D+pASDa7tgQXIuy0UtdCWa9qqm4YIzdVRyCzWEWk2K2lP3pCyhe5ZKNdAVwLx7wpVClRXdEFH2JWA4reFKgfYxMz9iMqHDPp0hgSgOxzQqNossXvGbuwJD6fQdzMoB1tkSRQN9jtMXFt9EdRMX6r6itqJFP8JtSXg99MWKtj32Z05/n0R1EBfqoKZeMCVj7baQ+11vC1POdTfXQloorZQwZ59oMK1wRZDDbEUev0t0WlAIOn9TUO0+RiRr0itapSe7iNSB/JrbDGoKkOhib0qInucowPsvEEqGSIZQpUIKImfUP5yFx8gW7PFYKMMhV7f0rG2Rd1L00h6mCYzbbwLm3kZJyU8SCuraInG1dKTVOI4OArZR9BSaX8FTUXFxwBSyeSgKU1luT2ibXwfb8mCi4pTK5JKRC+TD66jLSlc/XNzyY0fiqXk4MgsrWElnbZcJhLp9uldBK0O6UJJb5FyvXa+RnkcpX2U3PPs8C1OI0G/6FSSyCWtp5D3j1NEfvmSxtBAwBZDMrAUdtbRN4li6K3/b/4dDSuKBdKTxBiXg0+rwAQaMtDEOtLQ9FZyactkIo8tapqw3LrQacgNvqOmhmo60z3h56cyXRE4jelKVTtoMcqv8xhcXVFl4O5ZX6xopL9GxLXRF0FNVKVK7hfPeBKSRsnZqXyo9jhr9y3c4ZGTQ1LIayikI+HzBEtKqgxqlxQXG61lJaEV7bPShZKG9PZcCbGoESn/hkKH/+95djqKGmkKJS01FIqWmujsXCPN7xD/pkhzIcTmwxauhFgy2VKIpVRIAWfl5qSAySApYEpNKSQty1vT60aBe6HKhN2ptdqiclLDjdRlwkbqYkUjYO5ZrjmQCmoYJNTY5RMs3Psi0c6e1hKdTWspbEXcaSyF0qRMzjvAmEw5bEaGRKneMMUMoOKQBFZzSKXamOPTnvA7dDwNuFXHk5k2LppBiQi1xNCbNQmTFAC9L6CEcSAgNhRHQw49AdQHDnRIc/7QgS4FDx5oAv2mSMR8aXM1haLJmkh5rghpJtRIRxMghjM8DWFpJLMRllC5IGEDwAJLEJZANnMckKr6sI82yvdeXwb2W1+s8o9MUAPeNzLFoF9kKJSwTIQLAaoMhmKiOcX/kosbocqgRqhi1TwKpQgvtmTOnScB51UclWqJiHkgsn79Bh5eD8rBpSJLojz1y8CTkuZ3+JQv0zj56cMj8sNSVwQf8balOqJ3O0OwBl2xUBHtraV6HBCeyA7Kwe0JlkS5owdGKAK29kA6eI8PJNUXRN68slH19gITjYzfTmCKwe0DhkJ5uno4RvEeGnT6Ivh0tS1VHn+S0eAOHY4J7Os5CvgQdECksQf0AZUlyhVjq4hQtD8E0SpNEBWnHP2J4v0D1KeDclh9hkSvwXcxBncBq82TSJqlqBQtD4JDcc0OyqE2ByQqD/iSbiUOkC4F/R9NoNz4GwaZATb7hiTwBt+QSqtlsVUH5eI2da0qDDbBNS2kBG+tiIgN7hXIHAlIprpnoO1S2oNgiQQ8iexminbL7aGduGGOQnb2p9vsDarIduIrREMCeBXJ0qiMnGdFgZkn4lZ5EtDIHJXhbU3FHUo5uc4tzk1Fqn8+zlxlE1++E9Cpb8fdoAJP0ckteo3Lf4224juGHIXsLmNDhNQNt1vTktsdPIlsj3tzdjwmMdrdZQ19bCCF4o4HTKYnDV1HXyDxV8NR6InRkKslaO+VaVxO0LyjsOnptL+S7hau3gVo/YvQTGIO/cN4LswJbxOORHSfmaXSmP92j4bBSW9XKprpdgQal1jFTTGloqusuk2JHxOKsAaQSiAHUJtIpBJDo22uQeqljPx9w6Z/HUHV6bYoZRWGD3uo1IODpxpYBPEzDLam5AlGzUXnScUvrPq6pqGfo6jtAlMHMwr07Ka3iPQljbk5uEkH7ceKzW1jdt4yehXFqooevxBNVQ9apJwg3w5ylT5i8W7KuyjfV7eRjE3ZVBQbQKiwTMF5mhAPY9LvkSUI8QnSj5VqlaHHSHaq1S+ONjVHWDmaxFFIrgb7WqqrJ3gHZa4iu17YtAsG1KwyYqk/0a8sVhJ+E0QUlb/0kXAZrKQAZqIXS0EMKPYi+pUlngRQVqDeXExGv6fadHwB84CEAUwBvRGrTSB9+mWuerMolik9JPGv7mCBT+qJXgjaqzgwnVhRltB7L42oerew7PY+JM5TTKyeNYAzBqnigs0dhkFQQ4idoJjYSz/OwQxv46TKFdBxlhhhQBrOBFoYclC6jSvTsxZrzdGG/AQGj8IZBqKX3uZmaF/zSqfGPFGI6fHwRTKpKXxtbDFNbp+UMo9agbkyRCcWG3pkS0SXPZ5lJ9Dsi9l69ix44muhNvPqdtMxBhSHKb0IztYDXwrX1QdF3tVv96DV6sOxcXg1IPlno373hFTS7xyN/x4fnpnU3kz0TN5cTdmRimRyo1NNrNQwAgFRShRdAKwJmoUvDGse8ZRHp5qnPp/UPMz7ZsmHAtL5/1igo716RJHFKPCBCubkz+i7AWuG/HSERoIJghvL5CsCa3rGw0yN1h7qGoFrWCkkrqBBnSsLaRgTIA0r+Z+rTGQWICIKuB6VVwiJEyD0C8NEFsvFehbb7rZ9vv+cx/s4lUxjOVL/G3QGmHJQmY1TI9aXoZP0HxA3p+44STyc8dUWxqDZ0IFzhNbQqq5UkQ/3QysrjuAj5yWI3gOzVkXg8WDgxiTqlaGwSrAlom0HeDGLkUFWaIpuWdyvNISW4GlDrIsE4aVoQ3hcFQ1VU+OBIw2GhnFNMIh/RU+RhbYQ1wk50Qcah0zkeZoPqaxGC0geDDHTmEQQmUxtHEXFYGaSRxujuWmGELM4d5dESxOEegNP5S3YSE511XbRM4gGVzZKlIS3IPKTb7TKGzeFsJTbyKaa6RfA30KXXpcUU4c7u+Du2jPHF1RpEGNILjyKqZVKCbQB1JiHSSQRC3VXzdosRlhQa0R7pC1qEq8xiInVg7hO7WCf6NLMyYbAtLIpw0JsIChaJzGFLArnUk1sAdPxsaljLX8mAiJ5ckFPJebSqq40gVR3vdmjWB4pT0n8Ul8m5exBt6lrWBmTEY2iwV2Dc/gPXAe66roj4nZaJwCGqd0Im9U2qZzPKEbR4csErZMxhwPR2U/jNYf50cfyQMc5bXhi5a1DmFCyV+h87XAYcrmetIvCKdv3+G10OCaoZyzu8wGlJ9FH7O0uUPSGfb3GqyyglJwaOL/E4wJgk+ri2NYu6ksmnTyR/2lmeFXbaN7Se9M8kVhk+3vTw5jjpGb/o7/nAbrvd+UV1BB3uDOv+kg8PyoVaKrxiGT0d7hTmUgWUl766EKvYgg06YTZJ9yMwuWbm64Lmi+1E0AVwihcdH9SlfrVj7okMr9a3wFZMIWZPAO9xnD+AEuVN3Sof4G+NI1CZCahQC8ymCiAr81X9NuzDUtpt9Y0zoJO05vkS6TZyr5Yis6j6CN/tyTqv8bbQZBOLLjtw0Em1wWpBaeysFVU5psGFCHcUjj1oNQbm/5OrPjyLVxBsnpzvnwLJfpguEgTd3gyTWN3TbvU1M79OgtzdMlExDYYkvhVnEuE0tfztOjp5ZcsZXki/0vZ8Kq26Wc213h99hAVaPdnXD5QLfCKq6p4U4epO0ywQ6oKk+fYG4L5bnv+YjPAFTwpAtYUfvFamYs8meYL3e/a9mFr+fULU5mnS3ykcIgUSQCHSCdt6uuB+ZjMVQRSLEmnQlL6EBMjSUopwkQnR5Qfsyi/C3Ulv+iYxjRt9irxNzGg8Kv0ML8XqSbM3WWvHhn/e75iLVlCT4KDNbnxVZpIzHYh1MbdUi75hoRLWvMxKdEUDg+kDOPqgFRvzZcsy+DmsMGt0dlCWv/9HTo40DAjneLRnYxcuSLw8shOlIuv+foV+fU8GEj2ZYiJQ3wck5tCsmAU0vpfN45tBjiD4QbIrwh4Dt26EryIUkLWwFFleZTz4i/LaOZrtBlv2MyHm6s0LuMokcwlZRV8zyPh/I7N0KPI2ehujHauzDeltouwbiB1pZzgSbhe0k2LUJd9qskNl3aSt5yMXDKUw2kx6yFdnutSxEfEw7NJZEMXSBdi1BpHbZW6wdUcqudHLS5JqfoAGaYOdo4MpbLqD+KkGalsLt7RyVA3l3l2kNlDRh7CIHDe12ZiI03j6myKu8zAEBTx0s3QJa3dSNZvPJH/hRuXeLeuKcypa+PZEyDVAezbQUp1X9tfwuKS9DY+XpQa1yZoeUZiV8aHKH+5eN4+ROke3WDT9oldgWWJspLMKGym2cYgcBZZdoVCJ7+tlyVgbltHI5A/tLVnqZelNphgdnMZw/CXUEtOnK0vXksT6t7WN/Z18uP6MovqPr5GLbGy7pfzZ2GuDZvQV2okllasGJRkmOgjSx4sMo3IIMFQw7ShCxq6kle1ZoIYJm3uhs1az9tIQi1xmgxT2ncyBRLzgOmBCQt51l+bG88m5pBQhzQHmNe3/g6l6XptzdGnHN50rEXGAGi9KwLw4NMrU2wkKZMtgu7DyYilq1ZlnRBrFEUyZsJIN7OyPzNJNthVVfzvs09romFyzk21v3aepUWZR3E1ncOL+Q4ibfqKu2zDJ/UElsO+eKuRaZHrgu08QbLTuhdVCUw9mJ1OdqZhSYpcrpRRXrVJTUKlbdUFCpPplbeaI8fwqAMS3d62b+9EaWXdDNu/6pObq6OTC6/9anACtancvLq9z6TzBcYGN47h8QRkM66HD0maYjfD9tl15Obq6OTCa2fvmUDtZizn0ixrj2qC+pL37P7bGmEkFarJz4vUKas9dhNTYGR0tqam6mJ9l2M+K7gpTWvHdWTk6nefs5HV8bUGhP7XIP5Vf/NLzaQyPO5llHdlb36pE8k3P+A/693Mj9kOJQX59c0vNydc+4Dqv96hIt73LN5gninaVm32TFuaq/S+SmpBUpAPJGpJ2uI2dw8qo11URmd5GVfxe3HxFn9LeGr780/kUk61s/gN7a7Sz6fyeCqxyujwLWH26N/8Im//zS+czG+agFE+VMBixlgF9Dl9e4qTXSf3ZZQUg40LEYtzbP3fEf697kv8aZZo/9Jx+pSlmowa871DR5Tu8Cd3hw7HBDMrPqe30ROyke1LgT6gfbR9ua5Sn5I7RiIm6o5gzf7mXRzt8+hQNDz6+vhPjOHd4fl//h9Dvskc/DcJAA== + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.Designer.cs new file mode 100644 index 0000000000..596e5ef91b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.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.1.3-40302")] + public sealed partial class MoreIndexes : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(MoreIndexes)); + + string IMigrationMetadata.Id + { + get { return "201705281903241_MoreIndexes"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.cs b/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.cs new file mode 100644 index 0000000000..1cd8f0c3dd --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.cs @@ -0,0 +1,37 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + + public partial class MoreIndexes : DbMigration + { + public override void Up() + { + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + // Avoid "Column 'Name' in table 'dbo.ProductVariantAttributeValue' is of a type that is invalid for use as a key column in an index". + Sql("If -1 = (SELECT CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'ProductVariantAttributeValue' AND COLUMN_NAME = 'Name') ALTER TABLE [dbo].[ProductVariantAttributeValue] ALTER COLUMN [Name] nvarchar(4000) NULL;"); + } + + CreateIndex("dbo.Product_Category_Mapping", "IsFeaturedProduct"); + CreateIndex("dbo.Product_Manufacturer_Mapping", "IsFeaturedProduct"); + CreateIndex("dbo.SpecificationAttribute", "AllowFiltering"); + CreateIndex("dbo.Product_ProductAttribute_Mapping", "AttributeControlTypeId"); + CreateIndex("dbo.ProductAttribute", "AllowFiltering"); + CreateIndex("dbo.ProductVariantAttributeValue", "Name"); + CreateIndex("dbo.ProductVariantAttributeValue", "ValueTypeId"); + } + + public override void Down() + { + DropIndex("dbo.ProductVariantAttributeValue", new[] { "ValueTypeId" }); + DropIndex("dbo.ProductVariantAttributeValue", new[] { "Name" }); + DropIndex("dbo.ProductAttribute", new[] { "AllowFiltering" }); + DropIndex("dbo.Product_ProductAttribute_Mapping", new[] { "AttributeControlTypeId" }); + DropIndex("dbo.SpecificationAttribute", new[] { "AllowFiltering" }); + DropIndex("dbo.Product_Manufacturer_Mapping", new[] { "IsFeaturedProduct" }); + DropIndex("dbo.Product_Category_Mapping", new[] { "IsFeaturedProduct" }); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.resx b/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.resx new file mode 100644 index 0000000000..70fbc6ff9e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201705281903241_MoreIndexes.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/201706020759565_UpdateMediaPath.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.Designer.cs new file mode 100644 index 0000000000..2071542cd7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.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.1.3-40302")] + public sealed partial class UpdateMediaPath : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(UpdateMediaPath)); + + string IMigrationMetadata.Id + { + get { return "201706020759565_UpdateMediaPath"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs new file mode 100644 index 0000000000..b4f74fd90f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs @@ -0,0 +1,114 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + using Setup; + + public partial class UpdateMediaPath : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + var tenantName = DataSettings.Current.TenantName.NullEmpty() ?? "Default"; + var uploadedPath = $"/Media/{tenantName}/Uploaded/"; + var thumbsPath = $"/Media/{tenantName}/Thumbs/"; + + Sql($"UPDATE [dbo].[Category] SET [Description] = REPLACE([Description],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[Category] SET [BottomDescription] = REPLACE([BottomDescription],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[Manufacturer] SET [Description] = REPLACE([Description],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[ShippingMethod] SET [Description] = REPLACE([Description],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[PaymentMethod] SET [FullDescription] = REPLACE([FullDescription],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[BlogPost] SET [Body] = REPLACE([Body],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[News] SET [Full] = REPLACE([Full],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[Campaign] SET [Body] = REPLACE([Body],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[QueuedEmail] SET [Body] = REPLACE([Body],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[QueuedEmail] SET [Body] = REPLACE([Body],'/Media/Thumbs/','{thumbsPath}')"); + Sql($"UPDATE [dbo].[Topic] SET [Body] = REPLACE([Body],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[MessageTemplate] SET [Body] = REPLACE([Body],'/Media/Uploaded/','{uploadedPath}')"); + Sql($"UPDATE [dbo].[Product] SET [FullDescription] = REPLACE([FullDescription],'/Media/Uploaded/','{uploadedPath}')"); + + // LocalizedProperty + Sql($"UPDATE [dbo].[LocalizedProperty] SET [LocaleValue] = REPLACE([LocaleValue],'/Media/Thumbs/','{thumbsPath}') WHERE [LocaleKey] = 'Body' AND [LocaleKeyGroup] = 'QueuedEmail'"); + Sql($@" +UPDATE [dbo].[LocalizedProperty] SET [LocaleValue] = REPLACE([LocaleValue],'/Media/Uploaded/','{uploadedPath}') +WHERE + ([LocaleKey] = 'BottomDescription' AND [LocaleKeyGroup] = 'Category') OR + ([LocaleKey] = 'Full' AND [LocaleKeyGroup] = 'News') OR + ([LocaleKey] = 'Description' AND [LocaleKeyGroup] IN ('Category', 'Manufacturer', 'ShippingMethod')) OR + ([LocaleKey] = 'FullDescription' AND [LocaleKeyGroup] IN ('PaymentMethod', 'Product')) OR + ([LocaleKey] = 'Body' AND [LocaleKeyGroup] IN ('BlogPost', 'Campaign', 'QueuedEmail', 'MessageTemplate', 'Topic'))"); + } + } + + 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("Common.For", "For: {0}", "Für: {0}"); + builder.AddOrUpdate("Products.Sorting.Featured", "Featured", "Empfehlung"); + + builder.AddOrUpdate("Common.AdditionalShippingSurcharge", + "Plus {0} shipping surcharge", + "zzgl. {0} zusätzlicher Versandgebühr"); + + builder.AddOrUpdate("Address.Fields.Salutation", "Salutation", "Anrede"); + builder.AddOrUpdate("Address.Fields.Title", "Title", "Titel"); + builder.AddOrUpdate("Account.Fields.Title", "Title", "Titel"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.Salutations", + "Salutations", + "Anreden", + "Comma separated list of salutations (e.g. Mr., Mrs). Define the entries which will populate the dropdown list for salutation when entering addresses.", + "Komma getrennte Liste (z.B. Herr, Frau). Bestimmen Sie die Einträge für die Auswahl der Anrede, bei der Adresserfassung."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.SalutationEnabled", + "'Salutation' enabled", + "'Anrede' aktiv", + "Set if 'Salutation' is enabled.", + "Legt fest, ob das Feld 'Anrede' aktiv ist."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.TitleEnabled", + "'Title' enabled", + "'Titel' aktiv", + "Set if 'Title' is enabled.", + "Legt fest, ob das Feld 'Titel' aktiv ist."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.TitleEnabled", + "'Title' enabled", + "'Titel' aktiv", + "Set if 'Title' is enabled.", + "Legt fest, ob das Feld 'Titel' aktiv ist."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Shipping.SkipShippingIfSingleOption", + "Display shipping options during checkout process only if more then one option is available", + "Versandartauswahl nur anzeigen, wenn mehr als eine Versandart zur Verfügung steht", + "Display shipping options during the checkout process only if more then one shipping option is available.", + "Legt fest, ob die Versandartauswahl nur im Checkout-Prozess angezeigt wird, wenn mehr als eine Versandart zur Verfügung steht"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.OnlyIndividuallyVisibleAssociated", + "Only individually visible products", + "Nur individuell sichtbare Produkte", + "Specifies whether to only export individually visible associated products.", + "Legt fest, ob nur individuell sichtbare, verknüpfte Produkte exportiert werden sollen."); + + 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/201706020759565_UpdateMediaPath.resx b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.resx new file mode 100644 index 0000000000..70fbc6ff9e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.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/201707190940318_V302Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.Designer.cs new file mode 100644 index 0000000000..37942deb8d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.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.1.3-40302")] + public sealed partial class V302Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V302Resources)); + + string IMigrationMetadata.Id + { + get { return "201707190940318_V302Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.cs new file mode 100644 index 0000000000..d2f4b57b0b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.cs @@ -0,0 +1,58 @@ +namespace SmartStore.Data.Migrations +{ + using Setup; + using System; + using System.Data.Entity.Migrations; + + public partial class V302Resources : 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.Settings.Search.DefaultSortOrderMode", + "Default product sort order", + "Standardsortierreihenfolge fr Produkte", + "Specifies the default product sort order in search results.", + "Legt die Standardsortierreihenfolge fr Produkte in den Suchergebnissen fest."); + + builder.AddOrUpdate("Common.Recommended", "Recommended", "Empfohlen"); + + builder.AddOrUpdate("Admin.Configuration.Themes.Option.AssetCachingEnabled", + "Enable asset caching", + "Asset Caching aktivieren", + "Determines whether compiled asset files should be cached in file system in order to speed up application restarts. Select 'Auto', if caching should depend on the debug setting in web.config.", + "Legt fest, ob kompilierte JS- und CSS-Dateien wie bspw. 'Sass' im Dateisystem zwischengespeichert werden sollen, um den Programmstart zu beschleunigen. Whlen Sie 'Automatisch', wenn das Caching von der Debug-Einstellung in der web.config abhngig sein soll."); + + builder.AddOrUpdate("Admin.Configuration.Themes.ClearAssetCache", + "Clear asset cache", + "Asset Cache leeren"); + + builder.AddOrUpdate("Search.Facet.ExcludeOutOfStock", "Exclude Out of Stock", "Nicht verfgbare Artikel ausschlieen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.IncludeNotAvailable", + "Include out of stock products", + "Nicht verfgbare Produkte einschlieen", + "Specifies whether to include or exclude products that are out of stock by default.", + "Legt fest, ob nicht verfgbare Produkte in Suchergebnissen standardmig angezeigt werden sollen oder nicht."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.resx new file mode 100644 index 0000000000..f001c68117 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201707190940318_V302Resources.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/201707281452589_TierPriceCalcMethod.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.Designer.cs new file mode 100644 index 0000000000..8fb78dc364 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.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.1.3-40302")] + public sealed partial class TierPriceCalcMethod : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TierPriceCalcMethod)); + + string IMigrationMetadata.Id + { + get { return "201707281452589_TierPriceCalcMethod"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.cs b/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.cs new file mode 100644 index 0000000000..3662816352 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.cs @@ -0,0 +1,47 @@ +namespace SmartStore.Data.Migrations +{ + using Setup; + using System; + using System.Data.Entity.Migrations; + + public partial class TierPriceCalcMethod : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.TierPrice", "CalculationMethod", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.TierPrice", "CalculationMethod"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Product.Price.Tierprices.Fixed", "Fixed Value", "Fester Wert"); + builder.AddOrUpdate("Admin.Product.Price.Tierprices.Percental", "Percental", "Prozentual"); + builder.AddOrUpdate("Admin.Product.Price.Tierprices.Adjustment", "Adjustment", "Auf-/Abpreis"); + builder.AddOrUpdate("Admin.Catalog.Products.TierPrices.Fields.CalculationMethod", "Calculation Method", "Berechnungsmethode"); + builder.AddOrUpdate("Admin.Catalog.Products.TierPrices.Fields.Price", "Value", "Wert"); + + // settings + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ApplyTierPricePercentageToAttributePriceAdjustments", + "Apply tierprice percentage to attribute price adjustments", + "Prozentuale Ermigungen von Staffelpreisen auf Auf- & Abpreise von Attributen anwenden", + "Specifies whether to apply tierprice percentage to attribute price adjustments", + "Bestimmt ob prozentuale Ermigungen von Staffelpreisen auf Auf- & Abpreise von Attributen angewendet werden sollen"); + + builder.AddOrUpdate("Admin.Header.Account", "Account", "Account"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.resx b/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.resx new file mode 100644 index 0000000000..93f0affd46 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201707281452589_TierPriceCalcMethod.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/201708251628482_SystemTopics.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.Designer.cs new file mode 100644 index 0000000000..f2bedc3ccf --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.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.1.3-40302")] + public sealed partial class SystemTopics : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SystemTopics)); + + string IMigrationMetadata.Id + { + get { return "201708251628482_SystemTopics"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs new file mode 100644 index 0000000000..70488ff6a7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs @@ -0,0 +1,58 @@ +namespace SmartStore.Data.Migrations +{ + using Core.Domain.Topics; + using Setup; + using System; + using System.Linq; + using System.Data.Entity.Migrations; + + public partial class SystemTopics : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Topic", "IsSystemTopic", c => c.Boolean(nullable: false, defaultValue: false)); + } + + public override void Down() + { + DropColumn("dbo.Topic", "IsSystemTopic"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + var systemTopics = new[] { + "AboutUs", + "CheckoutAsGuestOrRegister", + "ConditionsOfUse", + "ContactUs", + "ForumWelcomeMessage", + "HomePageText", + "LoginRegistrationInfo", + "PrivacyInfo", + "ShippingInfo", + "Imprint", + "Disclaimer", + "PaymentInfo" + }; + + var topics = context.Set().Where(x => systemTopics.Contains(x.SystemName)).ToList(); + topics.Each(x => x.IsSystemTopic = true); + + context.MigrateLocaleResources(MigrateLocaleResources); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.ContentManagement.Topics.CannotBeDeleted", + "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/201708251628482_SystemTopics.resx b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.resx new file mode 100644 index 0000000000..4c7f4e14fe --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.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/201709141000226_V303Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.Designer.cs new file mode 100644 index 0000000000..17d5c9a425 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.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.1.3-40302")] + public sealed partial class V303Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V303Resources)); + + string IMigrationMetadata.Id + { + get { return "201709141000226_V303Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.cs new file mode 100644 index 0000000000..12b8d62ab1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.cs @@ -0,0 +1,65 @@ +namespace SmartStore.Data.Migrations +{ + using Setup; + using System; + using System.Data.Entity.Migrations; + + public partial class V303Resources : 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("Account.PasswordRecoveryConfirm.InvalidEmail", + "No customer account matches this email address. Please click the link in your email anew.", + "Diese Emailadresse konnte keinen Kundenkonto zugeordnet werden. Bitte rufen Sie den an Sie versendeten Link erneut auf."); + + builder.AddOrUpdate("Account.PasswordRecoveryConfirm.InvalidToken", + "The used token is incorrect. Please click the link in your email anew.", + "Das verwendete Token scheint nicht korrekt zu sein. Bitte rufen Sie den Link erneut auf."); + + builder.AddOrUpdate("Customer.UserAgreement.OrderItemNotFound", + "The corresponding order item could not be found.", + "Die entsprechende Auftragsposition konnte nicht gefunden werden."); + + builder.AddOrUpdate("Customer.UserAgreement.ProductNotFound", + "The corresponding product could not be found.", + "Das entsprechende Produkt konnte nicht gefunden werden."); + + builder.AddOrUpdate("ShoppingCart.RewardPoints", "Reward points", "Bonuspunkte"); + + builder.AddOrUpdate("ShoppingCart.RewardPoints.Button", "Apply", "Anwenden"); + + builder.AddOrUpdate("ShoppingCart.IsDisabled", + "The shopping cart is disabled.", + "Der Warenkorb ist deaktiviert."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.AllowDifferingEmailAddressForEmailAFriend", + "Allow differing email address for email e friend", + "Abweichende Emailadresse fr Tell A Friend zulassen", + "Specifies whether customers are allowed to enter a email address different from the one they've registered their account with.", + "Bestimmt ob Kunden gestattet ist eine Emailadresse anzugeben, welche von der abweicht mit der sie sich im Shop registriert haben."); + + builder.AddOrUpdate("ShoppingCart.AddToWishlist.Short", "Add to List", "Merken"); + builder.AddOrUpdate("Products.AskQuestion.Short", "Questions?", "Fragen?"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.resx new file mode 100644 index 0000000000..cd224dc4f0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201709141000226_V303Resources.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/201709251538312_UpdateTrustedShopsTask.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.Designer.cs new file mode 100644 index 0000000000..d955362e1c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.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.1.3-40302")] + public sealed partial class UpdateTrustedShopsTask : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(UpdateTrustedShopsTask)); + + string IMigrationMetadata.Id + { + get { return "201709251538312_UpdateTrustedShopsTask"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.cs b/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.cs new file mode 100644 index 0000000000..ac54451906 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.cs @@ -0,0 +1,50 @@ +namespace SmartStore.Data.Migrations +{ + using Core.Domain.Tasks; + using Setup; + using System; + using System.Data.Entity.Migrations; + using System.Linq; + + public partial class UpdateTrustedShopsTask : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + var table = context.Set(); + var taskToDelete = table.Where(x => x.Type.Contains("SmartStore.TrustedShops.UpdateRatingWidgetStateTask")).FirstOrDefault(); + + if (taskToDelete != null) + { + // remove old task + table.Remove(taskToDelete); + + // if task to remnove is not null we assume the plugin is currently installed and so we add the new task without any further request + var taskToAdd = new ScheduleTask + { + Name = "Trusted Shops Review Import", + CronExpression = "0 4 * * *", // At 04:00 am + Type = "SmartStore.TrustedShops.ImportProductReviewsTask, SmartStore.TrustedShops", + Enabled = false, + StopOnError = true + }; + + table.Add(taskToAdd); + } + + context.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.resx b/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.resx new file mode 100644 index 0000000000..cd224dc4f0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201709251538312_UpdateTrustedShopsTask.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/201710102038287_CurrencyRounding.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.Designer.cs new file mode 100644 index 0000000000..a07392cfe4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.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.1.3-40302")] + public sealed partial class CurrencyRounding : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CurrencyRounding)); + + string IMigrationMetadata.Id + { + get { return "201710102038287_CurrencyRounding"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.cs b/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.cs new file mode 100644 index 0000000000..7cfdcbb63b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.cs @@ -0,0 +1,119 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Setup; + + public partial class CurrencyRounding : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Order", "OrderTotalRounding", c => c.Decimal(nullable: false, precision: 18, scale: 4)); + AddColumn("dbo.Currency", "RoundOrderItemsEnabled", c => c.Boolean(nullable: false)); + AddColumn("dbo.Currency", "RoundNumDecimals", c => c.Int(nullable: false, defaultValue: 2)); + AddColumn("dbo.Currency", "RoundOrderTotalEnabled", c => c.Boolean(nullable: false)); + AddColumn("dbo.Currency", "RoundOrderTotalDenominator", c => c.Decimal(nullable: false, precision: 18, scale: 4)); + AddColumn("dbo.Currency", "RoundOrderTotalRule", c => c.Int(nullable: false)); + AddColumn("dbo.PaymentMethod", "RoundOrderTotalEnabled", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.PaymentMethod", "RoundOrderTotalEnabled"); + DropColumn("dbo.Currency", "RoundOrderTotalRule"); + DropColumn("dbo.Currency", "RoundOrderTotalDenominator"); + DropColumn("dbo.Currency", "RoundOrderTotalEnabled"); + DropColumn("dbo.Currency", "RoundNumDecimals"); + DropColumn("dbo.Currency", "RoundOrderItemsEnabled"); + DropColumn("dbo.Order", "OrderTotalRounding"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Round", "Round", "Runden"); + builder.AddOrUpdate("ShoppingCart.Totals.Rounding", "Rounding", "Rundung"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingRule.RoundMidpointDown", + "Round midpoint down (e.g. 0.05 rounding: 9.225 will round to 9.20)", + "Mittelwert abrunden (z.B. 0,05 Rundung: 9,225 wird auf 9,20 gerundet)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingRule.RoundMidpointUp", + "Round midpoint up (e.g. 0.05 rounding: 9.225 will round to 9.25)", + "Mittelwert aufrunden (z.B. 0,05 Rundung: 9,225 wird auf 9,25 gerundet)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingRule.AlwaysRoundDown", + "Always round down (e.g. 0.05 rounding: 9.24 will round to 9.20)", + "Immer abrunden (z.B. 0,05 Rundung: 9,24 wird auf 9,20 gerundet)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Directory.CurrencyRoundingRule.AlwaysRoundUp", + "Always round up (e.g. 0.05 rounding, 9.26 will round to 9.30)", + "Immer aufrunden (z.B. 0,05 Rundung: 9,26 wird auf 9,30 gerundet)"); + + builder.AddOrUpdate("Admin.Configuration.Currencies.NoPaymentMethodsEnabledRounding", + "Regardless of the currency configuration, the order totals are only rounded if it has been enabled for the selected payment method. Click Edit for the desired payment methods and activate the rounding option.", + "Unabhngig von der Whrungs-Konfiguration werden Bestellsummen erst gerundet, wenn das fr die gewhlte Zahlart auch vorgesehen ist. Klicken Sie bei den gewnschten Zahlarten auf Bearbeiten und aktivieren Sie die Rundungs-Option."); + + builder.AddOrUpdate("Admin.Configuration.Currencies.PaymentMethodsEnabledRoundingList", + "Payment methods for which order total rounding is enabled", + "Zahlarten, bei denen Bestellsummen-Rundung aktiviert ist"); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundOrderItemsEnabled", + "Round all order item amounts", + "Betrge aller Bestellpositionen runden", + "Specifies whether to round all order item amounts (products, fees, tax etc.)", + "Legt fest, ob die Betrge aller Bestellpositionen gerundet werden sollen (Produkte, Gebhren, Steuern etc.)"); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundNumDecimals", + "Number of decimal digits", + "Anzahl Dezimalstellen", + "Specifies the number of decimal digits to round to (Default: 2)", + "Legt fest, auf wieviele Dezimalstellen gerundet werden soll (Standard: 2)"); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundOrderTotalEnabled", + "Round order total amount", + "Bestellsumme runden", + "Specifies whether to round the order total amount.", + "Legt fest, ob die Bestellsumme gerundet werden soll."); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundOrderTotalDenominator", + "Round to", + "Runden nach", + "Specifies the nearest multiple of the smallest chosen amount to round the order total to. 0.05 for example will round 9.43 up to 9.45.", + "Legt das nchste Vielfache des kleinsten, gewhlten Betrages fest, auf den die Bestellsumme gerundet werden soll. Bei 0,05 wird z.B. 9,43 auf 9,45 gerundet."); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundOrderTotalRule", + "Rounding rule", + "Rundungsregel", + "Specifies the rule for rounding the order total amount.", + "Legt die Regel fr das Runden der Bestellsumme fest."); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.RoundOrderTotalEnabled", + "Round order total amount (if enabled)", + "Bestellsumme runden, sofern aktiviert", + "Specifies whether to round the order total in accordance with currency configuration if this payment method was selected in checkout.", + "Legt fest, ob die Bestellsumme gem Whrungs-Konfiguration gerundet werden soll, wenn diese Zahlart im Checkout gewhlt wurde."); + + builder.AddOrUpdate("Admin.Configuration.Currencies.Fields.RoundOrderItemsEnabled.Validation", + "The number of decimal digits must be between 0 and 8.", + "Die Anzahl der Dezimalstellen muss zwischen 0 und 8 liegen."); + + builder.Delete( + "Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation", + "Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation.Hint"); + + builder.AddOrUpdate("Admin.Orders.Fields.OrderTotalRounding", + "Rounding", + "Rundung", + "The amount by which the order total was rounded up or down.", + "Der Betrag, um den der Auftragswert auf- bzw. abgerundet wurde."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.resx b/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.resx new file mode 100644 index 0000000000..b1a1ddce2b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201710102038287_CurrencyRounding.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/201710252016556_IndexOptionNames.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.Designer.cs new file mode 100644 index 0000000000..2024642a96 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.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.1.3-40302")] + public sealed partial class IndexOptionNames : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(IndexOptionNames)); + + string IMigrationMetadata.Id + { + get { return "201710252016556_IndexOptionNames"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.cs b/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.cs new file mode 100644 index 0000000000..d7f9caa6a4 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.cs @@ -0,0 +1,55 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Setup; + + public partial class IndexOptionNames : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.SpecificationAttribute", "IndexOptionNames", c => c.Boolean(nullable: false)); + AddColumn("dbo.ProductAttribute", "IndexOptionNames", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ProductAttribute", "IndexOptionNames"); + DropColumn("dbo.SpecificationAttribute", "IndexOptionNames"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Search.Fields.SpecificationAttributeOptionName", + "Option name of specification attributes", + "Optionsname von Spezifikationsattributen"); + + builder.AddOrUpdate("Search.Fields.ProductAttributeOptionName", + "Option name of product attributes", + "Optionsname von Produktattributen"); + + builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.IndexOptionNames", + "Index option names", + "Optionsnamen indexieren", + "Specifies whether option names should be included in the search index so that products can be found by them. This setting is only effective by using the 'MegaSearchPlus' plugin. Changes will take effect after next update of the search index.", + "Legt fest, ob Optionsnamen mit in den Suchindex aufgenommen werden sollen, damit Produkte ber sie gefunden werden knnen. Diese Einstellung ist nur unter Verwendung des 'MegaSearchPlus' Plugins wirksam. nderungen werden nach der nchsten Aktualisierung des Suchindex wirksam."); + + builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.IndexOptionNames", + "Index option names", + "Optionsnamen indexieren", + "Specifies whether option names should be included in the search index so that products can be found by them. This setting is only effective by using the 'MegaSearchPlus' plugin. Changes will take effect after next update of the search index.", + "Legt fest, ob Optionsnamen mit in den Suchindex aufgenommen werden sollen, damit Produkte ber sie gefunden werden knnen. Diese Einstellung ist nur unter Verwendung des 'MegaSearchPlus' Plugins wirksam. nderungen werden nach der nchsten Aktualisierung des Suchindex wirksam."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.resx b/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.resx new file mode 100644 index 0000000000..c9afccbfca --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201710252016556_IndexOptionNames.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/201711112331162_ProductMainPictureId.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.Designer.cs new file mode 100644 index 0000000000..794345d94f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.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.1.3-40302")] + public sealed partial class ProductMainPictureId : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ProductMainPictureId)); + + string IMigrationMetadata.Id + { + get { return "201711112331162_ProductMainPictureId"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.cs b/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.cs new file mode 100644 index 0000000000..a3073c4e43 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.cs @@ -0,0 +1,30 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using Setup; + using Utilities; + + public partial class ProductMainPictureId : DbMigration, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Product", "MainPictureId", c => c.Int()); + } + + public override void Down() + { + DropColumn("dbo.Product", "MainPictureId"); + } + + public bool RollbackOnFailure + { + get { return true; } + } + + public void Seed(SmartObjectContext context) + { + DataMigrator.FixProductMainPictureIds(context, true); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.resx b/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.resx new file mode 100644 index 0000000000..b02bf9dbde --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711112331162_ProductMainPictureId.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/201711222311112_MoveFsMedia.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.Designer.cs new file mode 100644 index 0000000000..1a420850cf --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.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.1.3-40302")] + public sealed partial class MoveFsMedia : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(MoveFsMedia)); + + string IMigrationMetadata.Id + { + get { return "201711222311112_MoveFsMedia"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs new file mode 100644 index 0000000000..547f68afe8 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs @@ -0,0 +1,64 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using SmartStore.Data.Setup; + using SmartStore.Utilities; + using System.IO; + using Core.Data; + using SmartStore.Data.Utilities; + + public partial class MoveFsMedia : DbMigration, IDataSeeder + { + const int DirMaxLength = 4; + + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return true; } + } + + public void Seed(SmartObjectContext context) + { + if (!HostingEnvironment.IsHosted) + return; + + // Move the whole media folder to new location at first + MoveMediaFolder(); + + // Reorganize files (root > Storage/{subfolder}) + DataMigrator.MoveFsMedia(context); + } + + private void MoveMediaFolder() + { + // Moves "~/Media/{Tenant}" to "~/App_Data/Tenants/{Tenant}/Media" + // This is relevant for local file system only. For cloud storages (like Azure) + // we don't need to move the main folder. + + var sourceDir = new DirectoryInfo(CommonHelper.MapPath("~/Media/" + DataSettings.Current.TenantName, false)); + var destinationDir = new DirectoryInfo(CommonHelper.MapPath("~/App_Data/Tenants/" + DataSettings.Current.TenantName + "/Media", false)); + + if (!sourceDir.Exists) + { + // Source (legacy media folder) does not exist, for whatever reasons. Nothing to move here. + return; + } + + //if (!destinationDir.Exists) + //{ + // destinationDir.Create(); + //} + + sourceDir.MoveTo(destinationDir.FullName); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.resx b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.resx new file mode 100644 index 0000000000..b02bf9dbde --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.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/201711291017168_SyncStringResources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.Designer.cs new file mode 100644 index 0000000000..4f5a89531e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.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 SyncStringResources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(SyncStringResources)); + + string IMigrationMetadata.Id + { + get { return "201711291017168_SyncStringResources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs new file mode 100644 index 0000000000..a2184757a0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs @@ -0,0 +1,166 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Collections.Generic; + using System.Data.Entity.Migrations; + using System.Linq; + using SmartStore.Collections; + using SmartStore.Core.Domain.Localization; + using SmartStore.Data.Setup; + + public partial class SyncStringResources : DbMigration, IDataSeeder + { + private static HashSet _unusedNames = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Enums.SmartStore.Core.Domain.Catalog.AttributeControlType.ColorSquares", + "Plugins.Payments.PayPal.IpnLogInfo", + "Plugins.Widgets.TrustedShopsCustomerProtection.Prepared", + "Plugins.Shipping.ByWeight.SmallQuantityThresholdNotReached", + "Products.ProductNotAddedToTheCart.Link" + }; + + private static Dictionary _missingEnglishResources = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Enums.SmartStore.Plugin.Shipping.Fedex.DropoffType.BusinessServiceCenter", "Business service center" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.DropoffType.DropBox", "Drop box" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.DropoffType.RegularPickup", "Regular pickup" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.DropoffType.RequestCourier", "Request courier" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.DropoffType.Station", "Station" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.PackingType.PackByDimensions", "Pack by dimensions" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.PackingType.PackByOneItemPerPackage", "Pack by one item per package" }, + { "Enums.SmartStore.Plugin.Shipping.Fedex.PackingType.PackByVolume", "Pack by volume" }, + { "Order.OrderDetails", "Order Details" }, + { "Plugins.SmartStore.AccardaKar.Amount", "Amount" }, + { "Plugins.SmartStore.Exports.BMEcatProductXml.ExportProfileInfo", "Please click here to view existing BMEcat export profiles or to create a new one." }, + { "Plugins.Description.SmartStore.DiscountRules", "Provides standard discount rules, e.g. \"Country is\", \"Customer group is\", \"Has amount x spent\" etc." }, + { "Plugins.FriendlyName.DiscountRequirement.BillingCountryIs", "Country of invoice address is" }, + { "Plugins.FriendlyName.DiscountRequirement.HadSpentAmount", "Customer has spent amount x" }, + { "Plugins.FriendlyName.DiscountRequirement.HasAllProducts", "Customer has the following products in his shopping cart" }, + { "Plugins.FriendlyName.DiscountRequirement.HasOneProduct", "Customer has one of the following products in his shopping cart" }, + { "Plugins.FriendlyName.DiscountRequirement.MustBeAssignedToCustomerRole", "The customer must be assigned to a customer group" }, + { "Plugins.FriendlyName.DiscountRequirement.ShippingCountryIs", "Country of delivery address is" }, + { "Plugins.FriendlyName.DiscountRequirement.Store", "Limited to specific shop" }, + { "Plugins.FriendlyName.SmartStore.DiscountRules", "Standard discount rules" }, + { "Plugins.FriendlyName.DiscountRequirement.HasPaymentMethod", "Customer selected specific payment method" }, + { "Plugins.FriendlyName.SmartStore.DiscountRules.HasPaymentMethod", "\"Customer has selected certain payment method\" discount rule" }, + { "Plugins.FriendlyName.DiscountRequirement.HasShippingOption", "Customer selected specific shipping method" }, + { "Plugins.FriendlyName.SmartStore.DiscountRules.HasShippingOption", "\"Customer has selected certain shipping method\" discount rule" }, + { "Plugins.FriendlyName.DiscountRequirement.PurchasedAllProducts", "Customer has already purchased the following products" }, + { "Plugins.FriendlyName.DiscountRequirement.PurchasedOneProduct", "The customer has already purchased one of the following products" }, + { "Plugins.FriendlyName.SmartStore.DiscountRules.PurchasedProducts", "\"Customer has already purchased the following products\" discount rule" }, + { "Plugins.SmartStore.GoogleRemarketing.ConversionId.Hint", "You'll find your conversion id in the admin area of google adwords" }, + { "Plugins.Description.SmartStore.OfflinePayment", "Provides offline payment methods, e.g. direct debit, credit card, invoice, prepayment, cash on delivery etc." }, + { "Plugins.FriendlyName.SmartStore.OfflinePayment", "Offline payment methods" }, + { "Plugins.FriendlyName.SmartStore.OutputCache", "Output Cache" }, + { "Plugins.Description.SmartStore.OutputCache", "Allows the temporary storage of entire shop pages and thus contributes to a considerable increase in performance." }, + { "Plugins.FriendlyName.OutputCacheProvider.Memory", "Local memory" }, + { "Plugins.FriendlyName.OutputCacheProvider.Database", "Database" }, + { "Plugins.FriendlyName.SmartStore.PostFinanceECommerce", "PostFinance" }, + { "Plugins.Description.SmartStore.PostFinanceECommerce", "Enables payment with the swiss PostFinance." }, + { "Plugins.SmartStore.ShopConnector.FinalResult", "{0}... {1} of {2} processed. {3} success, {4} failed, {5} skipped. {6} added, {7} updated." }, + { "Plugins.Widgets.TrustedShopsCustomerProtection.Password", "Trusted Shops password" }, + { "Plugins.Widgets.TrustedShopsCustomerProtection.Password.Hint", "Please enter the password provided by Trusted Shops here." }, + { "Plugins.Widgets.TrustedShopsCustomerProtection.UserName", "Trusted Shops user name" }, + { "Plugins.Widgets.TrustedShopsCustomerProtection.UserName.Hint", "Please enter the user name provided by Trusted Shops here." }, + }; + + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return true; } + } + + public void Seed(SmartObjectContext context) + { + var resourceSet = context.Set(); + var allLanguages = context.Set().ToList(); + + // Accidents. + var accidents = resourceSet.Where(x => x.ResourceName == "Admin.Configuration.ActivityLog.ActivityLogType").ToList(); + if (accidents.Any()) + { + accidents.Each(x => x.ResourceName = "Admin.Configuration.ActivityLog.ActivityLogType"); + context.SaveChanges(); + } + + // Remove unused resources that could be included in the German set. + var unusedResources = resourceSet.Where(x => _unusedNames.Contains(x.ResourceName)).ToList(); + if (unusedResources.Any()) + { + resourceSet.RemoveRange(unusedResources); + context.SaveChanges(); + unusedResources.Clear(); + } + + // Remove duplicate resources. + foreach (var language in allLanguages) + { + var resources = resourceSet.Where(x => x.LanguageId == language.Id).ToList(); + var deleteResources = new List(); + var resourcesMap = new Multimap(StringComparer.OrdinalIgnoreCase); + resources.Each(x => resourcesMap.Add(x.ResourceName, x)); + + foreach (var item in resourcesMap) + { + if (item.Value.Count > 1) + { + // First is ok, rest is bad. + foreach (var resource in item.Value.OrderByDescending(x => x.IsTouched).Skip(1)) + { + deleteResources.Add(resource); + } + } + } + + if (deleteResources.Any()) + { + resourceSet.RemoveRange(deleteResources); + context.SaveChanges(); + deleteResources.Clear(); + } + } + + // Remove resources that are not included in the German set. + // Unfortunately we cannot do that. We have no information about the origin of a resource. We would delete resources of other developers. + + // Add resources included in the German set but missing in the English set. + var deLanguage = allLanguages.FirstOrDefault(x => x.LanguageCulture.IsCaseInsensitiveEqual("de-DE")); + var enLanguage = allLanguages.FirstOrDefault(x => x.LanguageCulture.IsCaseInsensitiveEqual("en-US")); + if (deLanguage != null && enLanguage != null) + { + var deResources = resourceSet.AsNoTracking().Where(x => x.LanguageId == deLanguage.Id).ToList(); + + var enNames = resourceSet + .Where(x => x.LanguageId == enLanguage.Id) + .Select(x => x.ResourceName) + .Distinct() + .ToList(); + var enNamesSet = new HashSet(enNames, StringComparer.OrdinalIgnoreCase); + + foreach (var resource in deResources) + { + if (!enNames.Contains(resource.ResourceName) && _missingEnglishResources.TryGetValue(resource.ResourceName, out string value)) + { + resourceSet.Add(new LocaleStringResource + { + LanguageId = enLanguage.Id, + ResourceName = resource.ResourceName, + ResourceValue = value, + IsFromPlugin = resource.IsFromPlugin + }); + } + } + + context.SaveChanges(); + deResources.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.resx b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.resx new file mode 100644 index 0000000000..e15616dbf6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.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/201712081631552_Liquid.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.Designer.cs new file mode 100644 index 0000000000..a4efc6b562 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.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 Liquid : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Liquid)); + + string IMigrationMetadata.Id + { + get { return "201712081631552_Liquid"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.cs b/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.cs new file mode 100644 index 0000000000..450133d11a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.cs @@ -0,0 +1,115 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using System.Linq; + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Localization; + using SmartStore.Data.Setup; + using SmartStore.Data.Utilities; + + public partial class Liquid : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.MessageTemplate", "To", c => c.String(nullable: false, maxLength: 500, defaultValue: " ")); + AddColumn("dbo.MessageTemplate", "ReplyTo", c => c.String(maxLength: 500)); + AddColumn("dbo.MessageTemplate", "ModelTypes", c => c.String(maxLength: 500)); + AddColumn("dbo.MessageTemplate", "LastModelTree", c => c.String()); + DropColumn("dbo.QueuedEmail", "FromName"); + DropColumn("dbo.QueuedEmail", "ToName"); + DropColumn("dbo.QueuedEmail", "ReplyToName"); + } + + public override void Down() + { + AddColumn("dbo.QueuedEmail", "ReplyToName", c => c.String(maxLength: 500)); + AddColumn("dbo.QueuedEmail", "ToName", c => c.String(maxLength: 500)); + AddColumn("dbo.QueuedEmail", "FromName", c => c.String(maxLength: 500)); + DropColumn("dbo.MessageTemplate", "LastModelTree"); + DropColumn("dbo.MessageTemplate", "ModelTypes"); + DropColumn("dbo.MessageTemplate", "ReplyTo"); + DropColumn("dbo.MessageTemplate", "To"); + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + + if (HostingEnvironment.IsHosted && DataSettings.DatabaseIsInstalled()) + { + // Import all xml templates on disk + var converter = new MessageTemplateConverter(context); + var language = ResolveMasterLanguage(context); + converter.ImportAll(language); + + DropDefaultValueConstraint(context); + } + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete( + "Admin.System.QueuedEmails.Fields.FromName", + "Admin.System.QueuedEmails.Fields.FromName.Hint", + "Admin.System.QueuedEmails.Fields.ToName", + "Admin.System.QueuedEmails.Fields.ToName.Hint"); + + builder.AddOrUpdate("Admin.System.QueuedEmails.Fields.ReplyTo", + "Reply to", + "Antwort an", + "Reply-To address of the email.", + "Antwortadresse der E-Mail."); + + builder.AddOrUpdate("Common.Error.NoMessageTemplate", + "The message template '{0}' does not exist.", + "Die Nachrichtenvorlage '{0}' existiert nicht."); + + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.NoModelTree", + "Variables are unknown until at least one message of the current type has either been sent or previewed.", + "Variablen sind erst bekannt, wenn mind. eine Nachricht vom aktuellen Typ entweder gesendet oder getestet wurde."); + + builder.AddOrUpdate("Admin.Promotions.Campaigns.Fields.AllowedTokens", + "Allowed template variables", + "Erlaubte Template Variablen", + "Inserts the selected variable in the HTML document.", + "Fgt die gewhlte Variable in das HTML-Dokument ein."); + } + + private Language ResolveMasterLanguage(SmartObjectContext context) + { + var query = context.Set().OrderBy(x => x.DisplayOrder); + + var language = query + .Where(x => (x.UniqueSeoCode == "de" || x.UniqueSeoCode == "en") && x.Published) + .FirstOrDefault(); + + if (language == null) + { + language = query.Where(x => x.Published).FirstOrDefault(); + } + + return language; + } + + private void DropDefaultValueConstraint(SmartObjectContext context) + { + // During migration we created a new NON-Nullable column ("To") + // with a default value contraint of ' ', otherwise column creation + // would have failed. Now we need to get rid of this constraint. + + if (DataSettings.Current.IsSqlServer) + { + string sql = @"DECLARE @name nvarchar(100) +SELECT @name = [name] from sys.objects WHERE type = 'D' and parent_object_id = object_id('MessageTemplate') +IF (@name is not null) BEGIN EXEC ('ALTER TABLE [MessageTemplate] Drop Constraint [' + @name +']') END"; + + context.ExecuteSqlCommand(sql); + } + } + + public bool RollbackOnFailure => true; + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.resx b/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.resx new file mode 100644 index 0000000000..22e4900e92 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201712081631552_Liquid.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/201712290151517_AddressFormat.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.Designer.cs new file mode 100644 index 0000000000..d3db49312f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.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 AddressFormat : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(AddressFormat)); + + string IMigrationMetadata.Id + { + get { return "201712290151517_AddressFormat"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.cs b/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.cs new file mode 100644 index 0000000000..038f51ae45 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.cs @@ -0,0 +1,39 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + using SmartStore.Data.Utilities; + + public partial class AddressFormat : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Country", "AddressFormat", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.Country", "AddressFormat"); + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + DataMigrator.ImportAddressFormats(context); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Configuration.Countries.Fields.AddressFormat", + "Address format", + "Adressenformat", + "The address format according to the countries mailing address format rules.", + "Das Addressenformat gem der Landes-Richtlinien."); + } + + public bool RollbackOnFailure => false; + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.resx b/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.resx new file mode 100644 index 0000000000..460884a86c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201712290151517_AddressFormat.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/201802081830029_ShippingMethodMultistore.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.Designer.cs new file mode 100644 index 0000000000..2323322d13 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.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 ShippingMethodMultistore : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ShippingMethodMultistore)); + + string IMigrationMetadata.Id + { + get { return "201802081830029_ShippingMethodMultistore"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.cs b/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.cs new file mode 100644 index 0000000000..92e74e27ac --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.cs @@ -0,0 +1,38 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ShippingMethodMultistore : DbMigration + { + public override void Up() + { + DropForeignKey("dbo.ShippingMethodRestrictions", "ShippingMethod_Id", "dbo.ShippingMethod"); + DropForeignKey("dbo.ShippingMethodRestrictions", "Country_Id", "dbo.Country"); + DropIndex("dbo.ShippingMethodRestrictions", new[] { "ShippingMethod_Id" }); + DropIndex("dbo.ShippingMethodRestrictions", new[] { "Country_Id" }); + AddColumn("dbo.ShippingMethod", "LimitedToStores", c => c.Boolean(nullable: false)); + AddColumn("dbo.PaymentMethod", "LimitedToStores", c => c.Boolean(nullable: false)); + DropTable("dbo.ShippingMethodRestrictions"); + } + + public override void Down() + { + CreateTable( + "dbo.ShippingMethodRestrictions", + c => new + { + ShippingMethod_Id = c.Int(nullable: false), + Country_Id = c.Int(nullable: false), + }) + .PrimaryKey(t => new { t.ShippingMethod_Id, t.Country_Id }); + + DropColumn("dbo.PaymentMethod", "LimitedToStores"); + DropColumn("dbo.ShippingMethod", "LimitedToStores"); + CreateIndex("dbo.ShippingMethodRestrictions", "Country_Id"); + CreateIndex("dbo.ShippingMethodRestrictions", "ShippingMethod_Id"); + AddForeignKey("dbo.ShippingMethodRestrictions", "Country_Id", "dbo.Country", "Id", cascadeDelete: true); + AddForeignKey("dbo.ShippingMethodRestrictions", "ShippingMethod_Id", "dbo.ShippingMethod", "Id", cascadeDelete: true); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.resx b/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.resx new file mode 100644 index 0000000000..c7e857c781 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201802081830029_ShippingMethodMultistore.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/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 + + + H4sIAAAAAAAEAOy923IcOZIo+L5m+w8yPZ2zNkcqqbrNZtqq9hhJkRJtJJFNUtKZfqEFI0ESrciIrLhQZK/tl+3DftL+wgJxxcVxR0QmVfkiJQMOB+BwdzgcDsf/9//8v7/9z8d19uIBlRUu8t9fvnn1y8sXKE+LFc7vfn/Z1Lf/499f/s//83//3347Xq0fX3wd4H6lcKRmXv3+8r6uN397/bpK79E6qV6tcVoWVXFbv0qL9etkVbx++8sv//H6zZvXiKB4SXC9ePHbRZPXeI3aP8ifR0Weok3dJNmnYoWyqv9OSi5brC8+J2tUbZIU/f7ycp2U9WVdlOjVu6ROXr44yHBCunGJstuXL5I8L+qkJp3825cKXdZlkd9dbsiHJLt62iACd5tkFeo7/7cJ3HYcv7yl43g9VRxQpU1VF2tHhG9+7QnzWqzuRd6XI+EI6Y4JiesnOuqWfL+/vCo2OH35Qmzpb0dZSaE40h619CVgOH/V1qu6//7thQD0byNT/Prqzau/vvrl314cNVndlOj3HDV1mWT/9uK8uclw+p/o6ar4jvLf8ybL2J6SvpIy7gP5dF4WG1TWTxfotu//6erli9d8vddixbEaU6cb3Gle//r25YvPpPHkJkMjIzCEaEf1HuWoTGq0Ok/qGpU5xYFaUkqtC21dPlU1WtPfQ5uE/4gcvXzxKXn8iPK7+v73l+Tnyxcn+BGthi99P77kmIgdqVSXDTI1dVp1jfVT2rV2WBQZSnJgjAZkeZo1K3SaX2KCMtkE46vOk6r6UZQrUlCjlNAyFOWAcHbCXuE6m3/6Lu+LsjY19ddfYjBKTlSgppG3f/1rhFYOi9XT7ET7hOqEiDtlg2qRxt6hKi3xptPGC7S3DO99xGsi5qurotV2VahkXqB8hcqD6hte3aE6FFuH5R9FPj8duqa+lcmGWB810fBS323qE0n+wc1b2MgPCXOjMoK+LHFRtkuWfvGzUIZXyd38+rC5+SdZJ66KgzSLsPpQc6O6d6Xib68ni0lvRyWPR8RAuCvKJx9rKnl8xWDYG1Tqtgym1F9+sVshHRnoHa42WfJ0RiXRRX6s+ecS1XU7FmfeIZrqFt81ZQv9qsez5yBvDno7Dwd9TbImxgLm2GxLKzN1vXj2Y5EmGf4XWg1tenBvj6NjXgnhno3VbXXT4Ta1gOWX5HdNcufIIgAeOnWI0Od9WTSb5RX02P62ml5Kvj8nD/iuZSDFTL58cYGyFqC6x5vOByZL1vUEflIW64sigwR6hLq+LJoypeMrjKBXSdla/X46ZexWoCrp8ew1iLotw0L4ZiZx6Wemp7p2JZ6jfVL7jwZdouKI4NC1HmEPc5Ild6drMtgTnCEDuaO4di7q4L1SZF+A58Yrngmu1Zmt6u4m4wJVrY6r1ApUgFTrUBXgqBs5NaqEHpSuv3UmoI5ioAk49xrWrOtCrauB1tvZugytb2kLc1pR6TrPmjucS0rEVPWqaFJI+cSzqsKVAmhbGVWIl1I4R+UaV1Q4L1Danp04K4RLlDbUjfhKxLVXBOq2Ih0Aum7+bQ4fbU97HNuevKGzt6wU3qOWt1FJxQpe1kUevhaqTCKsh5Rk2AAeJMQsKh+HYV+9esUi2kuvt/TOJEEnJUKXhFU3bXthxvNV8nj8iNab4MM4gqg3xCkeYQr0VQ/SGj8En4kNUQ4d84fhiqofbZWSqBlmVkzijsNSj/lZFwWRf3eFRKtV7b97JaRuK9ZeYpumSB+qMfvZcTSnAz3KP8s/EPk4b036MGwHWVb8eN+gqib7kq9FHYwwwCcinRMRuXtHGPNLPcaO0T+v8NpY9zhfedYM9TX5bduopgG3aVyBbNJxpZAFp1X7pPZBXv0g6kzZqa78ulOjfLeYIlmlC+XBOrxDFqTJOxR7fa7RUYRKz1OXf27WN6g8u6UarAobwBxO3U58wkQMkn1IBF36RMjVOnQ0Rp8Adc0KI99ZBRioG1SwwXqCRRykLVhE0XTGi8OkQn0HKHUHQ3cI7TNKZ0cmZxm1WALmmn2IbU2cEuSCiOJ+2K8S6rYGGr1v8Nhq99v12LMi7ZpOIGMcQR6TWc5mb8Ui+D9uQydFuU7q0AV7wHaZZPXsXT9YrXF+VKzXTCDzjLdPovmYDm5vcYaJuIRSO47H6R3KUITrKoPj6iBNiwaILJ/DdxWHjz4mVX26OVityBZNd8vCNmDEoPFKRDXlWQ7uJ52DTar6Y3GHc98NKqnfchFR1UoUrjOXZM1wn2/eaBDT5ZE4E3aCy6o2OVHfxrhSRGdjkYZOyIyaAs2jzBDRzpskj3FIZmdGdFu32VniEJf1PRUQo7wpjeVe3SjO7YbxXDNgk4ksl0qWMQDiuqc7xFlGyDfqRV03RVigrzyIusMCnGuvxV2Qrts9zPVk7cv9FmGkHagSENp+hp3mjqiVhyUwhJrY5lNbXY+PH6mxn2QHTX1Pzf20BdJ5AHQ1wGmwqiDNiV0t1wkiJnKzPi+qGh7bWAwORC6Veg2AeHWxu7uu7mNbru4kXwz3UoBx7WbrD4N72BaBneNLpH4Jxa5dukBk/01Y5I/2+ALsGgcCdhGGkLqqAHPv8o+kXJ0XOK+rD5ggodEoYL8lOEXv1XDAGDTAriMZwgCs1hoJGFB/AoxaAYqArirw8r5o6x8lZX1KNixw30UokPxKIIn2akhXwn9LMrL50zEPBwH2G4aQOq0AC3LSjizgcatwvS7yVz2CvYdO3ZbVdu45JaKw2c9FSjtg3s/Facjkk4zTinkzF6udJq9L+eqf8YS/RuTbA85T2bNmaJFJGzDbsHpd82apht7O3tA/8Iaaq0lmuGoUKejlvsiRcYMfSUckjwu1FOYOVG8mOxkCl/TB1hlhprVcKJLsJrHc2VxihVTbOQFS7iIHoOwoDxV2JjiQy9nYeIdLlFKz51WPY29vqNsyLJgz3dpsQ9eq3vcUJQ6uihbb/KP4iCgRT6sl7nRe3ZcI2Tb4a4QGiaJFJU6FxjzD/IakOl+T4BOoXbjZOe/d1V5Jdke/8Vc6u1VAsWPm9DawUoEA8k4ZhAracwoLU8BiwGHaLwnqtlQbgrjRhHOtLDc3JXrAph10nIOpXdBZXjapp7yLZp9eKwSFgo0HShEiwnpce5nXyHxPqlChtwnssL1XbgqAOdhsCOuFC1/UEJEvm9UsG8zRExz7gFXlq1eexHqJ9WFW3I2xY84iTWtXrxgcuxHV23fmCj3OHxBHB0+9P/HihweMIEsxtL6eACd2gsolVgKBgtmo60oAD1EE+7VA3Vasq4Gxzhccm42UKdrHCdEze/hOsV/ZenxHbISnpzFe1PGRXiV386fJ3s5VQstM4LY5AM2N7Vom8Dgji+xBmcl7z6xTsHvEazUUnSPaJTPKTdQRK3QbVSoEl2seIiw5UFmsmrS+ILtx9MNnH5fUCenRKw7Pbhh+fZd2ZYHUt9IRbhEj9SKpGQ+8H1E+oGxz22T/haorwilZFGSfCx9c6pt33fTD1+5Ybr0eIZkLdxCAfNUOhHK+J8pi6WmRk805kdUSq3IYKepcC/KsGJGyhnyX1K5a2LXSgf6h2me/XzBqQ9pc+IW2krTZJn1ucwHH0LJfcYUJ9Gm+wg941SRZ9hRqh5guc8zj2m6f9FnSTqT3VpZsb9EbhgPXovUmi3A5MG5qlwFPvLPM/YYmziXYdps/XONoV6hou/3Oerps1tG2+pEwDuhaI0oYdHAf4yGN+cRP5I3r5fdmfqFL8uY2SakJUpJ1tDZG1MVp9n2NdQIep5HT6j2+rY+SMviwZ8ATw1qhV0pwic7qe0LxbjmJ8NhZi3MyfgxXpKMotYbaxvTeFrGNDlYroQ/BYzqt3hU/8qxIws/JezyhM/clzzoBHxAGj/HTEMJ6divh9Exm1KM5ftzg7jmmd8mTiNMORXvnvEURg+0/JNVlQqwmFGtWeWyOweSkNzQZycFdiRBrOPp2hkO2iNfktLqgSbDLCMGLI6KjpzRDXadCdRyL8RyVuAiWvhFnu/a3iANl5bQN/zzOaZUIqTVi5sol+hRTyUuyAePRPfWEjP5rlOI19U2dl+RX/0b2v798cUmztpP106P70VKnnFbHVTA5mZcIQxmHmDj0XDJ/IKJJ0BGz/z6cG4mFl37/e5P0vo4gld1t11qMBw8JJnVxxmANDA8De+q9YOE84sg/Fj+6UffZTYKDB4sa3z61DoGTohz6eIjI7isM8WGSfm9fQKXPwAdnBKK7QYrxtKMl2YGMm95gi6Ld9JNZwutmHWeSOozJYzyMA5bLGm1iYHqixy9lkVFM47pLvdJDQ1y5q92CV0jAE+XqAVr1WDFawFgnuoB28LB5OmzqenKuBOgWCv0NV/cZruo4SHvllyEivGRd4/xX3oe/ZHfSosNpsH+NQxJ9BT7LVvM20G/Mjtpj6JnauNwQPEnmOBB7nGNcBz2894jQYHH1cR6emAZ33nFeo7KKwl+92uYwo5mZolfsi7ZJNl9XGHUyGbzgERsCVfVBTVTnTVOjo2J9g/P+WDMiE5I+E53XZs2jIcQZDt8xfEP47n4+UeT3cdHRf8OrGbF/mJc240oTqk9GRGHKJN6JTbzLJXEzTo5n6oFOH5uMk3G8xzsU4C9PDX5A5ROt7Oj3GixZQgn5iNxmwauukhLf3hqPCX6Nk5mwvRt0dntW4jucO3aYxmr1C30UD8+I7xNKqqZElIYaCkTJzTi2ebBmI29DV7MRLf3Bo7YjbZOvMtQenhqcnXFksWvvHJU0k1IsHxuHlFIjNk42BVT4SQHOz3F7Tufu6j4v25PSvr5zV9T5MTtDaDSMwNCwofS6h74qphOiKRxMDSWFgGlAndM58gpGF9p2LcFKsWwiiCo+T4JzjdBjFwBtn3lAucNsubK3HJBnMGEnF+rkaxLYdS9JylBIFagqYlAJ78ozwza6804bAiIHH7YmplMEUQ1AgvPsOHvir+07C6jpPwSmGgMI6zmOXptph9DDaHovQKg6LoJ59nmMWIkXA6wNkA3tb+suue1Ts457bG3/4Sqa8egrqMZnqOU53u5ukkahcnCyPmWKleqUhfHUpl+JDU7seMjpoe2+pp5mgixqqWbJpqrnVImonQbuMFrXIXqPi92aaXmQB5SZkC1XciEH5JwhTAjk0HRWBJW7y0MoOyyAuXaZdTgC3R2LIc6QCiVekCGCLiAMFq5X5preFh9/7S8hqNsyOJJsc064H561MxMjrOpLRXebKRluhADyoWMyxuge2KEpV7+Gc5jGNu4B96GQFdnnbYqczZjmfYwpYZotPdEwM20wcytloTw6YaLEcw3aMnkfrgrDVkz2F7AVdC6ICU5alrTArovTiMO8N5PbFeroxsOBWgyJh/ce1eTvmcM9ZB6G5Emy9rP0qHp5Vl/1HtuEawDjgADVQwGhwzJm9d6EgEuOA4q9gaFuK9JJlaPCtXk6Ks5aseB1rcOiJny6aIvJ6s50vhGxpcv6abqL5htaiJMFXsfrBT/WLcj9LcNIr4TSa9Cxoqb9Dpvo9dNL/C+RjS3iIccEjFcFWVJRWouoRtPZrgdnfDztbBkF223/RZLfacM448xw5OvC8UOadvgy5a4FxsQL+dnlQJXbhBh+XzH68WmW1ydin0x7bAzNp9PALtLa1z2c2EP5O8fzwAGIydsplMn5OkWAsKRNQw+c9xKf0Aonr/r6+42ERn11JDrEeVJOt3j6v2wEyRR6vEbc9QloCZtjq8KFkdrFs3ChoXbPDqHiBGco12+Jfo106fwz+hG6OJxWV2WSVzjGzdSYCr0VV8rJUKJPW6XGIoHPjnqdxAMyJ0dAuXxuBAF5nrc6hRrI2hiGMIYaCKrbSzPzJPRUzyySvY5Wt0XEKvFRzVohdM1a5r8WC8nL9ouybQ6zubbay+0ovAxQb8WnOOJX6UdHZR03HszUV02ORXslzRyzBMgti2Yvteq2tuOTX9TnyHDC3km7W07avVt159yqe0/js/c0xvBlx3YmegdmmF2KcCBHDKOOj9CXTTqoXDKSQKAgEwkIgIgSHMng25tMWhXSkivCFkUk+0WTIatbvZEeBtmg2YMO+xdqI72IOCYAi4PuAlWEtmm73o0paYnUOlokE5pxEzz/qy5Mo8kTZZ4ul9eSDQ+zYbaDYrfcmyeO83T8SBQV65ha4PBtCl+fL1ROuUZqA+v81h7uEmXAW7Isov1qo27LsBBYXqx3jrDOivIDevyaZM3yrfc2+seCrjlzJxWItyE4rfojfu1u0tWFPV3VDfdiT7j2EqduK5Ijm7tYHYosWnLNNGbmDPUVmS0/C2KMHFv4FRCcERZkb2cGBqvhFbq6b9Y3eYKDQ8v6x112x9HzM3po1O6UgSk6HrFMCCHUumYXCU1yCHU1c6IITV1Xl4uQzWLeDBiKMytjxoyIB21MW1YDsO96lBM301THtDsE1HszRN3WRLTgHMsD0aMhavcFwQ/d01RM3TlMTAc1qwu3o0uNcmuhh0MkOcJ1NgHTXk7VbUWy8GNdVTmtTojV00xvz2zRHlOnGhs51CJb1ASsThc1/lDIngw4wzrvk9/K1N+YK3yk0BoA214/zK4fWHL/KXQEz62Wmdn4Svr0bNwfCjlUV5hBf4TkmLPpf0xdAqccC9cqMN69fpldv8CE7072ojzy0tm6UFpZm3ue/SjBq55biAQ2C3Ps1H8KAbdMGGg7MB0XeIxWj86WBDosjnTRogrSi/qheqtFHdq9VnRVZ8FvasWJyomcyCJKevl4bvcuHT53dBweVbRcctYFdJZbGlcr9Rem4EEy6dtV4Jno41FdUuY+OGbQ4tH1915z73VsYBykySiOa6K75txKUlRfFmXN4Gp1Cl/gg3W4wvMBT3EME2q+1NUtkq/QY6dc6Af3Y3ovsxpeo7agfMVVKUSBh7gWrpK7cD8CQbJXst5KNta1P5PVFi2/vMKiAnPQh/CmJod7OM9qkO95Wd3W5fdm9pCx9zXWhYlFuujJOGvPyWwb3xWLdAsz5tOQrg8t6rE5vqtoiJpzfEbREJ6xm4+RHVQVvsuJFA1Xa5d4T3krT/CdVu1L6eGBi3H855PP4X+tswj7F4POi/fufGv5nzX12W2LtN2cRDR9bZ/n0r2OYni5y7aqyldsXX+GM7/5XqHxGKzvqYHtay26tg0PudhW9Rm28fkXayuRH0TA7SgW0d72U7e1ldtRdlcHYt5O+nmuQkVN7yUKdfwt2F721G1FMpx6NNHO6GiOevJpvZk/U/1p1V+sDb70wqxLeV0WGcW2kynQ3C0an+flLBdxb4NF5Dmf8YB+V9s6riPUH29ZDFVE3B7POj0T2Na4VmpeIw209W0fFNQjieHhi7iY7FcRi1VkmVc6tnOouOQd07gHePFMwZ0/tBPwHT9uirL+lLSJTWZIaWK9JvWng5dIf0oCwNusUDbVVErZqm5UXTw1FFErT0j3+tlbc8Y6ggjcA0Sx9OYUJYW95ySGgZrEYehSH1gBNBFAU9lSo+gwzKBXouuUvT6xF/VpasNvGeyAJfmXOEdqkbKgt3mcZidJdwy6+icRojWaMZXgt/YAd4GGwm6mz2ZKt5vfGJ6hjzj/zuQq3EpqIg87eBcWMLt13GYJjOn47oPtY3u/W7T7xUyn+UC6/RQrWRy3xH4hA9rZL2R/ooVMdpXvis/d8gzCznHvtZy9K37kWZGsvF/jGhDsFymN3PY0et/gsdXut2sKvAoNuL6UwXkHAVSzrUJDW3O9GElPbclkULSzj8Xi8cY4DR0/kjFVS5xd7J+JVMxAK9+h61qHxWDtxTGgKG/2enfuu3RH9/Q9HLJ5WfCIxvhY56BnlK91ggDSQgxDBa20VxiVfaC/9y5xxLFfbdVtRTJG4ecNXHdN/k9axIktd7xa4pwyL0ubTga7ty+4o+WRWwEw/8R1DEVBDTC2es2DTioAhpB0gAIsZgj61MQIBXXzXBGUJUPEcG1doAeMfnxA2ea2yXJUVeFuLQllNP31gt7SYXhumKreHnxpoym63oWK+rek6gcY7+oG10HdtlUi8LVQVdqoGmqotqamakEMeFKUzfq8qOqvhVf0V1u/esWh2Q1OG7sUymUdZWLx19gtkLc4Ol4zsBMzKUAk7lHBxWGXCKyyN6jUbV0VG5zGsoRiBJcvH0d4en6wWpXtWjjzBm6HnkwQFyPv9yWMxpxe/VxPYIDmGUvVSmcCcTXeOL0Fx+8EaEkxFsekTZ163UqtgbAsIETaqVxDXAYoXJ33nQnS5y2OvUJXt9VSaWcUOp2tGEdRl83NP1GqWxz+Ms9txM+dIFSBlh0xnwNRfEyqOoaROeCJNcUDvm7ZEZeh/XrosB52ela5IPLFsNIWYLyWRFMHeyCwd+1PXdc6gPC1JHAZ2a8g6rZaAr0vi2Yzc0LXt5HyYInHkQveC/rc83Wgco+zylBVHGU/9zOuNX+uh+8mGVZr82sWSNDmTBmszVmAcG3edyJIpbc49npdo2R+em38U8q4FJS3xtMT7cEXMvuN1VVxkEY82+jkMlz1gE4USDd5qZ7PZFN2VKy7GFFn3UNrv2JQ7MZhRN+ZK1xrM9rEkbehsThuW4P2IpT2eq5RyaQDRpBNmYm9ngAnVoXKpZUSBApaLaeu+PPr/oVyk5WZ3zVQOJmr8yuSDLonLy+3cIpy0qqupVuN9i72ZU3EgS7K4BptCgFcedZs00r0GiJ4KT/YbMriAa16fEdAdlPXbWlRx0ca2YCJaql9QnVCVNGPotRmdo2UkJg0tqR1TNsz6STPkSnX2EGXKtfYdkmcoPgFlisEV1cewtXdyazPsLHqZQWIRqvWVPCyAmjseJkn2UFT39M1rXsn4AKlhG99Arp6A7N6pUO8Nxk0SiiSg+uYzMf89zWGWe5Gd4u1WdDjNtnv0Bds+Yzycst9yzR1kKaoqpZpkPz5gFeovHyqiE4x+Hdih+trz7N0igQ84bKqIC0BdrWCdly9M8lZqXaZvrv/9qpTIzSUQMa3GCIJKG0r+KJe1MsDoXgOcZYRYvURbMFhFkSCNxp0FuQlG6m6idaRONjOkydqfUVF1l12mFPzKjjmqClLlKdPR6TmAo12jV0kU2iy4abJv3vLwlXy2NsHMUKGvibmJ14iqpXL5qYmKjE7zdPsiqKd6VoO19jx4zKNXdHGyNykdPu/1Ai5RpcZaa91lhlh39iiIyMNOYiye2OcciSrCKYmQpKdIDQ3TdUtz01gdctzU7vHP0MeVYiHZmfSQdbnu9JYIjI7h0mW5DNenOyIRZXXBRnNiskXPGNTszVBdhJkEGjl+hCXczM/knJ1XuC8rr6hEhE5CnfqH92j9HvRTPlUlnS3So0v8nTVYEvFOsQ6uL3FGU7Cs26NG57N7DRoT1XoLo2g70T+iPAWbwx6sxTBRDHMP5G0y4vsKyTazHcCkVTf0Uo1JbOO8Ojh4e0iDR0/bnDZXYcv8ul5xYXa/C+UzE9PVr66R7XeoRscnBeGQXWQtobAhyJbLcAfcsMLMSbT8GGSf19kRy+0uYiKYds8PVqyufa8espJtUSTpzfJAsZFv5q2BuAYwza33Ddkh1Pif7Wapk0HlaT052QaLN70IiKjavwCVcwjbDNq+A09GViW4HKjC432srkZbfRlh3zelOl9UqElTyTOE+wbOzS4dITkPLPNS98cdTgQhbNpaibnz4Ju8HcoQxHSre5ugDe7E75A9CyR8SBYncN8SGjGv94t9bmgB+3dwWhwhFmaok19dY9J/xLyuQ1X+JDkq7MHj52V8mSZP9MCz5dbGb0WAafjZKhcOj0GgVxjiLTH4F0L0Hk3X6Lomvc9zsGT9qVK7tAHXNFXb+GUegDgdX/kzeTVU0JJwU4aUNdMB+/xbbtLNA4CArz+UqHVN1zfS4MxQ0uDsqjiOri2Fo0E0/B3Gygm9V8okjorlnv17LMyJcZYrOjZVAT3jCl37dkFWiG0RitWQx535j3QURbKyBRGYGkw5hquw6MrrDoUcSiVyc6XSB0Vin16tbHSxhKkqO0EAIXSE6Fcdd+3JCMWgk5fcBAyPYFiiagQTFBIJ6yxPd4479BUoGbfxyGp2xpPtwP93J36DXWWB1mnRrPA3hSYahisgeGjnG1XC+0q3L2wzmHJWHWc1wZekg6bNb7xhRC2vZir2xroFSqh3FIfA1n/yNB8J99zKJSBmvZm+VTDYJEPHyW51EO7KhTBIlxid2E1INj4DFI44Upmr1jUbQ3eQ0YrQLeV7bRTjPjHmQM2TquhswdpjR+SCD65AeFR0WwWcu1fEGJs6KMWi/gux9aWuedziXK64V5iZF1TywzrE9kVtpcFZ27ntJq4o3WibtvrPJOnzH1JtvKPSQu47WhgTaodz7WqjjwiBahyVVbBh18q8szjwF4s2qdysNmMEyIFX/qJYuefb/UpPdHyzXHdPjAxd2T22NDsgdhLjGaRkcwdOD74NjobcW6S8a3NTbutBPsuG+Q7vGAVIRvHaTUgi2bGfyQCkk+PGTpugai27l5wtecQwxXJJl9liJhZyfzhHJ2CP2pzxM8lTSjDD6h8ojajI23723Ushnin9gdVVaQ0Unw1mE7wYVFEm01lh5psvHBvs8P5LHjKBZzfWpvHmjenJtzn8ptTUqGma+cx3pwaz3sDrdzPMZ/6+Wmt3NBczczjQ8vl7xru+xZTjMxO7rANmsAuHgIUNyBewkvYgHiEoFRAAL69DGr4LlI+h47gMXAI1xz9T6ZmdiQv5c6bSTNo4/+gICEoGlAHJ6kNLXD886/IcVQ2o4l4+DVEP3nowiEQaQy92us/dVtRbJCrMkm/E4ovFHzfXpyOu9dseQb5hvQPGzPP6ovbPtYxiaLQK0IWXSMl1fGzLMT1pAPkDnIAythJHioo0I9FGUMp7Y8AzBLZ0ilKXJ9PAvI43nujMMSUA5W8wtLiJQdCmG7AToXDtBcFjSjsUuI62GDYakhLh37u9DBcK+c0YR6ZlR0N/JPiV5KKOUyZYunpo0z0bmBfHrrhe/NLlATbB6s1zs2Xo//i2ZrflpC/OQBtBmEISScrwOJFVHtck9D3MYqXi2ju/AL90SCvp8F7tzKHZr9kqNuKsmREM51irT1xAig6bXdSlB03Le887/kXtUe3UQ65wzqgvwZtN7XiYxjzRSzWye1tf81x7uCAedchaVIY3RYn5W2XA+6q6NS5dESzoAUy03OJBicuQ1CF+xaCAFydIFigf7No/QFHZAULCygUMe2XRZ3yiLAsnidlv6V23AZ1AS0eFdkpjnHFINqhV5y4yO1kWiQiiUrCQzQucLZtXByL5Tmo4om5FQ4tXktds/CsW0sJBji31LBRs4FIDUFriRLI3G/vPaAupEhqBYgsUsGYuxwlzkjKtOq/CEqo9qugui3DzSLbt8RcDwnRY00+rTfz5yKjN4D+aHAZ4XlBemxD4XuGj4X3tLpKHo8fEUMNX1QE0RFhg7uifIq2ENMna8sii2FrxHvb+bRqQ53dQ1+lOOewhwzVi4iohNq74fBBJwx7DajESV3b1pGOQ60rBp2Qwq1E1Oktvr1iV7clUSw4uHQrK0Vrkh+s/kn4hnW9RDfOuxCSBRo6rQgmIvYojXBNI0Ch2muu5XWWaHI6KzvPc4m0KSlf91kXg/O6KBDutZa6LZFkP3MmJ3GsCsclyEPXcmXWlWlXB3BuWlaMKmfxBGwvWRomfkoz1C3NgdJAEZ2jEhfBCZja4M0WX2C0vP7l9y3tLSJlOT7NcY2TbJc1GdtFKy12zddQqy4O0KiveGhXD5py/V9aLctpSh31uZdabpmefPpY3HloZFLrjoa0Mlj22ljdFkOmXTrEifcKxW4oJoHMoCgzMNcS/CS9GjBJL+lgo55GsA1BBxFQuba3cQ60JTLGUCcUfq9S1G117zeQHv0oSt1TEm/mcdQY3ENv52n1OKfAjjaWNR+HLYX7JVDvei/uPqIHlIU/M16UtTke2Toyy7H5EwK+WDa7zZiXPVjQPA0KUzjMHfpS6uI23vw1UoDcLSpLVC7SWNSQC6odtPciZ3Kkf6jrjfGZoTcxyPWlMiastF2DohhJKuNIaxRFNIaG11c9lpKxbjX93K8p6rZ69Rh8Th3Hc+PpRlLvZjTPd4zscQ083SEVynsACSL4xj77rlnYFdkJ05731W1t53x2yQR9EcNH7nJC66N7yuPuQR8xY0gc9tTZBUoLrxTwl9RRSNC9GpHsBUndVkf60DWkw7IdoRwMF6rbQ8dxSlbTLOIixvZN4dPqefSaB2W9WhAE4NcCwYIWNrLfCJDC4tVYfy+AP7MAXmbN3fKtRguKTPK7huzk3WbA/i0Tyhg4DYn0pucURf5KxLQXqrmFiozofVk0m+WZm7S8fKPcg07LnYZ5XE2zlr6re7RGX5MSU1QeotfWr15xaPZyp26rJVQEzl1k8xcmDW/jXH6ek/v7RC3utlu7tev+23P77Hzoeo1Rewowl41XZV6njwAmuj03nJvEkeCTguyQSMfJ/wdZRk9rgr0fH4pKm+khUh6gj8VdcY5TKlS7cwvpQ73ODosVY1XNd1O5yGsigkN2yc+o/lGU32dnmPMSE1331GqFo6YsUZ4G25A9zuPH9J5sNBB9JMUbteY6rLIR+IosHeG1thZzV9YELF+aNdZwv+8rz4x5ZAK4YkgclH4sPGjYFeCxWx5PZpcopTGRrwYk+0Va3ZZhkf7rTD7JbmIMT0L+dZYcR/bvMP2773LysaAIwslq49kly/eaTHrbwrzthd6PFZUWFbDqfqsX3+YM7gjLpyGOsdVtx/mKzOwCFtZF0eSrMc1bFcm2bbF+bta92AXe85j62F4didnHCes7lBdrnCeE42e7BSo0edEwqqO9XtlryxaOZuVsASLugT8l7bF64Fa4x7JfbNVt/QSHGXP6Y1KyGBDevkqq7/73bWltwpEyrj1jaqaVIVf4IfFFk+eMPeKrij8l6T3O0bYYnfBKrNX7BOetpeOVSfOySVOEVp61j8tyWrrmMxjIn3c0VOwcke2h9IalXd358kirc+0zfA9vmGU9cs1XYrbNBlh582yqEBZ3x3Uzgh7dK1B1W8Zt9CxK6iDDiW43ECvkvciPHzdURPWRfZF0CcWvaeXfd+j2EGggbc5yTuv6ovqMHmuylHoo/dPqA14Rzg3eDDU5Uej9Ohwv7As09OZWvtJTQLba2kv59rewvWOe+/rVKw7RXgOr2+II1d1/NFuOs90CswuFjpUhfBZPyDKhzH9vUINWx4Tps4O6JprGMwtMbzxWr0CEe8FRt8UQLPj6JekJmQTq/uY4nypzYUYkUFfvcTLlrpnPFDnBQNC0wVPLvEt/iPOErm02J8BB9uVMN6o/EVvDYI3N1TJa4aTnEdMEKO0MOnugYQHqiOsOfDIp1FDSTk4D6nqWy47cofN8NdMgWGjLwXBVXAfFoHQYE1fLNCTmq+WI2BpBO22un1FWrv16pdH8JS7K4NT5lJ2W37BfFcu3eYE22VOUhg1ugqPZmzhM09nbuGxu/olSXdqjSLYFDRabP1Qs5on2JRHQqxIHJ+gjaPx82a0CT1P63mKwqYry1ackb5Ise4rn2ZhWF/hmduR1TvRp2K+MtgNiSW4a0TUPDA6Eg9Gt0zxg0PLMd8t/fWbx7Bdog5jqtgx/nSWwqw8DMu2ULBs3BzovP8LzohRP1lxjkioyk3EI5LxfrypDMrKZWu7cYZdV5qzmZeK9Q7cJkWqyqraywMQwRXaLHSXrTYLv8hB9NeDY6yp1WwZtMdedPqONOVPDkWzObYZVLuOW7oXoCq3JmuJ1LXkUQwHVXhq9pXEmX+PPvCn/RCDb55rmDxP4mFR111yJYuhUoyugs8e7FFXa8cVJg/hzOQZi5YeIuSePfMEgaIuv3Ni/8TgTmmq/Dar9q7G29QL3Gf2oPiKqnwlfM2fV/uscjHG/3Knbgin2vsFjH7rfPiK5/D4rjj6J67B0j1aXkXwryu+EhDMnwjkpymYdKIotjuqVhGovgxoWiSl5sZ5eaGcw/EGdDU539JEYbY5eiX/BjL1qKMmtrAENciyfl/iB0GKMW/cVWB7PXlo10hpDoVMeiiWpV0UsTObtxV/muYxN319e3idEn2JOIjyX3KdnPnw6aOr78JhrBuEFSvEGM5dZtmPSqKObGDYG9SivVq55+EmRasAkTaqDdY0JmkTHpvcstLLvE5Cp5wxk0BLwCSUVUcTdi7FBySo4TPs1QN2WwUk405M2W35R54KSzvIGunfaCucMCq5i8o5ovLzy29xIkjIi2wvLXlh+JmE5XW+Ksiat3mKvrJxc/b1w7JpwnBTZKtqjOM4X0bM2oD9OCoo4mKJcVup4vjUj0Yq7guF7ePAdbwK3hsl3FIahyyB0locfXxCpO8EoW9G/FkgfNLDZUZHf4rum5G8DzXWkdfxItBc79zMmYMyadT6mzZm5tQtUEQV9mt/qjovjNNVfgCXIvTKB+OdZ4NYs5SVfNZS039OAhmVVeMpT/3xJlDuHjI+vGFT7VVoj1lGSJnX8oU8c/2YeB5tVvqaZFvw2VexjvaVtQEvzD0mlu6n5l3ipEE8dE890tYY+zb1mtI3RBVxazE2bqTz18x7aR8g/UmX5Dm2y4snz/rWIYq/R1G31q1KoStuOUMfKs7icUTMxZYwNi0UIfaR02+aI+TgNGZPNx2iEPjl7VSZ5tcZtGqEYUwHhHEfS5hxolRIM5rFz7txahswDcebksrnp/ASzt2QdRRmJEdr2LA47Iw4uTnQilUf8gD4xOasDbpL43EfR5H/vfYXAzkpcmK9H4GlfpYKRdlVKwLALeo9hDk+u/t7wULe1ww7PmdJ4UrVNf/Wkml9vWyaeipNy8wETwm4119Vp1S+Kg/AGRpPHMS47hRDTGxzmF5OZkjDjEptO8iddZ5fwvg7s2Pl8F3HDLriHOLu9rVDg5dv2ekMYisOkTu8v8b8CDxbOidboHj8LlLOI5tVRsd60kVcuBmm0DBn/wJuDMr2PEcBOa02PYoYbd5O1BeeUCDLwxAwSRkswmsefM9qUHn81lMI2je/xP0zS76c5kZf0e2B8/hFRillx90qBcW+5qtuKEvhL/lw1abimihTxu43YegXrgRH2JlhJAo0V3N81ayfMaSRjHfNAelDrcQzwYRGjZHW4TdrHC8uA6+WDLoHQ7RWJuq3tbEO/YvQjkt9w16LVCCOiu6J8isDLIqo9H+/5eDE+7pV7BDYWMO25eM/Fi3Fx7/MajSBvJuYR7XlY3da4q3gTaXfyNgzP/Ct+WVQVscGzcC4TUe35bFf5TM0dzZrhjb83SQtG487KIuuO2k+rkyy5q0a83uwCYI/GMUSzE4HJnqiPn6EKT8pPaH2DysEnscF5TmXsa5I15O9fJMpz4O/INKyKH/kI/0amcEdLDX1PkhTVl0XZvffrT9hLlJTp/asWXfWKxbpFgn7AdUUfTrGlaAt1wMC/0cN/TG5QxsLLEYL8jHGatK/zq++sDfbgB0wD7KJOHYt6i/N3dI/S7zfFI/Xa281g5xmynb/PzRqVOL2g0dOqObSajyuMynOCCR0lWdp0rqXhqaZoykrdyBanqDVpbWfnvHvMkV6e6yv8VV/hYPVPQq0ugnSY0l885udbkmWoPi8qqpAuUFJRd3v4xPRuyOoVgH+Lc3KwWuPcek4aIvxJhWxlhtg3OMtsNR6BbvKVStdJfSEEw0kmVPqLQamiG1yrGMqKO+D3sIPZY7pgDDWwRf5ou/EJrzYFUe/vWAvCwCtcxS8bW5Y5yH4kT1VbmWvNwDtMNaYtn+XS9FRU8FQLKd1VLW1xzg+z4sZ2mmmME5FBRHnWWi90/o+ANVQXWxsui+w9KXVL2zT+MQ1XOG/zwNtN0yfSEbwh3T0pyjUdIFfZajNwUFVFilsSDiZtQa+xdw6sC1S1B1nXQ6o6of/H+epFd8ClrTUdh02B1FCFly+6ERFiki3Z7y//D2n4tg2OQQhMg8MQhEbe8GMijZzlXcaeFwdtnBM9kKjSZCVvgwlFV/yXXmjoGkZ2lBVhD6InZUcBzlMyb5nLUAQklv4G2smxObHkHdqgnLoKXObQph9sYkO5P2OzAjFNtPvtNcOsFjyM/9U6G9u+WTIwWEXJvSy0M+vCTT0/vtWOYymm1c7bs+BYsjHqV6ELlBblaoxwoKOslFyrrwZxrljDhXENrQHMywIYWnIhVpFlZonmoEBSFHSddhg+h/BZiSrY9QWkE5yD5yGQpOcHefUDldctn+iYgoFT8VkH4sptLGKA3yAG3g1eAzq+ELcBc2HTMoXfGq9dkj0MaqPdyXbr+ojGPpdPSo4DoSG+4wBdWA9uAVLvfVd3jge1I1iAE7VzZNN+X2VrLNlHyxuZUYCD2LAHcWFAEas96/3y6pXspvBiIUUfFmAeBU2fE9vwqsc0zby0xGUhHjfASFotGZ+dwP4syFQgrW3a5ypujcHGwO/pOo+KA2RQiLWmMHV73gIwA4xlx7Q+Yz/EGb04ODRg7CYPH50KAnp7UsjS5UGNNh9RXk/3DUz9FSvo6NHD+pBFakazPd49A8o0igU0lmm+rNZD5mLNVvTVYVbcUa+82V0hQUJ8OQC5MKSM+Fm5LpTdX4AFlXPyLFwYtPdHxbq9dDkyjo5LRGAVB/ZwrkwooQf4UMXgu8GHqhEsxIqq+bFpfqizPYcabq+UXX9CK5z0x+JqrxoADLrWOjgnvxqEGuBErp+zbQt0vVnCY6ahs03zbL3tcVYXfjyMZeAJJQOA4CB3cZBOTAa3AflwYeTb13f6ISzBm9p5snLqdlV2hjH7yw22TCPeJZ6DMYX7x3Ibu8+Y/BC2wJj8PFkx5pQ2YDtelP5arFFXioDgXrmHcdoki3jtNWO8tVfViSX2tgq6Pget9g5XbXqe64MNmRT6uHI/GqxxxukqQUw1wLswlbYNyPtix7gOpGFTJxhlCwKGSMHCuZADxL8NOdN1ZAFZ09H5ecobOyIXkePqzSd1fDPQ1sqeo4Po1C+zLiQaqsxHnbEFe5srAk2GHxfojwaXqMv9Zew0VMuFMlGNRdv+AXQF4MyT6KXrrEi3gNKzIpFNP4b6295EDafhZ7dnJb7DuWkXJcJrtlEe+ycJ+zYiFAx9WW4npKK1TQ+EqltnM6Ke8AMqn9qUaSYuYIEjMxiHGlJpbD9nZzGoNwvyF0RnK+XF1Ns2Zx02+SpDpzVaH9R1iW+aGnVZe6+nEhPD2eDQ8KGyugeDWnVFbeIwY95VB5PLCJeTBRcWsDoXGmvtjoD0Q7H0l6rqWQlCEOcL7T1DJ6ppLNvga3gW7Xl5285VeUDujLwcC/swb0zTQt2VrfDeM3Tm9+2PLuXRrWlgAqmChtt8fPzKZhxcsTujKJWjWI5LlfNltc3q6+wMl1rqRBF+Zh59xku5agxbYNDnq0S5swLOTW3gIGVFDcP6HuwYm3R0ue8MCxtHtBwvG+fTpitsvZ3ibEvlC9VZiJ+fsSLWjWNLDPx8FfLlBqX4FnfZk0aPhy0D62trWBmu6MHUhh48Q/a2G9FyjG43x8+B5eGRnHUvhSg4UsV+HrjAK+QaNE43yj26A93VtBLL7YtKwHAXEJwA3rDpHYxhRxcSLYP76XY9ebe25Gi7ZS1rsOhvX+LCx771FcuGb/zlr8OzbSkcluWr5E6T1EqGjXy4zmJWm2CkOGLOqg7n16TESV6P03JUrG9w3gI6hR7Y4tEQToPCg6bWHdp2LINrR5fTC65zatOzXYqA0I3PckNngWInOP4Z7+8chrUbovEMd3oWoxoeJvmS4yCpYPHshGhwHQLkgxv4NhcDqKO7wfHQnNr0jK23a7zvuwJ4qP0IDP0zKfjd0erPWJUL+6zqEo27DLOzzgGHhs2B6h6cbtUJNdfvsFPOY4DLiYLL3DuIxc6430R/hoZn3RhUg8leVsLlRNcNC2lRi+yuyo3FgLcmPRY84SNDE5ZtS5Nq4bReaowItmNO/RwrjPXotm9p/RRrizi49v2baxW3OrKmFpmDlLR4IoiKvj9qsTFJ7c5Kj9WAtydJVvzhIFUiim0Ll5Mjytbb5HNms1N+oy07h563B4g+AZgVycouFSAIDSYh6AGd0jOAyLeWDVDbnQXYS0trm/Z3KR/g9WVCH9Qb2cKkYHjwyNpLQA4dgirYN77ugvuyoPaCKW3TAb7m1jhsfIiZewFLyWEwOMRhI6QLjynQOz7H1fLZtm1A/VAWYFH9VNl0gK23AwxqOlaRIGdgy+d4WKLs/aJM+HwPRC7QA0Y/bE/1eGjN2tsBeqzAQgvPiRW1I1hu2Ybn6Nmx5AeUbW6bLKdPlfBMZcVByupGpmVqevOvunU1Q8Mis2NsbRzY0nxunGcHxu8qbo39T4qyWbfvBRjfyJFBIbYeoVz4GEDt9CJOlC2QuhML8JeauC525XbZ6KrY4NSSj3hYJSO1YM6cJCDfEivBvViKl2ACPx9mum7/fV8WzUbPSQygko2cOYhFCrAP07edWzpV/V+K8YD5sGl6qrULSqzjGgsl0w15DvXVYVYx347yHdD1ZRUeNx/WfLcD5hfDL2YriRlwfBOMQa7iPpCvd4QFFWNY1IaT58em+bbC9lnxa1Gj64mHjAzDw2sZkoJ6MaXQhoovd/N1OMMoluRMeK6s9eRWH4n7jH5UbR4+43OZEiTElAOQCz/KiJ/Vc5nK7i/Agso5sWl7689l0t4PLyyOjKPjEhFYxYEez2WC6AE+VDH4bvChagQLsaJqfmyaH+ps/6Fz9jzR4q1vDjz6U988dscTZv/L88ePNSrzJDto6ntK3i63wQVKi3JldkZZ1YZIpavoQj67Djyr98GdhrSAvDvN8bPwkJ2VK5rUHWcZzu8OVquSnhupOAwChji6hXNhXRAxwKlDB+fxtep6sQBz6ahr0zxfc8scZVSXPFhELnqemg7u+2Jc9yx11/Bo1JeKGPQfMOlN+TS+RKWO9NXV0j07xlbweZwNblDzjtjucanVUBZgWqs5tOnH1t8dA0fSaT4nfurEeCnu7VoDWBfU2TvKt9wgtsW03LzZdKKtsN3Fvd3l6nlUgFMu765uMxHv82FBRc+XWuHluXhOzGaK75QgZ2C45xjTqez9omz3DGM53+Pb+ighm/pz0uX7pEKrb7i+nzhIxS6GehBbDlVcuNLUjEotQtwf7+aPZa8W4D3LabDiRBDD1hmTMyJGFjLxC1hLx5S+VqO+QYA9VVKwfR1qNZQFeVo7hzb9GOrsFg9/YUXMjZG5qotxM9/q8zFE7QezLaYG59OmM1zF7Zqtn2l4hMUeaYJTmqyfHUNdRLzPhzUVPV/KWJXnYvf3SBfoBxGf84IgqAb5MfredZUgNgTgXRhS29yz8tLbjGQBbrWZv2fhwYcGYmcIGGtaqz1w/+PSkJ/AyM26vOFzjzdt0ImeSDwY+PJOD+H0yg6P9fksL3DHF5BXeB52f3EZ+t3tmQdWMfEFB61jOldfHNwA9KiMgqt3hwXBISzIieAc2bQ/IthueAHtxsY6YkWAjhhsIGK2j1mJ53bT9mQp21lFY1ue2uxA5Mq3JCO6wNqKhsEh5uIgXZhM0cSzspn1Y1iAP/Xz9CzsZH4IeqMPgJ2RJyPY2hG4ammDTkPj3bfqLlDdlPkF+qNBNskaYHB418NAujkIwCaelZrTj2ERp4Bunp6Fmps6bWneqSpEv5EQ085z2oAVbbNHSdmZ7IdNvsqQ9ghaUwfejPHgbhsydVPqMAlmDLMtDxY9W2S3ZZwKm15MtbboCRBGYlw2lDVmZ8LnuX4Yh7ENfn2Wq4g0ClP0mKrC7Jz6HEPKTIPYBps+wwCz8yLLvhY1GUV/e4x+OMirHxqVqqkDpgUUwJ3SAWqagrh16vzOMazFUBbgWYu5s2Lbsdb2jPR7lH4vGvFtCumz2mi3RAAa8WBdJ5PetnXIepDGuHPc7jq8BVjfdb6tjAyx8ha9KWlTElLcnSdP7WHKaY4pXtPxtaYW7FvhK7i5V3SN2Z/fRtmZWXVmEXeJxQxY9YOptzNcOLgmJbax5REVAhve9AoRsmwe4FaTaGxfKbuObgvsb5pvmy6JdbcmDXRaH8jcfyzurpnflGeUAqCpA/E8A+LC57pWIJ+i0Pmd42yL8SzAzBZzZ9MLoepOsK/RzwYBz8Swz9OxphvBwrz5LN1pVlxo4j5HrvPltp14XWhLjPZsGezg9hZn5Au6Nh33SZCgohuAnNSchHnx7EPKLiyhpFSEtVo2txy2dZBmQgauNi2cekWDwOElM3P3nCrQOyax274W049jkYVTN08umo3W295pFNlW0Re28Dopn44f0/skv0MXRNSOyF4J5emT+ljKVBM8n6KVnA6ljK2ArNv3fR5VaN2nBdjQehas3PdqNLvBoO0fbpzJVYnPkjz6LfMi2JmlmRAkuAP3cfW3x3bpPVo1GbpKqu+D84n9pmY+Q0WQBeU6TgxpahK6g8KOZedWd9sRLcHblvNp0xW23tY4++8NatDqeJ3g7KCuk/S+9aOeYI1Nqq4CcTMI7cLPmuZcn4zeNiObh7IAC5unz6YTFH7HmJZ7j96Nm6C33RdgYuhJe7ZZbkSzhZ/a921r3AnNj01n2Hq7wK3X3cBSfdJXVQUDZ3ryI98EwIVcn3fOODCNZFmeBefLpgtsvV3gVEb4WBZz028sYZbTqmyrADtrJGanuFk/oq2pYmBObfrCVNvuU1+XzU2VlnhDv1s+2gpWUT74xUI7P/oFN7Wll1y1nVmAAc3Ed3GsbvGpdfyQ1OgTqmjesOuTslgb+U5TB35UnQV3e0pd3dDybGfRmwX4zoL4Vlskpt6uMN9V4cp6U41ZGY9pZutsJ/dleaaTyW7Th6nW1hjudL0pypp07bb1KNg4SdVVIJbjoF04TtOMs0s0CtuZO7QA35mJ7+DLxPndVr2Zx4/OzKeuAj9/5sl8mma2w3zmDi3AfGbiPzvmIw1lRReNPLCJnifkCmrGm2DdeQ9oB3L26Bh8+/tj01AW41n1rNl0oa+yNVY9TNLvp/llXaTf3fbDpooQ6yrquHCwsdlnFeRrO5oFmNl2Pp/FRls1GNOVekO9hXn6Od6ztxzLFhl652/dH5M69ROpU5MaqBxMm3VS1mc3/0RpTYvQI5n8tJWzJM+LusXyty8VOspKyifV7y/rspEtDor6EtXjLnGD0+rli+47w19tAcCyQvXk8YjsVO+KEiMQy1j+ZMRFftFr5hCavsiI4mORJhn+F1r1Mwh3SoQyd214fB3ENr51b9M5dFmX7V36qmU+ZfcEOCPyc1SucVXh4VFfCLEIY0TKv60tI+QDgU09LLIM7BX5blW5Sx+gQjFkcbAckm44RiR94DhIkzHW3tQRerqnkJquzEJiiNAjwsQPRCGCiDgAa9q02iWvdSTqQYwoD7Pi7ryoQFxDmXnyO+ULzvywChpQDE8YQjimV0tN9NFpOms1d47TuilBHH2REQUbygDh4WNF7Kir6xYHYe5dkje3SQsLihlbbj1xND0gLtFawZcAmBk1yvADKp+u8BocNltuS8Up45mGkGweOVe0Y96IE5zVCm1oqGPbqJbdeRgLru/gTbwBgNmivtygFN/2L9uPQ9Y0AlcwK12w2llrWoI6WAPv2Zh9M7bEu0pAu2sqtUX0NSlxkk/pTY6K9Q3OExVxzLWMDf+9SdovX3IMqga23HcUDl23bcIGtz/Snh0JgA36Cdq3IetGfGegTb3jMA19eiXTEtBHCIPqf4weNu2BMCrJnhS2wMZCI5o2wkFlK42FdmiUu7qp1AZRosJg14v3ZdFslL1oS42IPqMflWo5HcqMSI4fybKXJ9lBU9/T/XinJNUbJR28sbE2nw6EtU9OZGnwMA/LwSsw/D66ATvwbB2MHX550BK7DqEd/VRTzjwGa4OGPoymRNM9T2dAIz+IBNMLfDjJtLj3T5KAy/n4EowlEhXF+DdrDMjY9PDwOIXs+0bqMam9YbJxmdeNY+XzZ8LjFfOcmnZ1YkY2cHcnJ9BzRatcwVSpAY20hRIhqbhTkejKsQ0b3GbvyZRYBHSgsPle7FG1Lk89ui4TkdFLB/bKqjdDMgKwH1N2BwuJphz8CdX3BahLeQgLKmXqtY7JFWBA86XUoBkLzYsEyhExiLTiJsKYbbB7sudvbcAb2FXJAVg41grYFdJfCTY60to7qgpH0Xjt16YTn5J2ppV96cvNyKTLinDvwAunDrhNOM32f6dF1MzPAVjsDoHAdXibCF5AsEdvQGo2UplbH6BRyl29Mboq15sE34Ebs6HMws3Yxh9eofUmU2g1AcTKnP+IamJesydyKuNehrTbeJhwA7H5xl0pG5EJ70P56FcjbZOqKdE3hO/uwenmAGzRvcOEayvFqEUYI1IuHBDCKIRfmtTEU57q1NlUbLGh4+Nu4E2cGCVlhVQzXCEeynT0AZ8+gychqggCB0+7VkoBONvTjicdYhHG2t2owSmAWNioFGylOSTiIcwDL4uqIhUzDUoRRkLKnOzrT4Cvp/Njpo7mKHiqIAYisGnoNPXGGJZx4MqzaSnWwbaJIWCFbWI6QxcDT3hi2RKSPcE3UxGGNowPrKSknxh4YKIejH1m0olBANfCIb9MPkMN9SD1FSEyAlEMGiIa8AOEFMYaTswiy7SsxwNohsLCgZTpoid01OBQzM1F0yMQXfwGPHQWRN9zBlI1/DH+w0AEFhVABpCSHiTgYiuux5ANmRAwoHoMIDxEFDH8Q0MXGCckIVNYSjCFhpSMGtqIIOoRCJAQPZiwHA0pREQLEUEIxVGTggc0j4Of2mCy8OgA4ui5zoNCY3wx01GZPACUejAyMEQYJhZMQxgAF0AVJZFDCHKIs4x5TFJHFQHUYjh8jQj0ERAuRKQ+Em26rqChkgRrHpVYRUenKW7OglwSYo25EoNeQ7Sd1lyRgdQDkWAh0jDxfxqayKhmNl9og0fFur2yM4UhwvSQ4PTjEMGDGQZECtBHSWof864L9ePTaEE2HgSnsc4AcNDaG2MRdaYehAygixAsGU4bLhryeoyKBKgDQ2qGBFYAKSQGbeoIBWOFTGMFughUGtwkZipBF3S04xFu5kSiknDzRsYaZRHr/Wc6LpJgNGuLAArqHiaqU7dMiajmZZchmOX6YLPJMFpdFWw/ZaJo4dWj0lWDiMWEjWtopcUKLevKKfCgHOvb1bERCKceEwQOUUgI6NVQCcS4NFcJ3bVhLL6KCxdwNWOyF48YWvN0sxKFkJMStKHhCO0yyqFSTMqNOOfV62PD8PUFDcnAChYjhOpFIByIFqAdPM5YVsTgqzm7PSvxHc41ZoQEalzxxRoaQ8LKgpDwzexgGprlr6uoCcTBmUfDggeThkMGsRF/JScWbZRXYa7ZqzhKkllVNw7eBouGwLorQGbKWzWu1onctab4s9J3wrxJUFZxIABf04rijiQWWph3qZFbdyKjDwHnJN2iRBs3OdM1NiXNZFjjsKQqGopZbsuUmOfeZogNm5lMArUflZnBPMm1KHdxGx7+RqOSaOo6xkEqq2rIaL97MzaywC4E6oOZD0Fwt4Ga+TGMkIuyJXxR1IKQhorGIevra4irvARrJrOhzXkJrrv5e626rwvED3ig0UQCuGMDYw/0t6B1oQgeHYCOoe2YIrqc6K9zuwqPFpsvd+spvKCYaTtiPacKpgrYKg8hpu2ld/VOmQUz720Z6OB9MotLraO6/seiiuYOvq1vwRqFkQK2mDSU1mciME+CdReW82PoumRev21qB1HFvJLPOSeLGlG6jvAJJ7ymg0MRRBUW05YmhusCMDtCAo+5pshDQvzFwkMWvOi8LNcDCUOuxSINea2qG0lgg0VDdjhFipnyVs2qZ2EOC1W0qNQ9tZ8UHRJXGmlwOUyQx+ToGraYIg1nxFdHNoJjruutOGxEJqqm2pawgAmCrhWFDnOhx+NMJi06hzkask24T5S+B+pJM3JJwIbN0qRytJtsjSO7PdvSZs6Qn8oYpAkDag7/IXgwlmDKnqWLJQDRLReoeX2ZrDcZmtJ5qdlHgDTPOV8hmIUEdNDGUkVyD/qMqcm4e3MQfRSQ6gHBFSD6sMnTNBRSIFzgit/Usmb/IAPZDEWzR3Amy6L7gAv0gNEPiw2VAGiUAB4+OHQexrogiT6gbHPbZDm9D8MVGGmmrmk5XCWCuFRVN6ORTVUzHuQeEyNqLwABUOrxycAQydh0jRpiAchmvugzpXg0k0QAMwyDh1YSZUg+aaKKgG4JslyzmScVNGFhDCNgQBXUSMxkYJEANOAyacZkjj6Rp5YzOhireezGEocnOlwwMSSKBikNNh+qTm0wcDayzowlhupg0Kk4BCZwCG2+FmS7x+Sd1ZBHALUYEl8jeElS4VVRK9Y9uSEDrfZSpQykHogEC5GGyYmroYmMauZLlbTB4aLilJoXpocEpx+HCB7MMCBSgD5KUodc6zZmXFFAqocDV4hwp3vxjCq61Mtae8auonrIVvUhihqSS2uobNfkzHZRmwrZItMACKceGwQOUW/Ilq0hE4hq5uwCXZs6fhMgTN3XcZA1DZbiCSi1+PX0TI76+hJcQeMb1NXTXV8S85db3GWC29DcZZqNkn0ieEsydtCO4+v4ZUYCdg0A1IOZ2VcE23VZSS8RxCA/E6RSCs1mlYhpGRJoXG0ykE3nNS42Z0Is41gbHhC4Pic9vk8qtPqG63vmQQCZNKYq6sEZakJkYx4/0FDNhFjFTrGMUOhdh+vpbQY1DeEK5oGC9XT0c9BM+jYAUirnKBYlv7ATak1OvpbjeLnKcxKWb2h2vUef5zCofgbEoKcmSKXG698KMWk8BtOcJADeE9Gao1p49ZB01SBKKZ5D0RBN28LMdizUtlFCzZXcBuspNo54fefK51pL/6KMWjgFCPW4eEDwnsn0Ro7uTgmPZ07BZN/TuZ5e6VETgQc0j4GD15HEbKLBKKGLFyoqe2+dhydKjB4FEdC0Bxbgg/bTIq6ZnQrc20laba6AVI8HrgARR3riSUMlBdaZtTbfqlLJQGC2Q1GqG3/qLGESMG9lGYwBEFK3vkAV4EWFf9FLu2iBSGfmnqnzZg2khLXwjFvoIUdf+8LaSHwozZALRQeuW4GUteClTXrgTbu8qZEvlctE6oNOLtXADoPUSWcg/ZaSUalhjXtLCeswLo2zK5BiC8WUFVn2tajblzbaw6Epzb0q874KXD02Ta3wkDENbkWKfkW2f58VAXxf8Rp40hFYIWzravS7JQqQyKpHJHXriW17kJADb2FGsFv4ByKvT3Nc4yTT7MB1FXQGh6YebMxIz1tq7Rkd+nktP/D5zmv56U0zMZV17QeuQmFDYlunkWWLANWNk+rziMP0iOi19KCoTHMduHrQmlrgsw7cM6oaYurwQjam9KZqVPLp7CMQzm5gOqvIi1JL2UImmljSwkQD49iXHPP4Fu61ZnMmA2kmToQF2YB5nlfHBBKumTdi4wu8xns4CkgdQ0MVYBnJbEKWFAgXCAyjF8PoNRe8Tsqn48f0Psnv0AWZpuk9XWDzYKyksfVNdeEnmQpTonkzXpCa04vCcUnZ/mFNQx7acpBcpRhU4xHOTi75SeRr/kljgGimOpqRGqqCBIRfd9bR09QKdErAv/McTFjwyeXrEwwrPw20epjqShAZVY9Fa+ioaWDm65twy6bLwBa1XAdruiYcjapLXyBmenHNv76tpSsPazVIroqBhtaU45EC9BJeHJ+JI7k30G0Zkq3kyiYsCeZkR7Yd8L6/esp87+Wwr0Obb/fB0OphqispbzEJL1ubrujAyGfebfAvs1+flMVaRzoduMbDqa4FpwUQ3pPX3rdVo16WdFeFA+EYYOuxTXUiE41BPDPJTtfMI/FGO1EDrR6buhJENQ5aTzQN4gXMweNHF8ppoNUDVFeCb/ZYU06DeDHKvUObrOjcon1H1HQDYE2Dk6uoaTbB2pANwAxZK9rZ8HkEMkm/n+bEXky/W6+rxjrqsZqqgs9nwnX0RDU2NPdLo4r2NYe8pirug9Uc+UYlatwD4N9ed/XpoWqCc1SOZb+9pkpjnfQffntNQFK0qZsk+1SsUFYNBZ+S9oS6mmr2X15cbpKUng/+j8uXLx7XWV79/vK+rjd/e/26alFXr9Y4LYuquK1fpcX6dbIqXr/95Zf/eP3mzet1h+N1ynlhfxN6O7bU7cGEUvrs0Qqd4LKq3yV1cpNUZF6OVmsJ7HKdlPXZzT9RWreny48CA/w2EnlosE/V092BlyeRQtOzjAGc/u59O7Sp1p30ivbpFXjFf6LhCRkW1VPtCBEz14p6pOZlmmRJSXhhg8r6aTASVmTkRdas8+lvkfnUtS+fqhqt6W8eC/vdHttp1dXrsyBw3eKLHHDmadasEBEYTKonGwGtVOrS2/Okqn4U5YoU1IRDkEhKCMAe/1CZRzp9tcd0hetMmKD+k8NM35MlD0DEfneZlbosxKloP9njOCxWTzyK7os9hk+oTv4TPf3ozhpYTHyJG8Z3aNTJMlKu0A0vQHzmsz2uj3hNmH11VQy+bhajVGiP9wLlK1QeVN/wql1/WLRimT3WrsY/ilwYOvvdFdu3Mtn0wUIQUq7YFTeRhx/ATEmFrngPCxrCIaoYscxBu5S4KMmaIWiX8aujdrlK7gAF03510DFNu9JeFQdpJmgZrsRJRzc3Ga7uAd08Fcj4fnstLLPiSv5aWsoFw0o0DOzMhuRR+3iwg/UwYpLOxmxsCF3teSwJ2YZwtR7e4WqTJU99LBeLiS/ZmdkmH2gcYthE90g8JllZc1cnuI0e5FH0nxxUDCWCOJDx486wxseCdB7/C6367oeqAxGfj1KwwDEP53R9EHFMXx1Mnz7HlIiL/e6AjRIEETOxz+LHYRTKPLAqELrjAuSGK9gdrh9TgAXxuiK3mQ2LK6vuqk4cenzUZPQBP5itx0J7vF9y/EeDLlFBHSU8VqHIHudJltydrkl/aEyCPHSg2GHzUQsWY/th+5sihfmpNT5/GgOn0zKXddnetqha/2eEdUzA6LuUGdHMI/Nx16Ch97I48SXuGIFVQyhy2YbRo+LzrLnDubgPY0tcMF4VTQps68bPOyMF56hc46rCUya+EAkQsXlwvxnFrq52cd3N01PbLK7p685wkCn3pj336IK1LThHX31XueakRGi40SyYHFyJg8sreTx+ROuN4D5kPjvh6pfv7g6PgJArs8fa3h8RsA3f3A9kulsA0HlMVzK3BG9LcxdZFqitCQYfDQ1Wew72SCwd3x++QEwyFm3HCqc+/LP8A1GD521gs3AYxpU5yGuWFT/et5ksrgqaOlwQXbl4iX2D2otG2JwwOPpSC+e0fImLj2cF4mO/L7eb26K+GS6bh2od+JK9pe5RVZ5HA9EWRQzDtyU1z+dmfYPKs9uvXfo9DhVf9BPv2aVkCqGMyCZb8GRHPYr5mLITA4g1p5L4E2egMdnikt9nt/9NZdv3M/ffA+z74fR7IVoPzYpY2O8ORutmvMrLdWn67GIAH2w2ZfEg+xmm7w7jLBFZy1ZnubTM8SUObtrNSoGRL1mcS0XmPMyKu/6VCw++1NaeiSe75q5oyB8/VWyBQ7QSGQJ960XsFft967N0rnvezUZZ6+vPpKm7RiU1PX1eNlKuG73MOOx3B2xJLbkthm/2WPq38f4Lke1DnQhHJVKhM97PhRrtWLZb3M28FhjK6FpUs/J8176C86dCh1izpOpHI8SZMd+3Po/cE1sek2eoP8+MjY2KaLgCh2AYyUegcgssPDvMe1Yec6OtPe9KL6t+vmR7tsPw9Jc4Tvb7zm0g4xxUBGxilt69vG+wYv/SlThY9RV9jEt0Z0xfHdxq3V13zqPWfdrGPYShDlF560Q22KRSd8yXSVbDWLsSB4fsao3zQRPxvliuxOnIGj424gocejhkhxIJyRUsfWT0DmVIugkzfnQ/ehoTXUCnT2Phto6QPyZk3Yb9DULRNr0EtCsfizucgy52udQN85CqTolcAnCYrSRr6kS+SsN+X3Z3114alNmH+exGPRnV9NWhV02WAZ0avzrZLpskF2Miho/uq2LnOYfXxaHMwYuCy/qe8pHgRJk+74wdNOXlC7GDFAkHLcwgZc15rKC9pNphimGRRZNRuniWUmQ989npSLhG5NsDzlPggoVQ6NBH6Y7akeP9tF4Q3ohW3PDVGdNbENNbF0z/wBu65U8yObxaKHKwge+LHEHqlitwkJ/kEcLGfF7CptnWLraVgdBrN70k+WxiVTXn0d6ybnPVa22EStU/jQwEr0xFrjjh0D2xzGFt+VF8RHWNytMKuN0glzpgvi8R0uEGyp3CE1CJUxCzWOagt4ebtV8TYZPFlzy3Kw/KlXymix79KtA5L8AFYijaGR3HrcqBt1BZVD53UfX153L8xjN/IijQm5sSPWDAhOZLnpsgbom5h3P1ML4esHjGLMBV5+HmuHG8MTZg4Qla2kW+d7VWwPo/FTng7AN3+rpHsncRhnDQBUVtbkQJ5HJz4E4gSvfl54nj3SfoiWmiPLtN2PmQti5EhZ/DifssNLiy5jwKvG+OjkQR0TEUuZyLlWRkbQ6CNm8DGCilgHGIRcAVvsnQab7CD3jVJFkm6H0QYNFLJfdtck+F3Mulbp53JWKpcJvnjwMToTWx1+RTQ6B425dihjpq4xOG2C8wBt5qbafhLmcbJgZaVyKEu5HVRSpeNmvYwmKKvcwrBXoYwr33beAiTB8QwmsM6kaUQB7enWgZ1eYyTi6/N0IH6QcH+Ujy5jZJaZ6TkqxoNeS6VsHYt/K+FhMYdF9c4hre49v6KBHjadjvDv3p60BGg1jmEsH8R4NLdFbfo3K0wYRYZgjCuYXJ3IDxc+UO8tsQvUUEP6WGxsFqJWATZdkI7TK7w7s64uxO3x2cLn0dcWbZ7w5RZXnWiSfz9A8XXwaUu8jf43BJToEfhnCnxvHjBpetM+xd8lTBlBFh3Ftpg1ZaDJBsqaEcrJukukyIsYVglgGKXU7j2ZrScaxU6tRrGoh4cFciJNumcqlb1ONYUY6dBYpd5HJ8i1UUTKbARX/1lY6e0gx9RPldfS9qMAjCt4VzVOJCmkcVjEcrrYHRopE0MQThFMd3jzfHeUL2f5JS5IpccKpTeohlTic4mEpykg21j+5pKn3pMEcBtb1I0NPquJJo235ycSaOKV9FNhOKnGwy6njOH4jEkspkr3IvM7ESyMWNWaTf/94kretG9GNyRc4HHm39g4cEZ8kNziT0aii/luBBwBAO84BzDXa51GE3UPzoxt6HeEqHD0C50y4J3z61/o6Tohz6d4jI3lTaKakBHQ4skvR7m0qbvmMhXdQUCx332+q3PqSNt/2zIOo2W1cImVq8btbwvMMQri0kj6YWRAj7FoY6lzUS0qvyJa4Y2xc+yiKTEy5B5Q62EV6hoWc9CsE8ggAc+QitegxYXKqBYictRNfhw+bpsKlr0XEllzpj/oar+wxXtQa9COJAmU73ZoiI/znZl8qeQhjC4fCE7A7bqjgVb4lxJS7+WAmVM46zbAWgmb46e4eP6Ik15BfuChzW5A1KcZIBveNL/DCOx5NXeA0cXmoh/VrsDzCN7Ylw7hHzx3mNygpiNAjAyQqgipjDgiD20QI6eQQs29MBOu1MrzDq5FDQjEKRk32Dqvqgrkt809ToqFjf4Lzd7wPjMAI7jYXoxO453IPNJsPi3gkEsMf/DeG7e/Hxk/6bA3WAfa/7TvcbXolI+k8O9ALG88F5POMaoVcvGjCPtnSKRQm0zQjKqGFlkW8LDkf2op+BL3RYAeImnN2h/D+aGcEPqHyirCZ5PYUyd0v+S46l+AOxzHXFrK6SEt/eqi+aCQDOAaJnt2clvsO5IlCULXbZa1aoNxgA15hc6oH5E0qqpkSUrgrsHIRHCwdrObBNKvTAS39ocbMADvibfJWh9rhc9i9Lha54z1FJkzHAbkkFiGcblAb6JkYI71EUnXOTrHDakbBgLrYhzs9xexIr+wO5Iieb6bxsj8j76pLFJBbvTKDbaMoFRboNWDxC3dRV54l1k1dU17V06DF4OieUOZ1BEc5OCYmkMCahyL2nKsRQuTt2SF2KZT9PYHB/Rl+RJXhT5PJ9KKjcad0HsfphG2ahDY5pt4MqfuUhHGNyulNoYplBMTls4c5ovUhPHQa8c/j8HjmMl+hBGaToGaB4WNTE1lZiBYpdDMPVHWSyTZ8dcV3WT2IkJfvdxSWPE8kN335ycS13XKiKzIXK9yGuelxdULvqRFsudcAMm61eJisNXb7E/xJPAcavniG91VVxiTKU1jB+E6x7/8+go0yp0PFo5CLJ78TFjCvYenj6zE7a5xO8u4suwfiOz+fhvrtNmqz+Sra5nyT7VSrcGVOw156BN736fb27HaisOY8Z2Dd3iPNEfH1MKHI53VsjOZ5h+vocz2MuUUHfAs0le5crcDkl+IyEyKH+k1PsXpnkFZZCX7mCbaqAT2iFE8rhwGVwsWxnFADbsTAtwGLyUAX66vPoA9pvQVO3X3ZmdvqTszhamsPlfy13aZ0d8ZWBmFuIZ5eYg72EFSjpDCYfSddW31UHUGy3DUsFlQNCBbN3Quhx7V0Fz89VsN8i/+xb5Fhumy2fFfdHXF3+oxjHxgzCgBNkLZaZzOe+adWxmh82ZiAXTYZUkWAW4E4Bx/BJLlfgcNbR5dxUJLqTS108qv3lNxg1UOxyuFvVRHm3ipZ9cVy+/qiG82ntXHkZHYLwaiF5ogzS3XFTtyJA+bQ0TAC43GrA/GapVQa66RkBHAIGHusykXfGzOfdUclMXGSgLmYw+ShhbfVd3dKQukX5AT1+TbJGirjgipxNm48FKZQ1Nlu0TXPptOpd8qIrcfy8Mzzeq74u+o+G/kXxAk3o/B1BOhy77wvqwylhjFKhe8Q1HGvtYxDBds/uRMKFK6IZs8ThrEbleDVIWI/lUofdDF6hq/tmfZNL70oIRfY4+3R9PLbx45b2vD/1XnVXlPrIgx1LRtbxAvYYKt+Icp4VYOoArLLVrzIqvXLDQKTX3NgCD3yt6aREOpa6GC3nJeocgXJGF65o1/g8UsApj80n7tSIYfftFlVonV9Q3Wl1QlRuM2VdE/lKKv6JT8j6McY7KAMQ+jPtNo7NYjIu23/dQdeegUMZuE2LcIvT9vIBY91GYGUYtT9T2+LbffaGR9L59yQvrQHW8UyxM8Hgl5qYMtew3SHeQxG5yxX/xEKlm6zAx3s0mH3e8nFCN49EwX2w4//lX04Lv0ISf3fc3ZYHXLJcwY5LxxxyEU0ino+Dfjc5dM7VYZ717CRJUX1ZlLWEky9xxDiEZH3AopMWKHYwafMVeuzUNv0gMIBcujO6oJ/z9jGkCLYmweNvWIKVty3n252Xr0mJkxzMkxVlvjT4/efRCelMNk3gCwnhbxcs88ZCjLyIP1tmsueaceegqvBdjlZjyKtoRgDlTnGQzyZX1WnV5kAWGHv6uh1vwWQp/6+1cHooFDnoqRmycbc211lTn922KFpbEcp+K4PszOrHsk7YOsdi8ljR9NW3bZuoZT3uvYd9gNAy1t0sJl08O+45unj7Skq3FlRuj53mUyGfpLcp2O8uDDw8pyRy8PTdY7liErQrj7YFmJ/YNSxOeBSZiyBsi0tZDEfV7i1V8zij4i9Wu+2MUmE+ftwU5SBQAl6xbGclvj8GInBxZX/CG0EL6JDtqtkZZ63dLS6ZgUOiccfC9hjAm6YZ5iGf29oU80pyezkDuK/h7Ow7WP2zqWr5NUGp0MF117rYVIjl0mXCH5dbi9vzWcg85gocHKo4/658QV4qnPOawm5tb1tyzrLH7U7Yo210Fehm1a5iHxSqFQLb69W9Xt3r1T+BXp2egg7RoeMbye76Ul11Ht04tPe+weKRFVficO2oGt9y/lIKpzhimXs/JZSB+KCsh2KZi67Ma9Rl+hc1JlPgdFEPyDfok2zw+JG0X0k+IOazi27cJy40JS5UzkMr6iK+6asrJtlWYL+7cdpXVMocwhU4SMI9zXWUFYKDkPm8MzqfeYsvROmPaDy0vqbu7h8Agek2PFJs6JKr+CdUiXfzPEYk0FGSpU3WBmp1CVXE62pS8c6IyUlRNuvzogr06Y5oPMREU3ceMbkqNjgVUYwf3VlbxdbuZ6LyaajT6nt+sFqVqBIPLqbPDmPboYvrStH1uPq/TTFrWSyCnLV4fAVNUXkeSWtbFFGMH7cmaZQE0P6aK3BYKruXEoSlcvjodB2m08TiXZj+q4MHAaMfApr+k4PXIKlq2rDkMGC+u2NTzSRU7o69zUkF4u1K9vpve/ovCVd8vjpvUXX3viyaDajzxpLnHCjyeVzHRC01fN6GwqNiDpp1XMFe+VnyzD4P0kwmYKsCIpiALR5fdaioPI9O3D0N9nNzt/oQaJ6c4/4PiG1JDj8TOzg8t+SAxUMG1VXnkcCPSX7XAO5u9rvD5kl+t+DK9c2CNsuhwCzdJwdbq8kEfuu+zGs+q72lu/2WcffGQrHuMrnL0b1TkQPOzaYsHtCqr3skX+yDIRzWjqI2N6IE2r5ujL8u/NxvnGxpRaDHpWWeZAdNfU8a7XNoXKC0pWXIKqHD7LFyuKGbZzWJ64o7XksZZftPbofglCqnK0qTWyxadVC5O/beNjQ1AoDZt3VGJ/aq+I4EMWS/O2I7SFNUVSqcXKnTOeMDJjay6r0KqHxnpL2/tBsi1t0GwV1+FfXmEdS2MSh7AlfgiE8OKGI+b+9wOY4y6t8q6U/LRJxyqYvB2z2BoUANFDvOC7Ey60bCKxS59xdGK5c6qI7u3REYsVTojLc7X1fqJRWQO8cdNWWJ8vTpSHoAGYZwaaGrd0EMRBEzW+Le56vksV+eoMMvNZRL5CqYr4X57MrXzU1d1GTtztOMdAxibxHCs4XjR1MLI4R7C1e0/vhUlG4sMGRgi9qxwZCuLfYqQTM2EcKzBc1YRAjPFkhdWfZgCE/9RPQ8ppZ7kp0gBJLMAjxG2yAxLcBjtA2S2QLcwVXVVRE2ytNXR/6Auc6H0+DXToQiJzcDIdQhKcnFUDahyHXEVDVckO6spDvhULkPdhVWF2wX6JZ0Aa2g3FJimQvWH0m5Oi9wXlffUIkIN4quQgWISzwtSr8XzXQtSemJ0UMGtCgnZVKAuNsbKsczVO7gdry9xRkGXhDmCjz2EBvFHmLj7GilOxkiEZ3wHREWgYwiPaRL8Gm5Aq4mjF/dMMlG8/TVERMwZr8Rfkqq72ilp6YKxq3PRw8Pb+Ued1/dMB0/bnDZhfwWuZjgDwTwxf9fKAGoLJb7cfA7XKK0fodusOjZVwG5HMOO1Q7Sds37UGTAkawKKqQliIPUUF4tHSb5d3lzCAJ445eFFQTww396pEZNy7yw9m+1KjGP5V7YT28SMURALHRfF1qbpD9ZglcIHsJB0hpi5Zb4X62YtvejkhR6QkEHF96azKR6yPAWL1AlZZszwbpoxw29Dq2hJwwR0gI0IjWUU3DFaOVpBqQBczkOL9P7pEJKvzEI4LITxPCJNlfg7qOEbtyIZe5Y6RaRiPSmqZl7Oyq/onUll6CnDEl31MePP1doEruNukDrBOfSdlMBYt/Gh4TeZe3dC5+LenwqgW9HA+ag99IUbeqre0x6nJDP7YnmhyRfnT1ImwA96M4cmg1uiS8V2a99wFUd/sIdgNLnmTs7NPMcsY1+WlFOme+OThHwCMdxefIW+S0x13t82+7ZIjIXgNKHuezQzMNcQ9siFva7g9au0Oobru9BJpMK3fACD/gwn/8EjBuHVwP4c7EL5r0JyHAL8DytGsqd+6GTSrHMYWUGPMTunuHTauhBm1g/AXIJAQDuYyeb4Q20P4PKXaytFG9oIg7ZjhWKPHAC8VximYMtjnK605DNbea7Kzagg1yBS4BjVUkPQo0fXbhpIntrcEJprCWAn1irjhojQpCWZ4i+pu6MwVq0QUWA1VS0vD15HjGrSLy8HfRliTZRB3h+Lpd6YAZPx+VSF0qq+uvbV3U/ffsIHst7HL4Pe6BueQUHrQDxbQMkgwLEwWQwHs2GHsnO8T7OkOwMuC8hFLksVENVpdkDALjcwkhRPqWSkxMVSsUOfSf68xvwShb73SFQtMlXGaKrjBAiynx31q9HNAMOpGG7AifX4QwvVvUxgSwCSdJkgN0yKoj5FOhXGNH4GhVw3TmNinADoO0zh6T74sw8V8VwZAayDlv8E9u3rC87jrdLxujj7LLCMg+nxo3j74Yg6NL+mysWMKpOKHLzlkE+CPb78jvQZydB9Lytu64aIjUDFp/HtpVVd1uTX5VJ+p0MDDreFcscsNKATci64gocz2ARfFgslrmbRSBaqfBPID3hLhYWU4AULeloGdsEIgaG7x5uG1g2nb3ezyaj+rcky1Adx3phcfnYLYb6M/HRTt5gjLVOxDkn6WqAFpRQ5InznN5uJCTX4J5Athkzc4GSSnQbDd+Wt/cOVmucgxGNfMnOaJsLVDdlTh/zRKGJijlUXpskbf3dVjaxl6u4yive2UAnWidF2c0WJHdMoQvedtpR69+U5Vko9MarjrDTArrPG5wQSC514dTk9rZzswnMOn1fQlGpKc2IL3wJXQHi0ga9cnZVdHaJiJwve56BnFvbuBTtn0dEpcfYvPDYvDYwJhS7vR6cJ2W//ZIzMbAlricjEEa+xGXDNtEYCgaCyrfmeIx4Uj7HqeAwKsLCqKSPfklPX8AQ21jD9/oOuIgbpPAkdB4azwLHPCqP/iucQSVu8V/0NQ3yab0BXtkYvrtFbf3R4BIK1hq+O3o8k5sM9aoCxq2Gcun3VfJ4/IgkMnAFTuEiR0R27opSev5KKPJQffR9tbLIILWvgnE+k4yYBvi0auMekEjY4atLKERYQspdUVgRXq6FccZQXYu+Viu1LlkcEMCS+nH/mmuYVtiabyxtSnoNv7+0FiuiAMLqF1VgiWkeuRObl3f7cvn+ypYz18Xltghstphef0oz9BHld1KOD7bAEd85KnEhBUAKRY7n821tMRUWW+Dk5Iuc7Dye5RTr3vRpjmucZKCAi2U/sZy3E0AKPhZ3YSLOIPKQbm3teQSbaRLcf8jF2/I6wblJ3H3jz5k5KeZoDNrmzQpiUhjDTM7gNr9En/pfUMd80ZK7ieOcOiqE/owfd4aFgvWanz5bUI+Rpj6iB5RJ1ymY707e+LIG4xD4EnuM9NUWECFX4LBwb+BHcDc+j+DGPR0gI/lSCr788aPT+SIqS1RKuLiCbXraCW/didvn4Zs9lg91vYGS+7DfnUKvgavE09edUUljGsvA9WxA47OSqevOZGzBefG98uHH2gFAOxL1fmSLsbxToqvwaF4maZbPcbgewa6eDO3+A4Ondzkh9dF9Ut6Je3ih6Kc/YzhIsxgvM41ovAx9Zd15WLxrW8QxfXXFJIsM+93dProoMmWS56HMxRl0uhKfDRu+7QwbEhsrBhuOaDzYUFP3z8WGl1kjpAvsvmzl5Nbrsc1t5a9COSpxGim+RMTmk8/KiGLXOfs/0VP3vDKHafrqhElC4lIfyMrmnJHNNdBwS3x8dY/W6GtSYupeCmNiDpUHBxvqz8O+baPC0U73aUkz+k/EcL1VHLTbam8leWyy4Hq7ureSnFSOzqnLKgNdyex3B2z0lFh2wjGfHRyXRZki0g3y/0GWUT+esIcCARzcX0Ul3szoPzk54opznNI85ID/ly3a5u72Q73ODouVtP6y312Ow/KaCM5wo/szqn8U5XfxdAyGcYqWIgL91Erj8C6fHPcNwzi3cvyY3hP7EbX5xfWNqUB3RnX2nQoNUBrG5hMIqKy6q0pU99ik3yOTcoo3j/xuVNg/FqQQzgQ1FLn6E4i+XCd1jcUc8HLpcs4upYA2Nxmu7sXVifm8TcW6S3colKMuaDb/4/YJOWFehCIH7qZP0k1pREEDQgXj2MrnZv0OpUT1ZhWAnyv16X8boWXoPw/j3co7lBdrnCdEBrQtcXDerV00otYAAXZm2WpVQ/8pguHfg/na/8rqu+4eiez4eya7xZSsBISdr5Lqe5zwbBmjjyFkhWUejmKbliZQKHO6lNXk8uMxzGd7XJ+S9B7nSGZVrsAxaBZcOfkShw0ozlsbA0ApFDn0sklThFZwP4UyB6kvS3FR6T+5bIWKOxoMcI7Irlq+niIUuuMFY5Ckwp3UKfF0SaASeT47qoMMJ4LB1n9ysa6L/PhxQ/lDfq5XKHPw5UpPubo+4+oaeqlZXTdnOSC7XIHDrKHHmihiSa+w3110/Qe8WqFcVPXDVwfLtMmJ3ujVumCT8kU7I/3cM+lh4s+h8pB/Q/15FADXqOpVPiWQW3CqMjpIKty1Dd4zjfD5e4MatGrfYzmoayJ74RfMQJQezG6JZx6mZxoXEQlFbjsoYthQV53M4FKhi4CK9+G6Ly7WrRxTNHxz8C5JOfpds/OH2xqf8BrJq/r01QETWuGknxWRNmLZLopzNCEOE93FVqkSk62+mAxn+uogDGWxFkSh/eJgVhaCUVm4XTnYZE8iivGjg8ksvCl+5PSO+GEqWI7tB5fN7c0/USpsHcePDv0oVsKcdl+26Zi/JDx/1fqVhJPt8bMbLmj/P3122Iy0a2UKPpMqljn1cPUpyZsky56kTjIlO6ME2aGGaUEWk4ca1FefyX0sv7nn/NpefwQmr8ZcgVt0hRxc4aTdi1L0QrVf3K4k5dKApq8u5lZVydc/p6+uzoPLSpyv6bPT+N6h26TJaqLVyKacXpqvpMFCIDsjt0fJepPguzwwWKHH4hOsoKy6q661n3mVfaY76t59fYXWRFWGhnkLyDx42ohhV1l7N4zoT8UKZV3CA34HyHx3ubBQ1V3NEgnkEYqcDPXOzujub4odBYqfo3qJd2lkHiN5rpgnf+NbeRoz+tLeQG4foNgH91s97rchuH/V4/5VjXtLS8Jn9KP6iOoalUR2Rh922MoA4/RYIGwRzbROgK3LDzPr4JbdHLldVV/QKREpT/m3oqQvk6nu1gHFOyNnJ0XZrOOJmITOQ7oscMwjWHpxChGiuIlaWgJJZw/DRxdzcYNTEc/4cQnB3NbxdNm+/tzb+YHn0xwunwNqA4KZeD2S6qN+fhV3i2UunKnCyZcsb5bTbOxCj9ovTkGAKBHnrv/mgqVPGXP4dNDU92IIDFDshfsCpXiDpXg2GOIn1hifUFI1JeqyQYf6JxhUXt4Jbf1d9U3MkQbwgp77S9eEsJO34tkl1O7n/x0mTVbBBpuIzZ8hNSj2PPmT8+TpelOU9JWSWxya4IBD5cGNhvq7yoonRbaCEgCy392Ck6CswOx310sxED6+ZPkQ4G6yLxB106/kWCqg2EE5fMdClpTui4OtmHwXQ6XbLw4nlO2lxbNcdGSy3+2xEW12glG2on8J7lahyJ3Tjor8Ft81JRCmpwBx4JbHukzk2WU+O1ieLYLhfhpvevJFLgcnVZPVp/mtdHYyfXe+ekT6oLl8xJTuzCJw+ZSncW4gToh8Lnzoas+j/o+j3T+8LJoyRVJqJeaza6/kJYX97iI4eU12ujI6rsB1pB+S6h4aavfd1cF2KmWZnz674rqsS8UN/6HEFeNhUWQQvu67i9Wap/AFOLZgZ9TC8SNdhN+hTVZEeIpFxOYTYmZEMY+W6G1SOTHK+HlJgzOWCRZ38ZtmBTI35dLnHcwWmnyKZhq/KpO8WuP2Hh9EMxVMWCvmNlyNyG7DLd/LEMucXL7d7kly+g6fXSMg4HAR/1iRtibomeZLth2pQXkbP6BPUnofrsBJFqUIzuHbjq1bUXwaHCrvFWvv02jJS9RATZS2rCW4QlcnoNxLvxuZ5M8HTEanvPQJlLuczPSqsGcF4VBGKFzeGOgYVemPAYqX3BdrOIswjWTtM5+d5p/qbMkJwn5356bOdSITFCrflul2dntbIWEZG745BgUCoYBOoZNJnd5f4n8J8sF8dpgBIqptRlOe7uPXbS/NR8V6057E6iwUJZDrie0/8OagTO+lYC651AFzhpJcTEg9ftwZc+AwSb+f5mTW0+/xwqUUSD1MBGtMux1Oct69HA/skYfP2wq6en4hC0SAbpM2gW4Z6WYFgNHnvNgKza4atV8x+iHvUqevP/Ep7xGZqLuifIrDTSI2r8tnJhR7Lto5Lup1eRwmEpB5hV8aMOxZaOdYqN9B9lMX+rY7i8vrZXc9gtkOEzqL6I3CUnrjhe2tAtvbn5qfjsqiqi5RlkXhKBGbz8JmRPHzctXcPHBQVUWK2yAUeW1CZX+E0b1cds2+Ekf2y5qFyFBTWnYEeBYc4L6V7JbQNHfdHaIDjGfFMxJyiIkomcdeBXf4ij4PCUmKVYdZXI6d/e01yA/2LDO0fc24VXTPQsjQ8vsPHczwILSZuDLWQA4YEUaYeaBzYbPd49naRB/ijPqZx/e6LWZbrKKacpe55nEG0lRAFnPaBdRhjOncsaMiX2E6ny9Oq89Nlv3+8jbJKtEzbBp9MPMQw6d17l4fbDYZpvfa+70r1usLfT2RjQboYV9swU66BgLnakQdgZu03QxcPHpiLa1P5CGxjjFHrhCqqhiDBfNiDq6dneYPvqdhLMLi2j6bjJsUJw6ZaqmYQ7HXsKL2gH2nWWLsZBg39GiWZoSh2cFhldzpNyQQuML5NcHYbD1kxKH7DS+CWncuymwTTFszOK03oaoaKnPTcdMJo9/FrcbPsrm8QD+ScnVe4Lyu+hdwrr9UaPUN1/e9E03n2TRWln2ZUhULvjA2FDgDPK4IfGLu8C7uUkxkiKdwLu9x+91liyvVibHHFZAG8pGILabGEXHvIgOZx29mocG7Sm/9JDinj4/wIKP7tv8y/l0NH/ok8G3mvmqqRyP41klLkGqTpJS4BOIEl1VNOe0mqVAH8vLFeR/2NgRp9tfG/siOsi4TxQBADHd8i6r6qviO8t9fvv3lzduXL9qHdGgyuOz25YvHdZZXf0vbaUzyvKjbof/+8r6uN397/bpqW6xerXFaFlVxW79Ki/XrZFW8Jrh+ff3mzWu0Wr8Wq/dorbD88h8DlqpacanfmUOHnk3a3DwvX4jN/e00X6HH31/+Xy/+b57hfvtPJHHKwEEX6PaFitl+ey1W/A1gWNqx319iSu9W1t8jwg7tUVgXhEuhUDuEly8oT9Jo0pEvX2vRs9GxXTP5Q1Km90n539bJ439n8dWl/IK71Ns+MranX4fxhgYVOvbrNE+zZoVO80tM0CWbIFzVcGWEFNQordEqBN10/yQCwa5wncUh/eV9UdYgupcvPiWPH1F+V9///vKvvzjPaV6XhRbn27/+1RVpl2IzwrA/oTrpk0dU0RByj/lEwhlvpqWMnf68fIGIQisPqm94RZf7AEwdhn8UeZwxdui+lcmmf6Fd0Td7XEQ+fnBz4D/Kw4IahoFaZHz8g9Hujjja4VBfQhT90d2BuioO0ixQ205vXtuiYY+a9Qtz8jj63P8EyzO0MHOq9y+//OKMlI8NsWU/6yki1mz3KPt+eogd7D49X5OsMStRO+tuiAuPPsn0KbAM/wuN+6Q/w3RPeSaYRtTDppX+9uL0f11LxLqm90Toq37/9qKVwr+9eEOI5NodNs1t9A79xadDFC1NnvG+LJqNLBhxevYr7VmgChx7Olcn30brpLc6sJflno/+DCJs1NhvfCaqJ+BRk9GTS8OK4Iz+S47/aNAlKo7a29865K5G30mW3J2uSdeHa72R944XdZAtGXGn42GULm45dQLfZb25QFXn2PwTCGXAQjbWHDXvLx4L10DsWey5AXlEu+60ohmfz7PmDucKfrbz1l0VTaqWCRmHNSuLUah/Bja28qIGemV1fjcr1NPuOQixNSNwJ99/UiYInrSTEqHhTClk/bpKHo8f0XoT5OkjSPp1sMs+BC6DNupneJgkxNfUSUrHXP54/OQtRD0WWfZnkIatr+yxdfKYpzqCxzWKSUrd2mf5h4Jm5LkLEoKDLCt+vG9QVROz4GtRByHzs5QhD1ZS0lNp1F757/DQjLw1pvPqRu/jfBUJk/e+xElBHOTVD6T1SPwsaoKO1l1FdLV2RD18btY3qDy7pYJThXD8zHvMMSJxOFb7+bmLTUPixmFTzSAuO91w8V12e0u7DdzBZlMWD2FLCJ9WRa0Z7ZxVbcJ0L2TOPPxnYt7uIbSuoaZ1COIW5S2mdHCdpDEZq9bb58yP/XuFcZGq4m1i4T0pynVSu5ySqXFdJlkdu58HqzXOj4r1momCCAzSirIPPLi9xRkmXB5GuvBdYP9CFodCpxh8d5l92ri5NpraLoexEH2/WbMIOayE1xwqoWdv3HumX3tcOjZi8jpJreqPxR3OY+0PCL6Wr4nGV6J0pfqA0GN8RC01Q8Bs1PMdc7yhB0+0kchmZ8xb56BDSsU58J4QaFeZlud4wCKeHLvPCj2NTnK/s+1JnjokQm88Qj0HjN0OKbBTHK5w9XOIy/qeSmiYeI5oeNm064F0I95tjyIhUG1V7IKI5YsLbt2RMdj3x3oDMK5kP7/9r1bdW443V2ppv5hmhWr2QgZuQ7wwKTSpJy5iPJYe4WNjxSDJJujROb2tk6ce7nihekhPjpio5yCC9lrgTUxkb6Mg+wfenBdVnWRQxI7fWcF9kSN4BfWT3uQxIrYAD5K906eTgj+DzjfaqD5Bau3xTtVbC8HnRFWU4+gfxUdEiXVazRDbdnVfImSP/1dX/ER+UIlTAbfXIddw9eJrEuRd2GKMW8QzMqVu7hx1HgrL/uoCu8r9GTTNfDbJFhXdzU2JHrDZ3eGxX3wOUaSHWXFHrY8/A/9uPb7Ebj9lhcrqSqz9Ct2fGIQp3v50scd1xLq/fdaEz0UdG+WUnSn0/uNORprorjfrLpjs3E3n0M5GNCeW2K2c9yne/gQ6uB8qbSH0ELUkXWjvjo1Z3sMwfsUVJtCE5PgBr5oky55CGMdorvjc8mrzNsQWQ3qWEBtn9HPogXH6ByHCpjpeHOSAI9ZGaK/CObNkuA9ATHT0I4p1cpHQ++aXzTqSaRIF34Dsiuj9TBhsYP9ioYyV8SHi0nz5vYkuIsmUUpasMLX7MaT2Jq9NF97XGNiJztjgafUe39ZHSRm0Tx1whK/sF+iPBpforL5H5TmXVNc3bU2LbzIS9IqVbPTdlVVDJ6fGKTUaDlYrocmg7p9W74ofeVYkYW6EHkfY1HzJs058B3RBI/s0HCuc3Ur4vIKheyTHjxtctlLyLnlSYbSZ1gFhG0zTIgzn7g9JdZnQdz9jzCqPyeOgTqgfclJHBkYjRQ/uSoRYq89nXByiK/QYK5TxAqVNWQYeRIxIjp7SDHVqI0zfsfjOUYmLQDEdMbaLf4s2SLBO2wOc/7+9b+2NHNcR/SuD+XixOLMzZw9wcDF7gSSdTAfo7mSSdPfd/VJwVykVbbvsOrYrncyvX0l+STYl6+lHVb7MdMoSRVIURVEU2RSedtFlvh4nEiXLEmtGcQ3t4olmIW2O6GiNd1FMc06Sf+UseeSv/yTnWfponmyTFqh7iYO9zi9zJxZyOa3chISYOtSjmTyTJUaAEUP+yVXu7mmd2z8PUeUacNDk5WmKwTt7jjDpi2MOpoMjHcTRavfCiTd6P6Q/SlqrqE23aSDWP358ZSfwqzSr8TtH5EDlApbWMma57GiKWMcwbnq4k5ZudpgUtn2RmcG7w87HxJTwohdf8GoY9wXau8Nh6X6zNKZwnGwSvEE1ZhVI5zt/tKkgYuTf3iZrmTY+P7yeH4oilaW10NULtPVXnD/FOC/cAVYKK0Zk8ZHdR3AJWXmgyaGCgcJrJ1eVAMD7/ngTb8IOUJ2lLthlZaAx7vcEThRbE6J1dcSN0VwjPeCdjwsgHnZ1reQJcu2Ju0wKlOXOslipaAEqCixAlRofdUxyKHrAqFy7ThsbsRBQXpwVRYa/HQp0ke6+4YQd64IKK8G/LtiTVwV7XKj4ivD2KdzyFc9i3sF/xZuA0N+H5U2zK/nWOQ1gvwrH14UKHISjkzCgrllUA1hVTwdX9RO/qoVTWk7D54hO+P3m8JyxuaIOzcS/g0S2TyG9Umf3wtIGu1/d/fRzeXkPCjF+RtkrXfrmTkixt4sLsj7DfE5wNxZAAw+xt9OjBTZY/hBl+PFRdoXERxNbPNpjEZU3jzcZ3uLEOiSzBeBC73mUo8rmdHYRNrA+oig/ZIjOhpJ55i8emyHOdnxAmW/bqRmG/kMcyuIu4vyQbGJUJvEHHOKuyqUEf4uya6K8fDhoBYCUDT7h3T+lpc+XmDJuN1A4ucXsulfq0NI0k28zdsdeQdNHSjtIrLbDTyFKbDB2yia1as1A9zu8zzkVxDUhzjF6qKnx2oPm3f6vh7LXeZpXAHMMjK3u43Oy6+3ThH+BY+WQ60Hx9WKgniQWJMPWtpugtnCocWVwB6j/0O6tvopbRKc8HYTbhu470O88Lcj0eocabbagPWMP7b54baMR7S6ncDR4ZWFssFfrxE/86luUaO3dYVHgfi7EYUNQxw1Qd3Q5Q9Eg4nv8l0xyjUNm84f0HsVoXXQBW6R+r0HciBe0vhITskPSXZRsB24KLeTDY6C3X0/4DMNp/TxXDJxezZdbeM7OtMfoEBdfyGHyo11CCP13R/Vh9fhtt4rUc5xEbTkDwtJv7Ae7TY3MLh95IdFZbtdMNs4I4SLJAsA9SmmFn2TIKv27Taz5J/TDRb9c5w9ZlOS4G2CqsU03q3TFAXG6vFGve2eUbJ4Yf0QbHFWlvM0tGbF3gFxc/ACnoHZoBfRhbePh7egpqXLwsaf+dZ3jI/5wx4Q55IPgH0CdgjAF8el4Pz5zk/LmQPDrQHg79A/gtrxD/9uZ+NjOxE7OF+Mb1uq2aHciFU9qsi0id5qeriZNl+93hxjJg7PsUvbsUfDbzyphIpwozdRDWL9r8wHsDuUFUbhMK/JFFp2iHlqgt9Kn1VZTxQGOXqk4lI/TfAOvOSzZ4RyhV9uPE4cvX4os4s+QIbyAfPzdKei7QZP/H+YW/0Uap9l79AKW7XUFXu3vZcFnz7FwvmyH67zyW2tbnKYejjIAjEZ/nYKQTuzkqKLtbJHodHdCxdN74nXIUE95HNcYmcM0L0IDJA7DMRHz5lGS00mLvkF9eDrsviVcOn4bQFUat+lPfUd2KrPX142AlPJyCuq7Jd5WeZZ9nTRnw3fHx/o1GGbcuMG6pnHSpYsqyBG+kr1Tikqc2FKQxF7pPEFperrVS82viII8tNm2ZuZnM5XdU7uBmVh+eXZbXJgLvd/kmJtQlovgEa8Zus0e8ibRwSUaZnzp5LJIEaeE5maf0Kur0iaUPw7Tj/KsIyHkp69wDhH9oiEKdp7C2oDp9yWWASqM2BZVGHpDYP7m19eBsnyQLHgqHS9CHKX/FOT+NERLVxG7bg8+raGraI2K+zQruEFsaGdw6uic99itUAmjp9wV6A8GnjVTM+0h2p7q+jOM8zLl7Jcow1EC5mw6BY4HyM4Op0YfJ+O7Y5hZyCRd9mmvdKBbZ70yf6sQLAPHkhJ9nOU53iZkBdYBhgHSZ77l6+m4XljiXbc7o2kP/e0l3P/f+alm7S25MbPjbg7FzSMDyYgMYdDw8nAKG2yIOBbFDbGfQJNFB7EEMApPQVBdVWP9/4Zpq48RC9xrdeeKFwGnN33dsayx9uWUotkxyE9c3QHnIhhlcRqns3l7qGkykGtzyuONxQhCNJxX0lQHnNTiD5JNxZ83zXvdw3GcVr52vJk4rfqwLl/2aVZUS9QmNNt6UVbR4PfozYi0dnZMuoU6zvwpzLpc5t3niofleD080s7RlXdPj0htnhD4Jrf0fm3+h0wjX5PNf2p85scbYSDLAMJwOyi7RwXMX0MwH3Dy3VMda3OfjeshtrpLPhm92aXfWmmCjJR99HnKHdsq17R935TtiSnbkZeKTY0ImYL3eZxbhvZvK9Yev6avaf3jgJuBDgn+1wFhBvIRD0VnGz/NyZtCvJ8zpxdQABgnn0YNz2fyPOo3RGUudl9P0cCEdVbALl8Ibrkvl9BbzrujzHkHiA1TNeYY1P2qf9DfnfjRgulbOI7odMsYWVhJdKV+QRm0vFyL4jzRdEJxurVYttqbYFPO7xR2wYmDGtQJNPQMfXmyE50nakJvH6EuLrSELVV5EcXrQ8w4UaZbCWBBXqXZYXfLKrge/+J5SPd4bS5zVTe3R5mV3NpLvIdLaz8XdNe3Z5tNhnL/FRmU7+q9WvY+U6GZ1rP0dlvC1i4TzlNYvIxa8+VTdWvrXS58GdPpNvJ9iGqs7Lr6EOUF1fo0YtTNICirJpiYtZ2ymxYxcZ/KLSvX5ICFhwmjHwa1mQD3UcVfxyuICopE6iyhlVnFLM6JgwL0q7kVdiI6X0MdDR/9zTaGk9kT/sjSw95yY6j6wr5pXw9W3eskeg6J+lSZDU4qDlTCFioJMsqPXtt6iqb2IuY2PsfFpcBq2XAKenERWshCqpcrqNANozwnuk8OSOuajXBC/UTM6FPJD/ohSrYHq/uOtqfb0RCqxmDn4mG5Kf2AotVU/UDykgNyntWQy5IR6Y7GjjgFaZ/t91n6jDYVrAvFy1e9jSQtfIP0WAvCq3I/0QIs2rqcXsFnSRSfHYonqiLLlDF3aE0Ydgr6fXLX3+WOy8PrHE1Bp/KaC5zxCLYy7DxDv6GC95B+R36WDwN3tl6jPPcHlPz5jMkEOxXK0F6RleV8/EuPEQqnAbGbewpPHUc23b34ZPqlKpVSXe5ZZEnu9He7V6gqgVhj0wPgEivAehLTsTh0MbEkywesqhKLR1BllIHnKj+1dF4csgwl69cLuxLKEOAS4F1U6MZg/NN6VT5EL9W2Bly+GccvS3Ia2SszcsgvyFqIr5N1TFANFpIiDHb5Ms5gD3SwprLVSBQKg45DaaUbxqGwGmxUyshABovVfDBBjRHFj+neEMVXCIXmqXzk0AyWjxya2xV8P1W6mJwEF0SXqjO6HgkyA+dRHCUBAwNLZlEFdUeo2XBpDQIOFWwIcvQgRKBN4EqAd+hHlG1uU7Jj519RhshacXMlXjyh9ff00L5x8u3h6Q3gLZNYbdVIfNamTsfHRxxj5wrIzTFm74VG5lOl5zJasZEtywsy/6LhZTXtBArt7WciKEreLO0efY41rvPvaCNjnTOmF8/Pv3kDdvmyx1kZKZ0mbXJMj3D/C0V+aOfl8h0myq14h5gc2oskB+ZszTa492m88TRXfeAeBYEDfh4l372dDTtwvS0xHu71hW+QVWFZ32Cvv0WeNqRKQzOjoLoP8rMmDsR6zfBfbKWxJ2jRWiyOEQS8N3GTDXCHci53oaM22tNH9v6Z0wfsEWtykG5sIv+o3x5o3xz59g/fRtjXzW599BUf8LjxtAJJD19kEe4PBfdEyLPz7h2KUTfTwGmE5/DHhTu0i3Air0aglVc7oo+Oq9P6p7Roim44Xfev12hfPDxhgmlEfmZXhO+jZHPzbGLk6ucbqM7Pn3NyaHiPiRycRkW/xvlonpq76el0HcK6mo9edXN78mK/RLXl6g/8yI4YpyZXNd3mM9v2dJrczznafMXFk6V8dbo7o+KzBNK0knwK0lubX5wIWJXXlcFxubGt58H9ti6wW/I6r1FlFQ8ix7xONTBylNx7PODcEWL3NC2IN8uygegvrOkeJfQc4AvDEpw/9D6iPOcKcDnnDK9nhNmSjq7sETRjs7BPQTU2xHqNMprQCrydNu+Ij2QdtAoJS9gR+jK4GSj43e8Y1IxCSei76voYVm7moVkmjhaad8HuH/3fO9Z565wfEVznNSgvVtMHIuJJm+1PajBqbc1E638NW0TtkGxi9C4qIl9By1RDX7AkQKFWhLy+mem1BgvC4+EF8HOVDuX0NOqBTGhVlCwekGFdlcuCM9P6jmru5jDvXz4hj9fkUe4lx52CpRmEToyXlUGaBw968nnm9HtXw7t7Vo6ex5WJr0i/NPoT3pc33Me/LCfcAx6yaP0dJ1uP97csXjGsDcYuYZGvW+LanPEEboz9q14dp+LRaei1eolU9vRwBdh3sAf3pGiLxNcojlFxQsYM9OpPRyDKbqtmmS47JWGYvUPLQRHWeCrBhw7mF0a5pc8MiXRMdAfqJLw2eaTuUJS33ioLf4DEwHUuyH622eFEEuJoWJ3M4EhYHLKEFoNFp5GP2cOraU+74uRK1McFR7mYrtKsFCQ/jpVKHBFztGo4lm2A6gbuGT037mTTcbsKLqLHR+qs8gPOo3KBeMtpER+Pw8v3WQ9pad5J/GrjOxBGSBp4/5SySNuLKDudc467Tr6Nsupg6OTqL6857EKK+L4uFiYvAe6hRJPvMhPf4od7yEiWLspovbaQ9Th87NGL04G9R6inoAShGDJVWjU9Ryd6KchPu72fpzU0BOxfB5w55kOkfkzavlrhPmBe5w/Ry+UL4ii1AUOAXJDp3KZZt4KXrdqhJe6yNHZX5L5KFV/nLHzB4Aobil2wzKdorwJOprpwj3KLnbsPImyCeRvV9FYn1+9KN3D5rA8ZfQlePQw7Ied5l3TzpdWH4N2PPcvAEZHqU5CVi9d1jEoF5zQ9FMwtynAqD/HTM03oBTOD5hRJopuMejRbAnrua4pGggscxZaXQmLv2b9bZBwnP32gFU6Pfxly5AI2tMb09gAs++ZTNwXGHOWV/noKMlsmRKgSuw9dE/q3xm0KrVwmtLGBFtae/xPRU4TMD+gZxRalONLtinX9t5+u88/svdb//emKjmmVwzjNCp0L6u41jxZ0WljD65PCvU5Z198syroGVNN62Vy26HPW8zeLa/8fNhdtjyjLUBYCtlc3MRHqbT+yUn89sO7ggnB0O7wvij2cGKejmE3Z9zmH3wGbVSjW31brBIinoFytM7L7SQzv4Yhiek4yionlkz0dvzAE8UaOVmjO9Gi7TQj3LggaW7e6ReFd9mfr+HQq85TUu3l0ShhBxLm2Yu7S2Ob+QOjtpDqvie6NQ+g9YgG9SdtMpO0+Pmy9A/VyT2mRa1o/7RKdX7w+qSAFH4JISKiK4nqWGALY6sDRncgVAeThpCEkFnOsjWkaIKctxA9PaIe+RBmmoE5BghnBJqKnVdzcUKvqwATFp+sXMYYaTpKqNyTHL0FBttAhh47VtpzHxm5dAAq9+x10ZlkI+FWarRHBkfz/LI6px8zpgPM+zfuR985vcj6k2/QWr2m26nmEb70vdvF5uuH2YLdg1jQpyDqoHwN/QsWPNPvue6pvM7yLsle2EusSajYBwRAUx+BkBvLyhVCZbBFLb+2KHwzMAE39ILkK+pvWJWL5D5tTsrw8oAjbPNIoZIHASpd8SCkAA6bo35wQnbwjU8alH/cE3mMlbi+12X2p5bnE2AMUpjSR/CUr2eV/f2TFwJoHkbkHe4NB/HTYvStXjVOwU4sdi57yhV0L8R1K0h1OIiLMweJKO0PeHdo179t8/xixu4RT2E/m7lILd05bE5VJZOghyr+fUOgvT7ZF+gyht6NH/O6Q8IU9BgqcNDh8jNZPOEH071UDxCkTAgfRynGmxujfPfjQWLSqW+oIBmB1hRNmKzhNXQ0kADq/mseY3B/Wa4Q2nt6wX2ZZCsRrOJoH5M8tvYC/ReSsL02/agLJT/INK215CmpS45hlvorPYhypTU+rcKU0uXzZU5kALu2dJbeMVbVQikx4RM33T08BmRqjf0IvBVHEq6q70/ZAZGt/kwiKwcZkrlCyVZkyiiyi8q7z93hDVobTAeCQEGVW7XwB7rWF2tqnoHEEguWl5PwEryrCfNyToHg/bIaP2fnzgA5ow+qNnBUFEepTed3FEW5+GhA6O1mUlCxizVBPHi+TdhlQ+jWInXfBK2wTP1T2ctHUfKJ5Qss3nETZq9UFx6AusXkt8ZFsXrCB4AoYbXBUCYU538XeAdITc4J/CkriNsNp5ph85SpLd94N6ofUO8g7tI9fzeBqWem9gtWuEM/Xa98g7w/f/get1WnRbLQnvaf1c0vr83bhnqyGhww7PiMmQPz4HZjtsbYsdSr2dtuKUbL5GCWHKI5fA1haPKanoDvBGnbi/vgP82vV6rpueFMXYWsVS+Zq0XjD9zbNZE4vvZvAnMyCIbGa1mKeD75OtQFcHnvu89jl4EPIfoceo0NcEM3HJJC7CPSZZSja7SO8TU5hQUJrxjIaFd4u7YBp7ZHjX8GHP3pXjuwHtCMa7TSix4OcxZZjC39MN4glgvPuC/8Q5UUJPUPDi1zTyi4tlvIB5QDKFi+0Z29z+3h/ojBux9JHvgxd4FancRj+CriJzJjdwvrNI6y/G8LS1t2f0I/8A6LqkIgx51E+ARUOUu614rDk5KB1VfQjj3vIlYfEuoiEeCPmaOy6awi/J3u72jBGbPvN4mj9Nc1ota2gr/Gu0uywO7XVGGwNTp7mik2na8K8PV7PP7fibcZqCTdxLScgte7p/qlX3V5Gxd5ulQRTezT4vm6uSw1b+j8s3kfQ7OF+zuY0cXjkmOC7ysJy/np2KJ7cQlI4YHdojfeYi02ba/HejyjKDxmqS0wev5oY9BbYJM8Lm5rvjk5F6Gc/ofJCVwL2jshskp+IAfUmY+PK2PVun2a0IsUjPo10A0EE7CqNN+bJ5jRD3GMW2eLjYYwPOM4xdKXE3SHqY9/wYUVWrrPveO90JIm+I5f+5cPAm8TNcUeWxBVG8Yb+5f9FYC1BF2nyiLeHLIKCLa08spcvRRbxU+j48j0+7JLmxZsHiHcoP8TFdfLYuzmwKwBYPj0g2Dm9WWr6h3iBfv+arE/90aDOVLRsWp2/llDa6eDTnD4QwbA5AaaHbI1sEyCJ6JWw1OiJqWYt+QhvYK7s/LWPry6pQFfnDZUl2HgpwtD6mz2tQFd3w5cN8T7K1YHI/2GZlODa6QFZCeO+yHyp+xLgeZrKQl20lDyZo8COg8sXagG9Q/s4PZniLdUJw6ro5KMyen06q99H9gK/FkorU+6mvlakm03qIZ1ANwu4GgmxjGHS3OQPWZTkO8weNbpzFYLo5Hcna6N0bww+N7Hg6f3hW3m09Q3YIJTFZt4YeC0nvC3qPkJI6ErAz+gjl+zIMjbTKLrTcJ968w2N4huyScZAFz/9V0W599Wv/fbTeA/I0mdMuBLydel1XunFWn4dosbcN/pyLfnyek3niACFkAifLxue/EmVti8PVS1ope/Lm6vKs9l28/iYI6dnCSwa0QXAeVSsn+7xX07GyC1Z0WXS2TlEaF6kuz27UzYzNoxPneyq+b/x/oyAcw0+i1GUtIm4Pe7l59H6+3VCZmf9/eRistyjW27LuuhWp9ey48JrHI4RTkEk/zFiiX2ztzcYrqnuMfpheiCcw3X0BeHpNs1e3wTgRAWgUphv83+i81+dkCoxOIXpb0yEX71YKL+5QAmt3bM0z+9RHL9N7wjTq69zUVZ5o8sCXny1MZtZ6MJb9acF7MePC/QZnkpwYEODvQ/Ci+ne0GRnv1fdw9QsKIeoC/9aTHgNQneiq6Gs5lgYy46ZrnPKo2+GQdszxEwS7c3cJrURbTOVNQztNVuNZTWXwmBmnOS6uq1PHn9DcWq7hpxN/lw8xozy4y11Vns0mKHR6R5ydmtLaISJrYZa6pzy6Fs54QLNZAW9PrhGWyuTSTo3quZkMDs7aVxWShGo8bfCoewc0iJytYNNzSJn+3cGttH87d3bNI6/pLSSUlWueJpzpgYzXBcZIfQsyX/Y3BvwfUNMAs1wcJHuWPTjsfK/ou8BF/2aYJYPJEqAOo+AtTLxkkmgRaLsElSUPUMIx3mcbk9FOHzNJeXZbZpbXFG2PUPMJUsbQcETlWu1iS5iNhsqLVIftF2dUGD89R83UBlbd+gZox/vUbx/PMSJpZtoEVMpEGxtnNbdnVD5GuUVx0NP7LHP5lSBG/72/nKevG0X9M16G7FnFcheSuZ/oZwV1/AA6lNqCEkm62d5nq4xm9lqhLIwa/nU6A7l7FXUqs5+1RH+y2TzEz1+0CSOVYMKo3sUP/6t/fHjIS7wPsZrgsJ//vzrz90lc5OUOU1+OmPxjNTXmK+jTZ8dhIyNFAcAcxEfsIGI2//pDUmWMcrKNL4XaZIXWUTY3V/zOFnjfRR3+dFpqKkeKKUNyO6Xd2iPEnrHpqJbZ1w+p1l//GaYzgwM8eP3Xzih0pA1/Be7w2a4LUjQeLT7UiZ+PQ4RE2hahHz1bih5n0m+ugeWCjfN3d7CLPc/jiJ6yvtnFX5iwyAC2WPJCIKpfx8vGV/vAn4GwvoQZVvUPeS3giEVBNXEn6CQGgvI1AI64OkeSzjTOF7G5kwxFYWM/bD4LZiRsYxdt3GIr/pIm85UQCkpcexhUP8cZo/UnUUP0lIRorULkuaTyct9ERXolr4+S8hR84JegPcCcriNrvou7HH1b6PIjoCvgEfnS5gNDOJPGCESydHarErkJhOlOmjMoxD9+9/+9mtv5lpIdSggD6n5bekCAMY5znzqFULrvoZnJwzmS3REkRCQm0wwmnCN5lnr0MG/7gGeo0baZLqhxhAqgfVMTfAIUqUMrJYMqQ4ZmkiyBk7pBgriaOXKZI4nECt5wP3YUnWOY5p3An5sYCVTA9uXkd5brjRojSUyf3ppYKkYk2IFEzHTzatCGkSl+XY0m1dNkcnmNZlc1aFMy3Dq1dgKOLQ/Lt6515CyDAcfZu89Vh/RBkf0EahKfIRG/PSJH4w2qgoB0XVX/xZEGOSkhhGImhqdoXjcppOJ6mlHJRog/nYzGMoDLCAsItL5FMYTbDDDrsIk0qMzYtV2NuIEPv3qTSY0i6ciThCH5iNObWzhNFZ0/f7Uo14aOkzVL4sF47f5cek6BX44LZv+ibVJ867ybE9YTisRV+jjYWdg3VeYx/bHUZRL77U6hEtg2WpIHkG41K/zJWOq3/hOLmZD8Tsm6uKIxcxoyqcQM0UWh5HETHhyP96GJiRXEA5ywoelb2zyHBKS8ea3ufEkLGp/k4oY3OBo9jljmZvjXidK3cB2Z6VMTkD6jCVhKgkcSH0zmRRWJ81FqT3Ie9H7djTKzsRTMUc910jYgIqb3ic1vXyN6JWyEa9bVSqhUaWr/scd+tcBZ4jmO5Df+M9Jd3EIg+gI349Gh/FUmeixqf3pdWDszeNNhrc4GSFA1kANLi9A1kTZdFg/uSgQJYCfUfb6QAsTSJc530hY38KHmUuEnNTpxYLHbWqZOD8kmxjRbEVnRZHhb4cClSWLVu2XIXuHawlMMP91zHs5KWVqJHuNQ1pJMh4HlVE5rTo4tL3nI7qVrC7myth2wSzPRLeTc2E6ZyRmXgVscLd8kxL1oDORj+Z2SJJYfF4Xfh2kIck6ouu+LkVaRnvVeDZytZhtbUKhGl9ZmUXDzENVCR58Reb8eV7aAERAknaEVzYQZTrD8h1mJXuLUWkzELbxVZt5PMQ81Nv9Hq3xI16zT83ZdjnCBuMP4SVreSQCKCFvCaIIo37DygmvdOjiMxYMy4OeIITKiqKgVQPLumGYLAn2IuSaQkVFrM74MICZKld9akMLzknqZmdhm1xhqyiYWua5YjrDqUPnYkW0OINPkrivR2ItcCQZWAgTJlYEhEsvsEgyp8BknoKI6U74lFIG1yAbV9C+RBmOkqLRrRfp7htOWMPJIwIUuEGipWx+THEEKkJ1sJhTiIFK/hZzNp+9oI6/77rK6NRHdQ3x/PMQsRoanxMsl1GhES8L4oejVI9yBs1a9Hi05yZ/y9WJOiJ5tNpvSSqvc8zO71FzGhn2SvYaArM+tidSTpcSO75ZSLEc1f+oINJARGfjd+z60HXJCykdo0qxBo4jSa+hJAWRYysZbhGfWpplm/2i1e787IApFK6LQTAbbdsl4ksUHxohVVMYRi7GlVxGrg6aVcOQMmwlT2FEuaTWQJ67AKYW68kP8iO+fJroSL6cc/e79EcSp9FmumSmNQaiM7358SjSmTbk6Iw1p3ymq/tot48RjL/tJM5OSxhNz4gaQmT+ZLLwgFFG6KPFqaTV+1xLMTKZCGLdNNgLuHC/HkWdxZYencF47GYgVvN38k4jRCM6cs3kZ2rX7VWaHXYsfb/vehDybakZU4DE/br4Qg4tLSY6ZFoheEj3eD22FLBB+2JQ/XwcclASsxxBWLH//pGlh71UCrgmvcmrfh5lN2ED9lEIJDoyxgQUHq2BWrzmoEIAvM1nLKS8TKB09OdyXI3Dms/A+JCh7TJ1IUVodOvFcF5HtV8YWpMJ0Sf0I2dpNBZRf6rGVsCh/XHx9acaUnTGmrz+VFMhkT/GL6qq66CLSOUqWqBFDZJlYltPX4CxI2xD5RxsfIAnJHRjuQWdBY92mE74Ll8KlCVRfHYonijE8mHcHVqn2WYZpUBVFAh4qRsuXgMqyVuEl+Em2wQpSqzyNrExBSDVL0uvRVySoTOQyPCJZ38RKmdcoRlTiehLzeTaos54/TknJ4X3mGCTva7gVN0zzWzOYw7iIzY4mtzmAlk6406e3ByUNWCtmOuIExAxfZ0ylXwxDKfd+Jinas4S1WDZxyCgi2o02WkJWZLAzD+sYxqxGTGsw0xwpg7r+AM/FhcROdrfHrL1U5SjzVdcPElosJ/GgfjDGgsBWPtjOE0yVur7hhYtmQCnYnIREWwdmCDbKQ2kayDMQXxGMHqMJMCTtBnbPXXHecnaZ34pzNQampOojWYjWcuZMKPTmk2f0gLN386mWPYxKH9dtgy1hMzfzr5DP4i036YEQF4rp0X4JwHEBXTA74v3XUJULcKTCcmZ101wwByfj7iMpoZsZUWYlulube+f8J6Whpz1TlYjKabYbX5ctgA1dMx/G6tRZR4jGG/bWQssOT2Hg/ghTIpjk4n1JETabommw7SXtBSNvd87+oEt6u2a/ucO0ycTga9RTBb4ouxhAWUBkc6XxdvAIj2LsH5FeRrD3p1WHEYzUcxlYerjdnHIElr4HAV4dhrooM2h3DkzCV8Wr1hEehahWJq46vHNFSPZXJ7RYiR4He5PeWhOGSIXUVZwZZxDlhwfEJMuRp2zTvfj8ZQG79GmM+YMSoH3RGgRu9QcxGzMvcpKuibfrnqyNf/gnjkI1oihPlZyNXXEz8UTWn9PD90cm72f5Rqs11JQZf2v4zw5A8lSoxYyi+YAP8MIpIRCLXXX7TrhsW99yAjF29vold1pXCeYwvV0taG69hIH7pzfuh+X7Qzo0aM1JjcTs5GP2lOkpsjXPAdzHIBEKVEL65ayExC/Qmniq+r2nUw+qRg8k5n4kG5X3L/pRMo9DZ12gseh+20UgeRGlWETym+h4lkYueOJ0hmug+IsRG0RJ8/ppGrM86apOE1+1PQvP+GSy3ZF5zhEZjGicvb4iGPyC1qN9CC/GVAE1P66dOd5S4rW1jPxbf/ZOu4kA+knOJlpVpoG9c7WE4+S8kM7D4zz5hNb5PigfafzdBIjn6akxrsoe718WT9FyRbdkRVxQSx6lKxfFeJVNRBFq/5RX8swFER3ZflLIJmA6AojDyUdep5J6QTMQzTYH28yMYFMCJyfThjWT2hziNFDlH+vvT78b/IYVr6RMKHCh3HuSPpESFEK6+GRsy6Q1AGU6QzL95tM9v48oAPaXO4iHJ8VRbR+Yq7pK6ywfcwrtAQROBDzTn0psMXiC7/AdOkMTOd1ZqI2WbWoecnP2HWk7GVoFkWlOPRXJRFrdT4soZGQLVH4MMpWySEvk7dAUiZnVXAp0xqOx28OssXpKSkpbhN7EpujqRhMptu4ntMWBbg/fMvXGS5rc49ck4Yfu5/jX/y6eAd1nyYT99KERRXxc1SgjyinOSpWV1m6G09KxME7cXnip8XLR4cgLSObm4y5CMhD+iYeMxGPdiomE47r3T7NCoLaIzkPBnU1yaVEwEGA2PlyFD4ikSYD7xBOtpP6hy5fphcVAYdOqvvjExWRpsWJChkoTssAV5AG93kNVl9BRB9AiP8Y5nxuPPdehI2jS2fMCr/JBO08Wn+/Tu6LdP096DkpiJhJkBdQkrZZvL0ko2wRhy6Z3M3/AdP8hG7E50wuMjf1q6bbNI6/pAXZ2qvoJDphLVB28G/0Hvm9oPvuQ7rq9htUiVVfuJpV/c3gDNgdX5D93keNixsn1VZTMMaJUMl5ozGnkC/6w1mS/1DsolyT7qzWP4+i1JxkzJcak7BrRrLVojhpidKLdMcOBZoKjOsytu7ih+5WKW1+PyKNJWW10XAjixFcZ9W20GzAYreGkuRJLxmVoR1ZfmrcprPj43RrqI64LmOrI35owV7nfz8idSRltdFwI4sR/Xe/XnhnFntF09sfxzkFmkuSJ3UEs2ce8lPjNu2FP8XgC83Zr6mQhE5jqyRx8F6IQPvliNSSguFGA44uUM1fUilpW4BTOZJ2shQqTxpKwqW5yFLTY8KAAuYJu0PPGP14j+L94yFOaJ47XY+UpP/onikZHoB3Fmh0RBpNb0aMxp5SDoUPQ774qpV0zieTKL/udYgZMxQmofM8JMtKrU2qy/SF+Xi01oJU1QJuCS3FaXk3guYyNO494CXpU7zSRUV6oKy+/0436ApnefEuKqJvUd4/9tFe96hoAg33eP3zT+XP3GRWv9OooV30nz9vvqVkrqNvcdulp3A6gKOXi6hAW/aEsw+e/woOwjcYGIr8i153AMM0X6Ahmo8D4D+k6yjGf6FNPePAQEAbaEig2dDgUbI9sDDY/pjNJ3Co5qsOeei+yNiNUZ4esjU4GthMSmSv5QAWtyjb4TwnMl5fxfUw6DeBRu+3GhhZTMbRG1X8DI0othiiM41jiDb2M0gP+6IBtb5WBWHXH2Uj1N81edWYIVJ2NS1UHGsaaQ6rGE890OAITW6e3gDNFwh+83GIAPpWDVSEzRcQ/frjkAIsiKokKuWZbG2QDHe+g8pQbDIwYOuT7o3VfoKGab8OSXRt1PTFuf4CynL9cQD8O5xXjzl78NtP0ADt16Epl29+6p1Pe9u7xevikEHz3XwBWVR/HAAvvprujSF+hgYSW+jNt4KmTgPF7K+qRquPEUtRPUxqlBweI9YH0jHiZ5BUoYWm7NH6IThDO1iRgq1UEik0HEIBxfgZZa8PeAfxWvwMDiq00JtbvjKEbHr5NooZ5puZDt4keb7CcQFvmINdtFDr9dLDVKE4ei1Ui6Bupb0Kqo4DiwFspcKDb2mKy/0erfEjXrPDD5dVXYaVrL0KP7iPNqZw95sqLLa/FSubgzuzsocVdtp4mWCkO6cPEXRS4z8qZot91xvnS5ThKGlzul+ku284iSTzotNJgZey3wC+fx4i9svnBEMbgfgZwkFsYccdfZYMbL3l/83XUbejHCE9TIzlsrO0ctJAAwe+sQ42fHsrvLRxMsHHVmqqIhK6olM1N1hHVY8hc6bJK9U3ZZpPoBnTfB3yZmGU3WYYPF1x30BPVvt5YBDuMr03CPcNGoR9zldaRyvWVub34z8qBtLzAbLGskGU8KsWOvD/yNLDXjZI9VExUtViYKQ2JrQ3TvsJGoV+HYR++ULswySKzw7FE3UFlzur1BGmbg5hoe4xgF1VS6SHRvU7NF71SfMg9Jk+i2/S/UlPQmIz1VFIbDmAxR/4kVjU2WYAC7gZhAXcUhMLxcjq0fSmUSLD3DfpdGqdtljLTym4fXPfpIOUnwcGuUM/CMG3KU6KXD5hYCtoYLDhkF3d1IDvW9LNJ9B2br5qjiCZMfGzaiSteevU+u0N1/kOjddpMjiHQklYYPaE7/C8CU0G+dmtwAbwtNsE5mu31ZAnsF8VrO8S7LcBfYP9ZqaDy0w2WUMtNPTMNGkNH2D2JS1hOZA0NkRHAw89BIZvG/iKH/0bB/4reOvAN9Afig6hHq5sMTBk2WjwUhGiTEqRFiVt1YM+De03EPv2s4bWpYv7IyqeUmgz7jaQaV6+zeAUxVJTj/sGT0usacR9zuSDcN+gQbjPQ0YMShA56agUXb8JaNT0Wg2dzwgMxA6P38Bb3M538JwmNhm8ckvBa4rqd/iKLdW4dmmTtfdVdPMJvuusv+qg3rhlYAqaz1JCtP2jUErv/qBQK3BsqKEBCgNjDw867CopNwGpBul8B50hYpNB/yGYpBNwJILtYI8i2FQfEfXwg4MOn5iFhLz9E7LwGTwRCy0G73J3+whvId9b+wm+y62/Dl62stxwD2i3j+EtrtcCvnLtNNJwa3xARYEyMZsH6OSAGspcHlBbHWfOABpAG4Vrx2j4btZBwKcpNlC54TpNByc/yg8Z+orw9gmS5s53eOKFJnoDvsNkWecwp/tNFMNyrQZG7qTt6w3b+Q6N2WkypPtfk7Vir+O/gpqfbzDoxeum/wI8d90msLeu20prZDlXO9/lY+pyVZoGqDe0tCUYHSRrbBC/oVKhYLOheA5tZVpfuCsw6DdRRQBpj1xfjsoH7rVQ3bHqDnuHaLONPFSr2wA+PItthpicpXlOgMfyUftNQCb3WhlGqA7Ejaqb60SurmhT7fvTGr48krHXQhUxWTVCw5cXtdNdEbfSb6Ly3q/O9vsYo81DWrXHBlgMRK/AzfSw4fvoIySX014LPTSq5sMY1BFzGmEXmtEXq7adsVxqhlnrh1sLNRj1wwx6KZ/6POk1kUVNi600bO0mgQJoYDdfZVZ100AjPFc+lPBVFqSrO1Tn2TVssbffpUZz20RPqoE3kjLhBpoqZBxobYLREBoaY/cG5B4AqZ9trNpHH1yfxvmq6tB9r9Q++Oq+QCEoyF+XiD0VL0tKKDovRX4RyddlDf/KZpgvcOtgTIFeE7UcUT4QMmdHz67gFSc5JVds73NGr6OcVNmbHkbp0DsdJSRoMwGhKt/meGflQ5RtaWCXMSurjnIGSAlWEThPFpJ9U7kexQYhliD/BqskGXpjZUda+ZBqVUKEieObOCLZ6yE+Amv6SZ53mZMoPBNaNe+T+oTCDRUCLr6QKmUbfv4k9AOfPrHe6hdN5qRXJyIV0d0m/sntnO5YP9kjNHsSO6yTEyo29D5LI5LenCqag69iY5Q3HtbgoPZWEi452QsAgjJCvq3JG3uZxzmw4RzHtGhGA1nBhE7TcCzQkiEHouvUJS1oOdW9tiGXQOfdsQBA9pjYnA31I1WlmdJvFMJU6T60ZT2lj2gtTJb6paTwVhOwW6B2crSht6MMddWbUNGYER+ClpaM5OWqBdnCY9JVAxggHG7pBXGxH/gAtuze+eSd/NoBOUw+nH6lTwaE/2zIb15GKua918b/jHcd5qU2k73+NidT5U9XGDc63eREdR/WM6Jkj+bBniBb+h/Dskdu8uh08zTnk7JHeLarWChgO/+LBbrXKXcU1dN7H1IhXPsYrRuwZ8ilI2US3CA4s0xWEdjTszzMlGn1jZ6RcHU7hZQraFPvfQvJGBNB6nbyb6tMxBYg0Qd4HlV3CCknQEYTAYgqRYm1FVt7224ebzK8xYnCjO019e+gM5ApB5LF9CtyeoV2ivkD0sGUE6dI8zI+2dLUKis+H4yUG1rdB0nsZ7HhiZUnplHDkiSlgUEPJZbxwOCKJcMnQ2mXYEdE2wnwwhYjhhwhK5pjcXvSkHKi3zbEuUiSNYlnhMdTUZe0YXnoNQ0mDeOyoJPWiTeRpbyQ9wlp6AODQyzybOZDJA9LC9g8mMRMwxJJwq1h5gx0DMYmdRItHppmZiyLe3dFEjBJBjPwVt4CjOJWd5gvegzRgComP1LAliQ08i2t6sFNRVgJbWRWzXQF9EOQlaFr8tbh7i56gdbC9QX3NQgzFMFn8taDREmoAciYB0sUifh0T83aIEY4UGskMeQ5apKGMAiLhzdxnd7BlujS2ClmdrTiqQBCziAoCSVjhSq55FJZbCGm48umDrf8sQhIUNnL5algl1b3QRYoadezHuX4KGEq0nL6YmmPH/yYuoxVARmRKRrQNSCHX+A6ojvcd0S5nVYJgNlXV9JhtVmqhjMKU3TgCqnRVMDhdGf2ZrzmNj/6Xh7oOqfOujsYdQg3VPgKncMOu5mES6NdliXYfsbvo90+Ri1g+Zx3WnpCfcTZbvIfr8SXRH2SJS0VtwbOr6J6eZ1Zd3nKZhfyFUZnv5F/MzM8qc1DXmXgONBqeIbNQ8Z7ybRZV+5XP+SyRNjD9HaaBSNYSOvdUgyn67YkecVn1pbQy7cZQFnI392iDObl7vfud/Q7sxVI5bSWbZwRnWY22UrkwapWLNfOI+ojr9s6YbvyeUe/kRxl++cd3bTyrGf7o79XTLpPvtUdQqgtVX4QAYrnd8gSSjXeuo3+dHsqFqmqBii3PL2OIaRJp5ICg2ZUEcGcdSyZvcbbQbCdnDzbh4NCCQfWC67QYEuoShg6LULMejjyoIoSqzYmVh58C3dQnN6cg2+h+hUCFGU9Ck+sqfiuyZeytfO8zoIdTY0MOQ+6TfwS3qvv0fbzZE20+CuOsv1G/o+y4Umtq6qsbonh8xTlaPMVF0/cCH3Ch7p4I0fo260bw7pKa8LYM0JYty18ORvgDp4IAXtKV7xWQR5PrPnMz7s2f8RefvXCVOxp6vkMKESuSQCFyNciavuBZYbMSQQqBylNIWX7EIaRolISA6JT+sgPWwbXxXAnv9IxDWvqokzyNdFp4Zfobtkq1k1aksqePLb/t3DlVIoNPSEO9uztr8r6WLYHobr0y+CRr9twSWc+odKXUuFJWoZQdWAFM9ZdXZjMlXzpQoaa+V3N45EslFob2OLAlmE2N6CEXKW7VZXhHHyFGstb2tb/Cg+dDqpb6W7gmaWq+eAZ0MuzSlmNv0rfD9Tt88Ag1cqQNw6xOCZnhcJFIG3r31MwNhvgyogroG4joDl0+yrkRVZqshScoeqRalj98CjNOpA2+41YUXF1neACR7Hi9KDq4PvkANeNrLaegVqQ7syoTYr+UMN8kfYNRK4SEnzs0ivmaZHctC1hueqVs+xzTtVcsZXD5TbLLV1dQ1MGRwbDM0tUWxfYLsSuNQ7ZQ+QGJ7NLnqfZrKucrhTmab+Rf7u0V6m17CktwmojuDGQRx0WXbBlyOv6Xj3XSoRlVVRtsnCnLBkj3kXZ6+XL+ilKtuiOsLatAQpYXYOdVEwRi5JWDIELjooGGF8ntbS6wDKojkxgf2hTL7ZeGNn9AqgrsT4pQPxQHwUhQO3Ukh5FTVQpBGjH16noas4msILp6grDWkLRWnHTbB1wrazYel9G6usUYPXFlqE4fI1ecmLdg/Jnwa6VWDFWySSxrZwwqIoto0dVnVbGGhlDgkmNMIau0PCdvJI1E4npVagdjg2HW4ewO6Uldu+bGFtl3U2b9y58JdzVVZbuVPxQNQ/BELjob+VdUtbwdWbFQ2rACK7x0tkglO8dtFwUrUPaLGCZYgZCXX3YJsbWhB2K1iHZAdYXLjcqZdlgW3a0pY9XDWgZM4C23gkBYPTLPHNgFKWbLapRwEWRlfvKYJ8QWmSgKDQDpFvh2R+bFPcQQ138X0dMy6JuydIVdUNepEleZBGm5x2y3TYiUtd1eUhX/VKnwIblC/awZFoUgREnT1ICtpzFobKuHtjOV2TT4CTXXE2UUfG3SVnCFbPVFRSh/m2fa44Qw0sdUP73vn7tJSu268bY9h2Zml1NOzXy2u/UJiCbq1isO/tCkWNgb3CDGF6egBrP5fahKN7sxti27JSaXU07NfLaZa0mIFsoSa07/5061pLTvhvU8HIFFvRu/QPSOt2uTOZe+A5xrm2qQYbeM+KJWFDZjL2a49rWk6S/IqGI/7FGsNikZPbt7+H67R6nSfhgxHSxpybpcnqXwz4rcRtkrR3UkSVXf/qcmTyc4LDT0P9Z1z/pv/9SAqGMJ7OMsubb779QX9Yuqn4gf5bXSh/TDYpz9uvvv9wdSO8dKv96h3K8bUH8TmAmaE3HbIHWba6TR1pVaI8yRgCPUd2k/lwXT0NFtImK6CwrME2gTj6vyVoiR6iff2IxcvSK5xvaXCc3h2J/KAjJaPctFi4+f/9FPf7vv/Rw/r3K2OeDBIImJiSgm+T8gONNg/dVFOedrU8G4oJw/w9Efi/nkizNAm1fG0if0kQTUMW+d2iPkg1Zcg9ot48JsPwmuY+ekQ1un3P0AW2j9estrT3NQv5kQIYnQmT77+9wtM2iXV7BaPuTP4kMb3Yv/+9/AUq7PljerwkA + + + 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 + + + H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2snZFKqi6zM21VO0ZRpEQ7urBJSprTL7RQJEiiFRmRHReJ7LH5sn3YT9pfWCCuuDjuiMigKl8kZsDhABzuDofD4fj//p//9/f/cb/NnnxHZYWL/I+nL5798vQJytNig/PbP5429c1/+/en/+P//j//j99PNtv7J58HuF8pHKmZV388vavr3V+fP6/SO7RNqmdbnJZFVdzUz9Ji+zzZFM9f/vLLf3/+4sVzRFA8JbiePPn9oslrvEXtD/LzuMhTtKubJHtfbFBW9d9JyWWL9cmHZIuqXZKiP55ebpOyvqyLEj17ndTJ0ydHGU5INy5RdvP0SZLnRZ3UpJN//VShy7os8tvLHfmQZFcPO0TgbpKsQn3n/zqB247jl5d0HM+nigOqtKnqYuuI8MWvPWGei9W9yPt0JBwh3Qkhcf1AR92S74+nV8UOp0+fiC399TgrKRRH2uOWvgQM58/aelX33789EYD+bWSKX5+9ePbbs1/+7clxk9VNif7IUVOXSfZvT86brxlO/wM9XBXfUP5H3mQZ21PSV1LGfSCfzstih8r64QLd9P0/2zx98pyv91ysOFZj6nSDO8vrX18+ffKBNJ58zdDICAwh2lG9QTkqkxptzpO6RmVOcaCWlFLrQluXD1WNtvTvoU3Cf0SOnj55n9y/Q/ltfffHU/Ln0yen+B5thi99Pz7lmIgdqVSXDTI1dVZ1jfVT2rX2qigylOTAGPXI3tbbbKLTbH1+VWwejqvqOEuqan4C5WnWbNBZfokJmZJdKI3OqnPS7R9FuSEFNUoJf4SiHBDOTosrXGfzs+TlXVHWpqZ++yXK3BK1rmnk5W+/RWLX2Yn2HtUJUWGUDeaXCdrYa1SlJd51K8wC7S3De+/wloj55qpoNXgVKpkXKN+g8qj6gje3qA7F1mH5e5HPT4euqS9lsiMWVU1WLanvNvWJJP/g5i1s5K8Ic6Mygr4scVG2y7B+QbdQhlfJ7fz6sPn6D7JOXBVHaRZh9aEmVHXnSsXfn09WoN42TO6PidFzW5QPPhZicv+MwXAwEtVtGczDv/xit0I6MtBrXO2y5OEjlUQX+bHmn0tU1+1YnHmHaKobfNuULfSzHs+Bg7w56OU8HPQ5yZoYC5hjsy2tzNT14tl3RZpk+F9oM7Tpwb09jo55JYQHNla31U2H29QCll+S3zbJrSOLAHjo1CFCnzdl0eyWV9Bj+/tqein5/pB8x7ctAylm8umTC5S1ANUd3nV+PVmyrifw07LYXhQZJNAj1PVl0ZQpHV9hBL1Kytbq99MpY7cCVUmP56BB1G0ZFsIXM4lLPzM91bUr8Rztk9r/bNAlKo4JDl3rEfYwp1lye7Ylgz3FGTKQO4pr56IO3itF9gV4brzimeBandmq7m4yLlDV6rhKrUAFSLUOVQGOupFTo0roQen6W2cC6igGmoDzoGHNui7UuhpovZ+ty9D6nrYwZxWVrvOsucW5pERMVa+KJoWUTzyrKlwpgLaVUYV4KYVzVG5xRYXzAqXt2YmzQrhEaUPdiM9EXAdFoG4r0qGm6+bf5kDV9rTHse3JGzp7y0rhPW55G5VUrOBlXeTha6HKJMJ6SEmGDeBBQsyi8nEY9tWrZyyig/R6S+9MEnRaInRJWHXXthdmPF8l9yf3aLsLPowjiHpDnOIRpkBf9Sit8ffgM7EhcqNj/jBcUfWjrVISNcPMiknccVjqMT/roiDy766QaLWq/feghNRtxdpL7NMU6UM1Zj87juZ0oEf5H/O3RD7OW5M+DNtRlhU/3jSoqsm+5HNRByMM8IlI50RE7l4TxvxUj/Fw9OcV3hrrnuQbz5qhvia/bRvVNOA2jSuQTTquFLLgtGqf1D7Kqx9EnSk71ZVfd2qU7xZTJKt0oTxYh3fIgjR5h+KgzzU6ilDpceryD832Kyo/3lANVoUNYA6nbic+YSIGyT4kgi59IuRqHToao0+AumaFke+sAgzUDSrYYD3BIg7SFiyiaDrjyaukQn0HKHUHQ3cI7TNKZ0cmZxm1WALmmn2IbU2cEuSCiOJ+OKwS6rYGGr1p8Nhq97frsWdF2jWdQMY4gjwhs5zN3opF8H/chk6LcpvUoQv2gO0yyerZu3602eL8uNhumUDmGW/URPMxHd3c4AwTcQmldhyP02uUoQjXVQbH1VGaFg0QWT6H7yoOH71Lqvpsd7TZkC2a7paFbcCIQeOViGrKjzm4n3QONqnqd8Utzn03qKR+y0VEVStRuM5ckjXDHcV5o0FMl0fiTNgpLqva5ER9GeNKEZ2NRRo6JTNqCjSPMkNEO++SPMYhmZ0Z0W3dZmeJV7is76iAGOVNaSz36kZxbjeM55oBm0xkuVSyjAEQ1z3dK5xlhHyjXtR1U4QF+sqDqDsswLn2WtwF6brdw1xP1r7cbxFG2oEqAaHtZ9hp7ohaeVgCQ6iJbT611fX45J4a+0l21NR31NxPWyCdB0BXA5wGqwrSnNjVcp0gYiI32/OiquGxjcXgQORSqdcAiFcXu/v46j625epO8sVwLwUY1262/jC4h20R2Dm+ROqXUOzapQtE9t+ERf7ZHl+AXeNAwC7CEFJXFWDuXf6RlJvzAud19RYTJDQaBey3BKfovRoOGIMG2HUkQxiA1VojAQPqT4BRK0AR0FUFXt4Vbf3jpKzPyIYF7rsIBZJfCSTRXg3pSvgvSUY2fzrm4SDAfsMQUqcVYEFO2pEFPG4VbrdF/qxHcPDQqduy2s49pkQUNvu5SGkHzPu5OA2ZfJJxWjFv5mK10+R1KV/9M57w14h8+47zVPasGVpk0gbMNqxe17xYqqGXszf0d7yj5mqSGa4aRQp6uStyZNzgR9IRyf1CLYW5A9WbyU6GwCV9sHVGmGktF4oku0ksdzaXWCHVdk6AlLvIASg7ykOFnQkO5HI2Nl7jEqXU7HnW4zjYG+q2DAvmTLc229C1qvc9RYmDq6LFNv8o3iFKxLNqiTudV3clQrYN/hqhQaJoUYlToTHPML8hqc7nJPgEag03O+e9u9orye7oN/5KZ7cKKHbMnN4GVioQQN4pg1BBe05hYQpYDDhMhyVB3ZZqQxA3mnCuleXr1xJ9x6YddJyDqTXoLC+b1FPeRbNPrxWCQsHGA6UIEWE9roPMa2S+J1Wo0NsEdtjeKzcFwBztdoT1woUvaojIp91mlg3m6AmOfcCq8tUrT2K9xPpVVtyOsWPOIk1rV88YHOuI6u07c4Xu5w+Io4On3p948cMDRpClGFpfT4ATO0HlEiuBQMFs1HUlgIcogsNaoG4r1tXAWOcLjs1GyhTt44TomT18p9ivbD2+YzbC09MYL+r4SK+S2/nTZO/nKqFlJnDbHIDmxtaWCTzOyCJ7UGby3jPrFOwe8VoNReeIdsmMchN1xArdRpUKweWahwhLDlQWmyatL8huHP3w2ccldUJ69IzDsw7Dr+/SWhZIfSsd4RYxUi+SmvHA+xHlLcp2N032n6i6IpySRUH2ofDBpb55100/fO2O5dbrEZK5cAcByFftQCjne6Islp4WOdmcE1ktsSqHkaLOtSDPihEpa8h3Se2qhV0rHegfqn0O+wWjNqTNhV9oK0mbbdLnNhdwDC37GVeYQJ/lG/wdb5okyx5C7RDTZY55XNvtkz5L2on03sqS7S16w3DgWrTdZREuB8ZN7TLgiXeWedjQxLkE227zh2sc7QoVbbffWU+XzTbaVj8SxgFda0QJgw7uYzykMZ/4ibxxvfzWzC90Sd7cJCk1QUqyjtbGiLo4zb6psU7A4zRyVr3BN/VxUgYf9gx4Ylgr9EoJLtHH+o5QvFtOIjx21uKcjB/DFekoSq2htjG9t0Vso6PNRuhD8JjOqtfFjzwrkvBz8h5P6Mx9yrNOwAeEwWN8P4SwfryRcHomM+rRnNzvcPcc0+vkQcRph6K9c96iiMH2b5PqMiFWE4o1qzw2x2By0huajOTotkSINRx9O8MhW8RrclZd0CTYZYTgxRHR8UOaoa5ToTqOxXiOSlwES9+Is137W8SBsnLWhn+e5LRKhNQaMXPlEn2KqeQl2YDx+I56Qkb/NUrxlvqmzkvyV//u978/fXJJs7aT9dOj+9FSp5xVJ1UwOZmXCEMZh5g49Fwy/05Ek6AjZv9dODcSCy/99rcm6X0dQSq72661GI++J5jUxRmDNTA8DOyp94KF84gjf1f86EbdZzcJDh4sanzz0DoEToty6OMrRHZfYYhfJem39gVU+rR9cEYguhukGM86WpIdyLjpDbYo2k0/mSW8bbZxJqnDmNzHwzhguazRLgamB3r8UhYZxTSuu9QrPTTElbvaLXiDBDxRrh6gTY8VowWMdaILaAdfNQ+vmrqenCsBuoVCf8HVXYarOg7SXvlliAgvWdc4/5X34S/ZnbTocBrsX+OQRF+BP2abeRvoN2bH7TH0TG1c7gieJHMciD3OMa6DHt57RGiwuPo4D09MgzvvJK9RWUXhr15tc5jRzEzRK/ZF2ySbryuMOpkMXvCIDYGq+qgmqvNrU6PjYvsV5/2xZkQmJH0mOq/NmkdDiDMcvmP4gvDt3XyiyO/joqP/gjczYn87L23GlSZUn4yIwpRJvBObeJdL4macHM/UA50+Nhkn43iPVxTgL08N/o7KB1rZ0e81WLKEEvIRuc2CV10lJb65MR4T/BonM2F7N+jjzccS3+LcscM0Vqtf6KN4eEZ871FSNSWiNNRQIEpuxrHNoy0beRu6mo1o6R88ajvSNvkmQ+3hqcHZGUcWu/bOUUkzKcXysXFIKTVi42RTQIWfFOD8HLfndO6u7vOyPSnt6zt3RZ0fszOERsMIDA0bSq976KtiOiGawsHUUFIImAbUOZ0jr2B0oW3XEqwUyyaCqOLzJDjXCD12AdD2mQeUO8yWK3vLAXkGE3ZyoU6+JoFd95KkDIVUgaoiBpXwrjwzbKM777QhIHLwYWtiOkUQ1QAkOM+Osyf+2r6zgJr+Q2CqMYCwnuPotZl2CD2MpvcChKrjIphnn8eIlXgxwNoA2dD+tu6Smz4167jH1vYfrqIZj76CanyGWp7j7e4maRQqByfrU6ZYqU5ZGE9t+pnY4MSOh5we2u5r6mkmyKKWapZsqnpOlYjaaeAOo3Udove42K2Zlgd5QJkJ2XIlF3JAzhnChEAOTWdFULm7PISywwKYa5dZhyPQ3bEY4gypUOIFGSLoAsJg4Xplrult8fGvwyUEdVsGR5Jtzgn3w7N2ZmKEVX2q6G4zJcONEEA+dEzGGN0DOzTl6tdwDtPYxz3gPhSyIvu8XZGzGdO8jzElTLOlJxpmpg1mbqUslEcnTJR4rkFbJu/DVWHYisn+AraCzgUxwUnLkhbYdXEacZj3ZnK7Qh3deDhQiyHx8N6jmvw9c7iHzMOQPEnWfpYeVS/P6qveY5twDWAcEKB6KCB0WMas3psQcMlxQHEwMNRtRTqpclS4Nk9HxVkrFryu9aqoCZ8u2mKyuTWdb0Rs6bJ+mO6i+YYW4mSB1/F6wY91C/JwyzDSK6H0GnSsqGm/wyZ6/fQS/0tkY4t4yDEB41VBllSU1iKq0XS268FHPp52toyC7bb/IslvtWGccWY48nXh+CFNK75MubbAmHghP2sOVLlJiOH3GaMf72d5fSL2ybTHxtB8Og3sIq193cOJPZS/czwPHICYvJ1CmZyvUwQIS9o09MB5L/EebXDyrK9/2Eho1FdHolc4T8rpFk//y0aQTKHHW8Rdn4CWsDm2KlwYqV08CxcaavfsECpOcYZy/Zbo10iXzj+gH6GLw1l1VSZ5hWPcTI2p0FtxpZwMJfq0VWosEvjsqNdJPCBzcgSUy+dGEJDneatTqIGsjWEIY6iBoLq9NDNPQk/1zCI56Gh1W0SsEh/VrBVC16xl/muxkLzssCjb5jCba6u93I7CywD1VnyKI36VfnRU1nHjwUx91eRYtFfSzDFLgNyyaA5Sq25rPz75RX2ODCccnLTrctIe3Kqrc6sePI2P3tMYw5cd25noHZhhdinCgRwxjDo+Ql826aByyUgCgYJMJCAAIkpwJIPvYDJpVUhLrghbFJHsF02GrG71RnoYZIdmDzrsX6iN9CLimAAsDroLVBHapu16N6akJVLraJFMaMZN8PyvujCNJg+UebpcXks2PMyG2Q6K3XJvnjjO08k9UVSsY2qBw7cpfH2+UDnlGqkNrPNbe7hLlAFvybKIDquNui3DQmB5sd45wjoryrfo/nOSNcu33tvo7wq65sydVCDehuCs6o/4tbtJVxf2dFU33Is94TpInLqtSI5s7mJ1KLJoyTXTmJkz1Fdk9vwsiDFybOFXQHBGWJC9nRkYrIY36Oqu2X7NExwcWtY/7rIeR8/P6KFRu1MGpuh4xDIhhFDrml0kNMkh1NXMiSI0dV1dLkI2i3kzYCjOrIwZMyIetDFtWQ3AvutRTtxMUx3T7hBQH8wQdVsT0YJzLA9Ej4ao3RcEP3RPUzF15zAxHdSsLtyPLjXKrYUeDpHkCNfZBEwHOVW3FcnCj3VV5aw6JVZPM709s0d7TJ1qbORQi2xRE7A6XdT4h0L2ZMAZ1nmf/Fam/sZc4SOF1gDYDvphdv3AkvtPoSN4brXMzMZX0qdn434o5FBdYQb9EZJjzqb/MXUJnHIsXKvAeA/6ZXb9AhO+O9mL8shLZ+tCaWVt7nn2owSveu4hEtgszLFT/ykE3DJhoO3AdFzgMVo9OlsS6LA40kWLKkgv6ofqrRZ1aA9a0VWdBb+pFScqJ3Iiiyjp5eO53bt0+NzRcXhU0XLJWRfQWW5pXK3UX5iCB8mkb1eBZ6KPR3VJmfvgmEGLR9ffB8190LGBcZAmoziuie6acytJUX1ZlDWDq9UpfIEP1uEKz1s8xTFMqPlSV7dIvkH3nXKhH9yP6b3ManiN2oPyFVelEAUe4lq4Sm7D/QgEyUHJeivZWNf+TFZbtPzyCosKzEEfwpuaHO7hPKtBfuBldVuX35rZQ8be1FgXJhbpoifjrD0ns218VyzSLcyYT0O6PrSox+b4rqIhas7xGUVDeMY6HyM7qip8mxMpGq7WLvGe8l6e4Dur2pfSwwMX4/jPJ5/D/9xmEfYvBp0X79351vL/2NQfb1qk7eYkoulr+zyX7nUUw8tdtlVVvmLr+jOc+c33Co3HYH1PDWxfa9G1bXjIxbaqz7CNz79YW4n8IAJuR7GIDrafuq293I6yuzoQ83bSz3MVKmp6L1Go42/BDrKnbiuS4dSjiXZGR3PUk0/b3fyZ6s+q/mJt8KUXZl3K67LIKLZVpkBzt2h8npezXMS9DRaR53zGA/pdbeu4jlB/vGUxVBFxezzr9ExgW+NaqXmNNNDWt31QUI8khocv4mJyWEUsVpFlXunYz6HikndM4x7gxTMFV39oJ+A7ud8VZf0+aRObzJDSxHpN6k8HL5H+lASAt1mhbKqplLJV3ai6eGooolaekB70s7fmjHUEEbgHiGLpzSlKCnvPSQwDNYnD0KU+sAJoIoCmsqVG0WGYQa9E1ykHfWIv6tPUht8yWIEl+Zc4R2qRsqC3eZxmJ0l3DLr5BxGiLZoxleCX9gB3gYbCbqbPZkq3m98YnqF3OP/G5CrcS2oiDzt4DQuY3TpuswTGdHz3wfaxvd8t2sNiptN8IN1+ipUsjlvisJAB7RwWsj/RQia7ytfic7c8g7Bz3HstZ6+LH3lWJBvv17gGBIdFSiO3PY3eNHhstfvbNQVehQZcn8rgvIMAqtlWoaGtuV6MpKe2ZDIo2tnHYvF4Y5yGTu7JmKolzi4Oz0QqZqCV79B1rcNisPbiGFCUN3u9O/dduuM7+h4O2bwseERjfKxz0DPK1zpBAGkhhqGCVtorjMo+0N97lzjiOKy26rYiGaPw8wauuyb/Jy3ixJY7Xi1xTpmXpU0ng93bF9zR8sitAJh/4jqGoqAGGFu95kEnFQBDSDpAARYzBH1qYoSCunmuCMqSIWK4ti7Qd4x+vEXZ7qbJclRV4W4tCWU0/fWE3tJheG6Yqt4efGqjKbrehYr6l6TqBxjv6gbXQd22VSLwtVBV2qgaaqi2pqZqQQx4WpTN9ryo6s+FV/RXW796xqFZB6eNXQrlso4ysfhr7BbIWxwdrxnYiZkUIBL3qODisEsEVjkYVOq2roodTmNZQjGCy5ePIzw7P9psynYtnHkDt6InE8TFyPt9CaMxp1c/1xMYoHnGUrXSmUBcjTdOb8HxOwFaUozFMWlTp163UmsgLAsIkXYq1xCXAQpX531ngvR5i+Og0NVttVRajUKnsxXjKOqy+foPlOoWh7/McxvxQycIVaBlR8znQBTvkqqOYWQOeGJN8YCvW3bEZeiwHjqsh52eVS6IfDGstAUYryXR1MEeCOxd+6euax1A+FoSuIwcVhB1Wy2B3pRFs5s5oevLSHmwxOPIBe8Ffej5OlC5x1llqCqOsp/7GdeaP9fDd5MMq7X5NQskaHOmDNbmLEC4Nu87EaTSWxwHva5RMj+9Nv4pZVwKytvi6Yn24AuZ/cbqqjhKI55tdHIZrnpAJwqkm7xUzweyKTsutl2MqLPuobWfMSjWcRjRd+YK19qMNnHkbWgsjtvWoL0Ipb2ea1Qy6YARZFNmYq8nwIlVoXJppQSBglbLqSv+/Hp4odxkZea3DRRO5ur8iiSD7snLyz2copy2qmvpVqO9i31ZE3GgizK4RptCADeeNdu0Er2GCF7Kj3a7sviONj2+YyC7qeu2tKjjI41swES11N6jOiGq6EdRajO7RkpITBpb0jqm7Zl0kufIlGvsoEuVa2y7JE5Q/ALLFYKrKw/h6u5k1mfYWPWyAkSjVWsqeFkBNHa8zJPsqKnv6JrWvRNwgVLCtz4BXb2BWT3TIT6YDBolFMnBdULmY/77GsMsd6O7wdos6HGb7HfoC7b8kfJyy33LNHWUpqiqlmmQ/PyON6i8fKiITjH4d2KH62vPs3SKBDzhsqogLQF2tYJ2XL0zyVmpdpm+u/8OqlMjNJRAxrcYIgkobSv4ol7UywOheF7hLCPE6iPYgsMsiATvNOgsyEs2UnUTrSNxsJ0nD9T6ioqsu+wwp+ZVcMxxU5YoTx+OSc0FGu0au0im0GTDTZN/95aFq+S+tw9ihAx9TsxPvERUK5fN15qoxOwsT7MrinamazlcYyf3yzR2RRsjc5PS7f9SI+QaXWakvdZZZoR9Y4uOjDTkIMrujXHKkawimJoISXaK0Nw0Vbc8N4HVLc9N7R7/DHlUIR6anUkHWZ/vSmOJyOy8SrIkn/HiZEcsqrwuyGg2TL7gGZuarQmykyCDQBvXh7icm/mRlJvzAud19QWViMhRuFP/+A6l34pmyqeypLtVanyRp6sGWyrWIdbRzQ3OcBKedWvc8Oxmp0F7qkJ3aQR9J/LHhLd4Y9CbpQgmimH+iaRdXmRfIdFmvhOIpPqGNqopmXWEx9+/v1ykoZP7HS676/BFPj2vuFCb/4mS+enJylf3qNZr9BUH54VhUB2lrSHwtsg2C/CH3PBCjMk0/CrJvy2yoxfaXETFsG2eHS/ZXHtePeWkWqLJs6/JAsZFv5q2BuAYwza33Ddkh1Pif7Wapk0HlaT0z8k0WLzpRURG1fgFqphH2GbU8Dt6MrAsweVGFxrtZfN1tNGXHfJ5U6Z3SYWWPJE4T7Bv7NDg0hGS88w2L31z1OFAFM6uqZmcPwu6wV+jDEVIt7reAG92J3yB6Fki40GwOod5m9CMf71b6kNBD9q7g9HgCLM0Rbv66g6T/iXkcxuu8DbJNx+/e+yslCfL/JkWeL7cyui1CDgdJ0Pl0ukxCOQaQ6Q9Bu9agM67+RJF17zvcQ6etE9Vcove4oq+egun1AMAr/sjbyavnhJKCnbSgLpmOniDb9pdonEQEOD1pwptvuD6ThqMGVoalEUV18G1tWgkmIa/20Axqf9CkdRZsdyrZx+UKTHGYkXPpiK4Z0y5a88u0AahLdqwGvKkM++BjrJQRqYwAkuDMddwHR5dYdWhiEOpTHa+ROqoUOzTq52VNpYgRW0nACiUngjlqvu+JBmxEHT6goOQ6QkUS0SFYIJCOmGN7fHGeYemAjX7IQ5J3dZ4uh3o5+7Ub6izPMg6NZoF9qbAVMNgDQwf5Wy7WmhX4e6FdQ5LxqrjvDbwknTYrPGNL4SwHcRc3dZAr1AJ5Zb6GMj6R4bmO/meQ6EM1LQ3y6caBot8+CjJpR7aVaEIFuESuwurAcHGZ5DCCVcyB8WibmvwHjJaAbqtbKedYsQ/zhywcVYNnT1Ka/w9ieCTGxAeF81uIdf+BSHGjj5qsYjvcmxtmXs+lyinG+4lRtY1tcyw3pNdYXtZcOZ2zqqJO1on6r69zjN5ytyXZCv/mLSA244G1qTa8Vyr6sgjUoAqV2UVfPilIs88DuzFokMqB5vNOCFS8KWfKHb++V6f0hMt3xzX7QMTc0dmjw3NHoi9xGgWGcncgeODb6OzEecmGd/a3LTbS7DvskG+wwtWEbJxnFUDsmhm/DsiIPn0mKHjFohq6+4FV3sOMVyRbPJNhoiZlcwfztEp+OM2R/xc0oQy/B2VD9RmdKRtf7uOxRDv1P6oqoqURopvBtMJPiyKaLOp7FCTjRfubXY4nwVPuYDzW2vzWPPm1IT7XH5zSirUdO08xptT43lvoJX7IeZTPz+tlRuaq5l5fGi5/F3Dfd9iipFZ5Q7boAns4iFAcQPiJbyEDYhHCEoFBOA7yKCG7yLlc+gIHgOHcM3R/2RqZkfyUu68mTSDNv4PChKCogF1cJLa0ALHP/+KHEdlM5qIh19D9JOHLhwCkcbQq4P+U7cVxQa5KpP0G6H4QsH37cXpuHvNlmeQb0j/sDHzrL647WMdkygKvSJk0TVSUh0/y0JcTzpA7iAHoIyd5KGCAv1YlDGU0uEIwCyRLZ2ixPX5JCCP4703CkNMOVDJKywtXnIghOkG7FQ4TAdR0IjCmhLXwQbDXkNaOvRzp4fhWjmnCfPIrKw08E+KX0kq5jBliqWnjzLRu4F9eeiG78UvURJsH222ODdfjv6LZ2t+W0L+5gC0GYQhJJ2sAIsXUe1xTULfxyheLqK58wv0zwZ5PQ3eu5U5NIclQ91WlCUjmukUa+2JE0DRabvTouy4aXnnec+/qD26jXLIHdYB/TVou6kVH8OYL2KxTm5u+muOcwcHzLsOSZPC6LY4KW+7HHBXRafOpSOaBS2QmZ5LNDhxGYIq3LcQBODqBMEC/ZtF6w84JitYWEChiOmwLOqUR4Rl8Twp+y214zaoC2jxqMhOcYwrBtEOveLERe4n0yIRSVQSHqJxgbNt4+JYLI9BFU/MrXBo8VrqmoVn3VpKMMC5pYaNmg1EaghaS5RA5n577wF1IUVSK0BkkQrG3OUocUZSplX/RVBCdVgF1W0ZbhbZviXmekiI7mvyabubPxcZvQH0zwaXEZ4XpMc2FL5n+Fh4z6qr5P7kHjHU8EVFEB0TNrgtyodoCzF9srYsshi2Rry3nc+qNtTZPfRVinMOe8hQvYiISqi9Gw4fdMKw14BKnNS1bR3pONS6YtAJKdxKRJ3e4jsodnVbEsWCg0v3slK0JvnR5h+Eb1jXS3TjvAshWaChs4pgImKP0gjXNAIUqr3mWl5niSans7LzPJdIm5LydZ91MTiviwLhQWup2xJJ9jNnchLHqnBcgjx0LVdmXZl2dQDnpmXFqHIWT8AOkqVh4oc0Q93SHCgNFNE5KnERnICpDd5s8QVGy+tfft/T3iJSluOzHNc4ydasydguWmmxa76GWnVxgEZ9xUO7etCU6//SallOU+qoz73Ucsv05NO74tZDI5NatzSklcFy0MbqthgyrekQJ94rFOtQTAKZQVFmYK4l+El6NWCSXtLBRj2NYBuCDiKgcm1v4xxoS2SMoU4o/EGlqNvq3m8gPfpRlLqnJF7M46gxuIdeztPqSU6BHW0saz4OWwoPS6De9V7cvkPfURb+zHhR1uZ4ZOvILMfmTwn4YtnsdmNe9mBB8zQoTOEwt+hTqYvbePFbpAC5G1SWqFyksaghF1Q7aO9FzuRIf1vXO+MzQy9ikOtTZUxYabsGRTGSVMaR1iiKaAwNr696LCVj3Wr687CmqNvq1WPwOXUcz42nG0m9m9E83zGyxzXwdIdUKO8BJIjgG/vsu2ZhV2QnTAfeV7e1n/PZJRP0RQwfuc0JrY/vKI+7B33EjCFx2FNnFygtvFLAX1JHIUH3bERyECR1Wx3pQ9eQDst+hHIwXKhuDx3HGVlNs4iLGNs3hU+r59FrHpT1akEQgF8LBAta2Mh+I0AKi2dj/YMA/swCeJk1t8u3Gi0oMslvG7KTd5sB+7dMKGPgNCTSm55TFPkzEdNBqOYWKjKiN2XR7JZnbtLy8o1yDzotdxrmcTXNWvqu7tAWfU5KTFF5iF5bv3rGoTnInbqtllAROHeRzV+YNLyMc/l5Tu7vE7W4227t1q7778Dts/Oh6zVG7SnAXDZelXmdPgKY6PbccG4SR4JPC7JDIh0n/x9lGT2tCfZ+vC0qbaaHSHmA3hW3xTlOqVCt5xbS23qbvSo2jFU1303lIq+JCA7ZJT+g+kdRfpudYc5LTHTdQ6sVjpuyRHkabEP2OE/u0zuy0UD0kRRv1JrrsMpG4CuydITX2lrMXVkTsHxp1ljD/b6vPDPmkQngiiFxUPqx8KBhV4DHbnk8mV2ilMZEPhuQHBZpdVuGRfq3mXyS3cQYnoT8bZYcR/bvMP2773LyrqAIwslq49kly/eWTHrbwrzthd6PFZUWFbDqbq8X3+YM7gjLpyGOsdVtJ/mGzOwCFtZF0eSbMc1bFcm2bbF+aLa92AXe85j62F4didnHCetrlBdbnCeE42e7BSo0edEwqqO9XtlryxaOZuVsASLugd8n7bF64Fa4x3JYbNVt/QSHGXP6Y1KyGBDevkqqb/73bWltwpEyrgNjaqaVIVf4IfFFk+eMPeKrit8n6R3O0b4YnfBKrNX7FOetpeOVSfOySVOENp61T8pyWrrmfCfxlkaKnSOyO5SesLSrO18aaXWqfYbt4f2yrEau+UrMrtkAK++dTRXCwu64bkZQowf9qW7LuIueRUcdZTjRbQZiRbwX+cn9joqoPrAv0t6D4te08u8rujwE2ke7jzmndH1RfUD3NVlJPXT+WfUWbwjnBu+Fmpwo9H4Zjhf1Bdp5cytf6SUgW23tpXz7S9jeIc99/eoZh+iggdVtcYTqrj+aDcfZLoHZRULHShA+iyNkmUjmvzWoQZsTwvTZUV0TTeOZBKY3HqtnIMKD4KjbYggWfPuS9IRMAvV+c5xPlbkwIxKoq/M4mVLXzGeKnGIgZtrgqGWepX+F84SubTbbrSD7cqYL1e+JrWGwxuZqGW1w0vOIaQKUdgadPdCwAHXEdQc+mRRqKGknpwF1PcplR+7Qeb6aaRAstOVguCqug2JQOoyJq2UaEvPVckRsjaCdNtfPKCvXYb3SaP4SF2Vw5nzKTstv2K+K5du8QLvsIUrDBjfB8exNvErT2du4bL7+A6W6rEeRbAsaKzZ/pFjMA+1LIqBXJQ7Oz0fQ+LmyWwWepvS5xWBTFeWb90neJFn2EM+zMa0u8MXsyOuc6NOwXxltB8SS3DSiax4YHAgHo1unecCg5Znvlv/6zOI5LNAGMdVtGX6bJa6rjwIy7ZQsGzfHOS8/wvOiFE/WXEOSKjKTcQjkvF+vKkMuspla7txhl1XmrOZl4r1GNwmRarKqtrLAhDBFdosdJ9tdgm/zEH014DjoKnVbBm0x15U+o405U8ORbM59RlUu45buhegKbcma4nUreRRDAdVBGr2lcSZf48+8KX9PINvXmuYPE3iXVHXXXIli6FSjK6Czx7sMVdrxxcmC+HM5BmKlh4i5J498vyBoi6/c2L/wOBOaar8Mqv2rsbb1AvcB/ajeIaqfCV8zZ9X+6xyM8bDcqduCKfamwWMfur99RHL5fVYcfRLXYekerC4j+VKU3wgJZ86Dc1qUzTZQFFsc1TMJ1UEGNSwSU/JivbzQzmD4ezo7nK70jRhtil6Jf8GEvWooya2sAQ1yLJ+X+DuhxRi37iuwPJ6DtGqkNYZCpzwUS1KviliYzNuLv8xzF5s+v7y8T4i+xJxEeC25z8786uGoqe/CY64ZhBcoxTvMXGbZj0mjjm5i2BjUo7xauebhJ0WqAZM0qQ7WNSZoEh2b3rPQyr5PQKaeM5BBS8B7lFREEXcPxgblquAwHdYAdVsGJ+FML9rs+UGdC0o6ywvo3lkrnBMouIrJa6Lx8spvcyNJyojsICwHYfmZhOVsuyvKmrR6g72ScnL1D8KxNuE4LbJNtDdxnO+hZ21Af5wMFHEwRbms1PF8a0aiDXcFw/fw4BveBW4Nk28oDEOXQOhjHn58QaTuFKNsQ38tkD1oYLPjIr/Bt03J3waa60jr5J5oL3buZ8y/mDXbfMyaM3NrF6giCvosv9EdF8dpqr8AS5B7JQLxz7PArVnKS75qKGm/pwENy6rwkKf+6ZIodw4JH58xqA6rtEaso+RM6vhDnzf+xTwONqt0TTMt+G2m2Pt6T9uAluZvk0p3U/Mv8TIhnjkmnulqDX2ae81oG6MLuLSYmzZTeernPbSPkL+nyvI12mXFg+f9axHFQaOp2+pXpVCVth+hjpVmcTmjZmLKGBsWixD6SNm2zRHzcRoy5pqP0Qh9cfaqTPJqi9s0QjGmAsI5jqTNOdAqJRjMY+fcubUMmQfizMll87XzE8zeknUUZSRGaNuzOOyMOLg40YlUHvF39J5JWR1wk8TnPoom/XvvKwR2VuLCfD0CT/sqFYy0q1IChl3Quw9zeHL1D4aHuq0VOzxnyuJJ1Tb9qyfV/HrbMvFUnJSb3zEh7F5zXZ1V/aI4CG9gNHkc47JTCDG9wWF+MZkpCTMusekkP+k6u4T3dWDHzue7iBt2wT3Ex5ubCgVevm2vN4SheJXU6d0l/lfgwcI50Rrd22eBchbRvDoutrs28srFII2WIePveHdUpncxAthprelNzHDjbrK24JwSQQaemEHCaAlG8/hzRpvS46+GUtim8T3+r5L021lO5CX9Fhiff0yUYlbcPlNgPFiu6raiBP6Sn5smDddUkSJ+9xFbr2A9MMLeBCtJoLGC+7Nm7YQ5jWSsYx5ID2o9jgE+LGKUrA43Sft2YRlwvXzQJRC6gyJRt7WfbehnjH5E8huuLVqNMCK6LcqHCLwsojrw8YGPF+PjXrlHYGMB04GLD1y8GBf3Pq/RCPJmYh7RgYfVbY27iheRdicvw/DMv+KXRVURGzwL5zIR1YHP1spnDld98uYCpW3ggjtfbGm+rAHBgRvUbUU9aPI89KEzFeouneytmf28X/CGbNr/XuSGpqK8YIVr7evPkQ4A1/d+cuwMTl2YyFVxlEaMlmhfNAZ9SpShaWmnfq47/p78SECx5ECHYIJekuIRBujUCclBr6rbaikVHClUksYovSN4rNtD1UiBG4ah0+R9s5+kLqMbL+/aYyzLR7hiKGNU9uGF9Lfu2DLScfv6lP8rdIvzN2Xhcfgpz96Pk/tdkm/Cx/ehOC2yrPgRjAf9+ILzTTiis9T0iGYM/q8ftFL2Ik5M75Z5Mmy2sRxX1XGWVIZ7i1HfBe5W/jgWgni6pLMi7CyEZsvYB39rkhaM3vQpi6wLbj6rTrPkthrJ4L1BB7BHsx7I5BCdkz3QqCpG7fBT9B5tv6Jy4OodznOqpj4nWUN+/yLNKAf+mkwqEdh8hH8hU7ijpYa+p0mK6kuymrQ850/YS5SU6d2zFl31jMW6R4K+xXVFV0lbirZQRwz8Cz38u+Qrylh4+U4WP2PcYtTX+dV31oa95VtMrzRFnToW9R7n7/gOpd++FvfU4LCbwe4s3nb+PjRbVOL0gt5XVc2h1XxcYVSeE0zoOMnSplOrw+O40ZSVupE9TlG7KtnODrEiU7J1oOlK+gq/6Sscbf5BqNXd2Rum9BeP+fmSZBmqz4uKKqQLlFTURgmfmD7wo3oG4N/jnBxttji3nhOyfN8lFbKVGbLXxVlmq/EIdJNvVLpO6gshGE4yodJfDEoVfcW1iqGsuOO4KcmONn1oHxqmE9hkMYyMKaUT1MAe+aPtxnu82RVEvb9mLQgDr3AVP+1sWeYo+5E8VG1lrjUD7zDVmLZ8lkvT47zBUy08oqVqaY9z/iorvtpOM71VQmQQUZ611gvdiXPAGqq7zRgui2xmCnVL+zT+MQ0QP29f3rKbpvekI3hHuntalFs6QK6y1WbgqKqKFLckHEzagiYO6/agF6hqN3fXQ3Jwof8n+eZJt+nT1pq2iA/j1VWoAtnytiMixCRbsj+e/l/S8G0bHL3WTIPDEIRGXvBjIo18zLscqU+O2pslNASsSpONvL0mFN3wX3qhoWsY2VFWhD2InpSdxjhPybxlLkMRkFj6nmknx+bEktdoh3LqNnaZQ5t+sKnk5f6MzQrENNHu9+cMs1rwMP5XG97R9s2SgcEqSu5loZ1ZF27q8fGtdhxLMa123h4Fx07u9d5tNmwt6CgrJdfqq0GcK9ZwYVxDawDzsgCGllyIVWSZWaI5KJAUBV2nHYbPIXxUogp2fQHpBOfgcQgk6flRXv1A5XXLJzqmYOBUfNaBuHIbixjgN4iB18FrQMcX4jZgLmxapvB747VLsodB7VE42W5dH9PbpuWDkuNAaIjvOEAX1oNbgNR739XV8aB2BAtwonaObNrvq+yNJfv7yUZmFOAgNuxBXBhQxGrPer88eya7KbxYSNGHBZhHQdPHxDa86jFNMy8tcVmIxw0wklZLxmcnsD8LMhVIa5v2uYp7Y7Dxqu2UQEHFATIoxFrTxWB73gIwA4xlx7Q+Y3+FM5qqZWjA2E0ePjoVBPT2pJCly4MabQbYvJ5ueJv6K1bQ0aOH9SGL1Ixme7w+A8o0igU0lmm+rNZDJpXBXvTVq6y4pV55s7tCgoT4cgByYUgZ8aNyXSi7vwALKufkUbgwaO+Pi22b5mZkHB2XiMAqDuzhXJlQQg/woYrB18GHqhEsxIqq+bFpfqizP4cabpN4XL9HG5z0x+JqrxoADLrWOjgnvxqEGuBErp+zbQt0vVnCY6ahs03zbL39cVZ34XMYy8ATSgYAwUHu4iCdmAxuA/Lhwsj3r+/0Q1iCN7XzZOXU7aqshjH76+S2TCNmb5qDMYWMT3Ib62dMfgh7YEx+nqwYc0rUth8vSp+IyKgrRUBwr9zDOG2SRbz2mjHe2qvqxBJ7WwVdH4NWe42rNiHq9dGOTAq9DN2PBmuccbpKEFMN8C5MpW0D8r7YMa4DadhkdUbZgoAhUrBwLuQA8e9DznQdWUDWdHR+nPLGjshF5Lh680kd3wy0tbLn6CA69cusC4mGKvNRZ2zB3uaKQJPhjwv0zwaXqMu2bOw0VMuFMlGNRdv+AXQF4MyT6KXrrEi3gNKzIpFNP4b6+95EDafhH28+lvgW56ZdlAiv2UZ57J8k7PuIUDD0ZbmdkIrWNj0Qqu6dzYh6wt9R+dAmqTZxAQscmcE41JBKY/s5O4tBvVmQvyA6Wykvpt6+OetVk28yRBMjHNV1ib82NereSbmeSkwMZ4NDw4fK6h4MatUVtYnDjHmtDiaXES4nCy4sYHUuNNZaj4D0Q7H0l6rqWQlCEOcL7T1CJ6ppLPvga3gW7Xl5385VeUDujLwcC/swb0zTQt2VvfDeI3Tm9+2PLuXRrWlgAqmChtt8fPzKZhxcsatRlMpRLMelyvmy2mb1dVbDpZY6UYSfmUcf8VKuGsMeGPTxKlHurIBzUxs4SFlRw7C+BzvGJh1d7qthYeOIluNl43zadIWttyrOtlS+UJ2F+PkRK2LdOPbEwI9XIV/uUIpvcJc9afR42DKwvraGleGKHkxt6MEjZG+7ES3H6HZz/BhYHh7Jx+5tRgVHqtjPAxd4hVyDxulGuUd3oLuaVmK5f1EJGO4CghPAGza9gzGsdCHRMrifbteTd29LjrZb1rIGi/7+JS587HtfsWz4xl/+Ojz7lsJhWb5KbjVJrWTYyIfrLGa1CUaKI+as6nB+Tkqc5PU4LcfF9ivOW0Cn0ANbPBrCaVB40NS6Q/uOZXDt6HJ6wXVObXq2pggI3fgsN3QWKFbB8Y94f+cwrHWIxiPc6VmManiY5FOOg6SCxbMK0eA6BMgHN/B9LgZQR9fB8dCc2vSMrbc23vddATzUfgSG/pkU/Hq0+iNW5cI+q7pE4y7D7KxzwKFhc6C6B6dbdULN9St2ynkMcDlRcJl7B7FYjftN9GdoeNaNQTWY7GUlXE503bCQFrXIrlVuLAa8N+mx4AkfGZqw7FuaVAun9VJjRLAfc+rnWGGsR7d/S+unWFvEwbXv31yruNWRNbXIHKSkxRNBVPT9UYuNSWpXKz1WA96fJFnxh4NUiSj2LVxOjihbb5PPmc2q/EZ7dg49bg8QfQIwK5KNXSpAEBpMQtADOqVnAJHvLRugtjsLsJeW1jbtrykf4PVlQh/UG9nCpGB48MjaS0AOHYIq2De+7oL7sqD2gilt0wG+5t44bHyImXsBS8lhMDjEYSOkC48p0Ds+x9Xy2b5tQP1QFmBR/VTZdICttwIGNR2rSJAzsOVjPCxR9n5RJny8ByIX6DtGP2xP9XhozdrbAXqswEILj4kVtSNYbtmG5+jRseRblO1umiynT5XwTGXFQcrqRqZlanrzr7p1NUPDIrMytjYObGk+N86zA+N3FffG/qdF2Wzb9wKMb+TIoBBbj1AufAygdnoRJ8oWSN2JBfhLTVwXu3K/bHRV7HBqyUc8rJKRWjBnThKQ74mV4F4sxUswgR8PM123/74pi2an5yQGUMlGzhzEIgXYh+nb6pZOVf+XYjxgPmyanmqtQYl1XGOhZLohz6G+Oswq5lsp3wFdX1bhcfNhzXcrML8YfjFbScyA45tgDHIV94F8vRIWVIxhURtOnh+b5tsK+2fFz0WNriceMjIMD69lSArqxZRCGyq+XOfrcIZRLMmZ8FxZ68m9PhL3Af2o2jx8xucyJUiIKQcgF36UET+q5zKV3V+ABZVzYtP23p/LpL0fXlgcGUfHJSKwigM9nssE0QN8qGLwdfChagQLsaJqfmyaH+rs/6Fz9jzR4q1vDjz6U988dscTZv/L8yf3NSrzJDtq6jtK3i63wQVKi3JjdkZZ1YZIpavoQj67Djyq98GdhrSAvDvN8aPwkH0sNzSpO84ynN8ebTYlPTdScRgEDHF0C+fCuiBigFOHDs7ja9X1YgHm0lHXpnm+5p45yqguebCIXPQ4NR3c98W47lHqruHRqE8VMejfYtKb8mF8iUod6aurpXt2jK3g8zgb3KDmHbH1canVUBZgWqs5tOnH3t8dA0fSaT4nfurEeCnu7VoDWBfU2SvlW24Q+2Jabt5sOtFW2O/i3u5y9TwqwCmXd1e3mYj38bCgoudLrfDyXDwmZjPFd0qQMzDcY4zpVPZ+UbZ7hLGcb/BNfZyQTf056fJdUqHNF1zfTRykYhdDPYgthyouXGlqRqUWIe6Pd/PHslcL8J7lNFhxIohh74zJGREjC5n4BaylY0pfq1HfIMCeKinYvw61GsqCPK2dQ5t+DHXWxcOfWBFzY2Su6mLczLf6eAxR+8Hsi6nB+bTpDFdxv2brBxoeYbFHmuCUJusHx1AXEe/jYU1Fz5cyVuW5WP8e6QL9IOJzXhAE1SA/Rt+7rhLEhgC8C0Nqm3tUXnqbkSzArTbz9yg8+NBA7AwBY01rtQfuf1wa8hMYuVmXN3zu8K4NOtETiQcDX97pIZxe2eGxPp7lBe74AvIKz8P6F5eh392eeWAVE19w0Dqmc/XFwQ1Aj8oouHo9LAgOYUFOBOfIpv0RwX7DC2g3dtYRKwJ0xGADEbN9zEo8t5u2J0vZzioa2/LUbgWRK1+SjOgCaysaBoeYi4N0YTJFE4/KZtaPYQH+1M/To7CT+SHojT4AdkaejGBrR+CqpQ06DY3Xb9VdoLop8wv0zwbZJGuAweFdDwPp5iAAm3hUak4/hkWcArp5ehRqbuq0pXmnqhD9RkJMO89pA1a0zR4nZWeyv2ryTYa0R9CaOvBmjAd325Cpm1KHSTBjmG15sOjZIrst41TY9GKqtUdPgDAS47KhrDE7Ez7O9cM4jH3w66NcRaRRmKLHVBVm59THGFJmGsQ+2PQRBpidF1n2uajJKPrbY/TDUV790KhUTR0wLaAA7pQOUNMUxK1T51fHsBZDWYBnLebOim3HWvsz0u9Q+q1oxLcppM9qo90SAWjEg3WdTHrb1iHrQRrj6rjddXgLsL7rfFsZGWLlPXpT0qYkpLg9Tx7aw5SzHFO8puNrTS3Yt8JXcHOv6BqzP7+NsjOz6swi7hKLGbDqB1NvNVw4uCYltrHlERUCG970ChGybB7gVpNo7F8pu45uD+xvmm+bLol19yYNdFq/k7l/V9xeM39TnlEKgKYOxPMMiAuf61qBfIpC51fH2RbjWYCZLebOphdC1VWwr9HPBgHPxLCP07GmG8HCvPko3WlWXGjiPkeu8+W2VbwutCdGe7QMdnRzgzPyBV2bjvskSFDRDUBOak7CvHj2IWUXllBSKsJaLZt7Dts6SjMhA1ebFk69okHg8JKZuXtOFegdk9jtX4vpx7HIwqmbJxfNRuvt7zSKbKvoC1t4m5QPJ/fpXZLfogsiasdkr4Ty9EF9LGWqCZ5P0UpOh1LGVkDW7fs+jyq07tMCbGg9C1buezWadTBo+8ONM7kq8VmSR79nXgQ7szQTggR34D6u/v7YLr1DmyZDV0n1bXA+sd/UzGeoCLKgXMeJIU1NQndQ2LGsbnW3HdESvG05nzZdYevtjbP/1qAGbU62Cc6O6jpJ71o/6inW2KTqKhA3g9Au/KxpzvXJ6H0zsnkoC7CwefpsOkHhV8a03Hv0btwEve2+ABNDT9qzzXIjmi381L5ve+NOaH5sOsPWWwO3XncDS/VJX1UVDJzpyY98EwAXcn1enXFgGsmyPAvOl00X2Hpr4FRG+FgWc9NvLGGW06psqwA7ayRmVdysH9HeVDEwpzZ9Yart96mvy+ZrlZZ4R79bPtoKVlE++MVCOz/6BTe1p5dctZ1ZgAHNxHdxrO7xqXX8PanRe1TRvGHXp2WxNfKdpg78qDoL7vaUurqh5dnOojcL8J0F8a22SEy9tTDfVeHKelONWRmPaWbvbCf3ZXmmk8lu04ep1t4Y7my7K8qadO2m9SjYOEnVVSCW46BdOE7TjLNLNArbmTu0AN+Zie/gy8T57V69mSf3zsynrgI/f+bJfJpm9sN85g4twHxm4j865iMNZUUXjTywiZ4n5Apqxptg3XkPaAdy9ugYfP/7Y9NQFuNZ9azZdKGvsjdWfZWk387yy7pIv7nth00VIdZV1HHhYGOzjyrI13Y0CzCz7Xw+io22ajCmK/WGegvz9GO8Z285lj0y9CO8df8e5Q3NGNAHFtKfShYGYCG25cFcuBVqADyxzBsY+f6ZVDOEBRhTM0M2rbfVl+HEE1KnfiB1alIDlYORvU3K+uPXf6C0pkXonkx82mr8JM+LusXy108VOs5Kyg/VH0/rspFtX4r6EtWjv2KH0+rpk+47w0dtAcCOQvXk/jip0W1RYgRiGcsfjLjIXzThAYSmLzKieFekSYb/hTb99MGdEqHMXXuX5LdNcgtj68vsOocu67LN6lC1nKfsngBnRH6Oyi2uKjw8Lw0hFmGMSPlX3mWEfEi6qYdFloG9It+tKneJLFQohnwilkPSDceIpL/CANJkvPVh6gg9Z1ZITVdmITFE6BFh4u9EA4KIOABr2rTaJa91JOpBjChfZcXteVGBuIYy8+R3ZgA484M9ZkAxPKYJ4ZjezzXRR6fprNXcOU7rpgRx9EVGFGxQDYSHj1qyo66uWxyEuXdJ3twkLSwoZmy59cTRRJW4RFsFXwJgZtQow99R+XCFt+Cw2XJbKk659zSEZDMauqIdM5ic4qxWaENDHdtGtezOw1hwfQdv4g0AzBb15Q6l+AanrR00DlnTCFzBrHTBah/bTQ6ogzXwno3ZN2NLvKsEtLumUltEn5MSJ/mUaOe42H7FeaIijrmWseG/NUn75VOOQdXAlvuOwqHrtk3Y4PZH2rMjAbBBP0H7NmTdiO8MtEmgHKahT/RlWgL6WHVQ/Y9x7KY9EEYl2YTCFthYaETTxtqobKWx0A6Nclc3ldogSlQY7HrxpiyanbIXbakR0Qf0o1Itp0OZEcnJPVn28iQ7auo7uh/vlKR6o6SDNzbWZnaCsPZpsiwNHuaJQ3gFht6HN2IHHlCEscNvYFpi1yG0o59qyplniW3Q0Cf6lGi6hxINaOSnuWB6gU94mRb3/nEccDkf3ySyRKKiGP96kgEZ+1ABPE7hHQgj9Zgk8zDZuDcAjGPlM7nC4xUz7pp2dWJuQHB3J6dydEWrXMFUSSqNtIVScqm4U5FyzbENG9xm78mU4gZ0oLCZh+xRtS5PPbouJ5bRSwf2yqo3Q1oMsB9TnhELiaYc/B7VdwWoS3kICypl6rWOyVphQPOp1KAZC82LBMoRMYi04ibCmG2wO7Lnb23Ar7CrkgOwcKwVsCukv5xudKS1t6UVjqLxArpNJ94n7Uwr+9KXm5FJ12bh3oFXnx1wm3Ca7f9Oi6iZnwOw2B0CVyjgbSJ4FcYevQGp2Uhl7h+BRil3CczoqtzuEnwLbsyGMgs3YxsJe4W2u0yh1QQQK3P+HaqJec2eDauMexnSbuNhwg3cEjHuStnYYHgfysdhG2mbVE2JviB8ewdONwdgi+41JlxbKUYtwhiRcoGpEEYhENikJh7yVKfOpmKLDR0fAQZv4sR4PSukmuEKkXmmow84DgI8CVHFsjh42rVSCsDZnnY86BCLMNbuRg1OAcTCRqVgG80hEQ9hHnhZVBWpmGlQijAWAjqEZcCiOQVtWCCaohhUyNgQEwEhE3OgP5u+nk62mTqaQ+qpghgiwaZq1NQbA2bGQStPzaUoDNsmhpAZtonpdF+Me+GJZUtINrbATEUY2jA+sJKSfmJIhIl6MPaZSSeGJ1wL4Qcy+Qw11IPUV4TICMRXaIhowA8QUhhrODGLLNOyHg+gGQoLB1Kmi+vQUYNDMTcXTQ+ldJEl8NBZEH3PGUjV8MfIFAMRWFQAGUBKepCAi/q4HoNJZELAgOoxgPAQUcTAFA1dYJyQhEwBM8EUGtKWamgjgqhHIEBC9GAChjSkEBEtRAQhSEhNCh7QPA5+aoPJwqMDiKPnOg8KjTH4TEdl8gBQ6sHIwBBhmCg1DWEAXABVlEQOIcgrnGXMg6s6qgigFsPha0Sgj4BwISL1MXLTlR4NlSRY86jEKjo6TRF9FuSSEGvMlRj0GuIAteaKDKQeiAQLkYaJTNTQREY1s/lCGzwutu21tilAEqaHBKcfhwgezDAgUoA+SlL7mHddECKfag6y8SA4jXUGgIPW3hglqTP1IGQAXYQwznDacHGa12O8JkAdGFIzJLACSCExnFRHKBgrZBor0EWg0uDAMVMJusSmHY9wey0SlYTbaTLWKItY79nTcZEEo1lbBFBQ9zDxprplSkQ1L7sMYTbXR7tdhtHmqmD7KRNFC68ela4aRCwmoF1DKy1WaFlXToEH5Vivs46NQDj1mCBwiEJCqLGGSiDGpblK6K4NY/FVXLiAqxmTvXjE0Jqnm5UohJyUoA0NR2iXUQ6VYlJuxDmvXh8bhi9WaEgGVrAYIVQvAuFAtADt4HHGsiIGX83Hm48lvsW5xoyQQI0rvlhDY0hYWRASvpkdTEOz/EUaNYE4OPNoWPBg0nDIIDbiLwvFoo3yks41e0lISTKr6sbB22DREFh3OclMeavG1TqRu3AVf1b6Tpg3CcoqDgTga1pR3JHEQgvzLjVy605k9CHgnKRblGjjJme6YKekmQxrHJZURUMxy22ZEvPc2wyxYTOTSaD2ozIzmCe5FuUubsPD37VUEk1dxzhIZVUNGe13b8ZGFtiFQH0w8yEI7jZQMz+GEXJRtoSvsFoQ0lDROGR9fQ1xlddzzWQ2tDkvwXV3kq9VN4mB+AEPNJpIAHdsYOyB/n62LhTBowPQMbQdU0SXE/1Fc1fh0WLz5W49hRcUM21HrOdUwVQBW+Uh+LW9jq/eKbNg5r0tAx28T2ZxqXVU1/9YVNFkB7D1LVijMFLAFpOG0vocCeZJsO7Ccn4MXZfM67dN7SCqmFfyOedkUSNK1xE+FYbXdHAogqjCYtrTxHBdAGZHSC0y1xR5SIi/WHjIghedl+V6IJXJtVikIa9VdSMJbLBoyA4nbzFT3qpZ9SzMYaGKFpW6p/aTokPiSiMNLocJ8pgcXcMWU6ThjPjqyEZwzHW9FYeNyETVVPsSFjB10bWi0GEu9HicyaRF5zBHQx4M94nS90A9aUYuCdiwWZpUjnaTrXFkt2db2swZMmcZgzRhQM3hPwQPxhJMeb10sQQguuUCNa8vk+0uQ1OiMTX7CJDmOecrBLOQgA7aWKpI7kGfMWkad28Ooo8CUj0guAJEHzatm4ZCCoQLXPGbWtbsH2Qgm6Fo9gjOZFl0H3CBvmP0w2JDJQAaJYCHDw6dh7EuSKK3KNvdNFlO78NwBUaaqWtaDleJIC5V1c1oZFPVjAe5x5SN2gtAAJR6fDIwRDI2kaSGWACymS/6TMknzSQRwAzD4KGVRBnSYpqoIqBbgizXbE5MBU1YGMMIGFAFNRIzGVgkAA24HJ8xmaNPMarljA7Gah67scThiQ4XTAyJokFKg83UqlMbDJyNrDNjiaE6GHQqDoEJHEKbzwXZ7jEZcTXkEUAthsTXCF6SVHhV1Ip1T27Ijau9VCkDqQciwUKkYbL1amgio5r5UiVtcLioOCUNhukhwenHIYIHMwyIFKCPktQh17qNGVcUkOrhwBUi3OlePKOKLim01p6xq6geslV9iKKGtNcaKts1ObNd1CZptsg0AMKpxwaBQ9Qb8nhryASimjm7QNemjt8ECFP3dRxkTYOleAJKen49PeCjvr4EV9D4BnX1dNeXxMzqFneZ4DY0d5lmo2Sfot6SjB204/g6fpmRgF0DAPVgZvYVwXZdVtJLBDHIzwSplEKzWSViWoYEGlebDGTTeY2LzZkQyzjWhqcNrs9Jj++SCm2+4PqOeapAJo2pinpwhpoQ2ZhnGTRUMyFWsVMsIxR6ceJ6ejVCTUO4gnmgYD0d/Rw0k74NgJTKOYpFyU/shFqTk6/lOF6u8pyE5RuaXe/Rh0MMqp8BMeipCVKp8fpXTEwaj8E0JwmAl0605qgWXj0kXTWIUoqHWjRE07Ywsx0LtW2UUHMlt8F6io0jXt+58rnW0r91oxZOAUI9Lh4QvGcyvd6ju1PC45lTMNmXfq6n94PUROABzWPg4HUkMZtoMEro4oWKyt5b5+HxFKNHQQQ07YEF+KD9tIhrZqcC96qTVpsrINXjgStAxJEen9JQSYF1Zq3Nt6pUMhCY7VCU6safOkuYBMwrXgZjAITUrS9QBXhR4d8a0y5aINKZuWfqvFkDKWEtPOMWesjR176wNhKfcDPkQtGB61YgZS14aZOentMub2rkS+Uykfqgk0s1sMMgddIZSL+lZFRqWOPeUsI6jEvj7Aqk2EIxZUWWfS7q9qWN9nBoSnOvyryvAlePTVMrPGRMg1uRol+R7d9nRQBffrwGHpsEVgjbuhr9bokCJLLqeUvdemLbHiTkwCudEewW/unK67Mc1zjJNDtwXQWdwaGpBxsz0sObWntGh35eyw98WPRafhTUTExlXfuBq1DYkNjWaWTZIkB146T6POIwPW96LT11KtNcB64etKYW+KwD98Crhpg6vJCNKb32GpV8OvsIhLMbmM4q8qLUUraQiSaWtDDRwDj2Jcc8vtJ7rdmcyUCaiRNhQTZgHg7WMYGEa+aN2Pg2sPEejgJSx9BQBVhGMpuQJQXCBQLD6MUwes0Fb5Py4eQ+vUvyW3RBpml66RfYPBgraWx9U134SabClGjejBek5vTWcVxStj+sachDWw6SqxSDajzC2cklP9Z8zT+2DBDNVEczUkNVkIDwu9M6eppagU4J+BeogwkLPgZ9fYph5aeBVg9TXQkio+oZaw0dNQ3MfH0Tbtl0GdiilutgTdeEo1F16QvETC+u+XfBtXTlYa0GyVUx0NCacjxSgF7CW+gzcST3OrstQ7KVXNmEJcGc7Mi2A973V0+Z770c9t1q8+0+GFo9THUl5S0m4c1t0xUdGPnMuw3+zfjr07LY6kinA9d4ONW14LQAwkv32vu2atTLku6qcCAcA2w9tqlOZKIxiGcm2dmWeb7eaCdqoNVjU1eCqMZB64mmQbyAOXhy70I5DbR6gOpK8M0ea8ppEC9GuddolxWdW7TviJpuAKxpcHIVNc0mWBuyAZgha0U7Gz6PQCbpt7Oc2IvpN+t11VhHPVZTVfD5TLiOnqjGhuZ+aVTRvuaQ11TFfbCaI9+oRF3mAPg9yht63tz73uhP8I03AEw9JAAafOGNA9NTCUIJbtTyRoFOQ5zfn3co6IlzgnNUjmW/P6cadZv0H35/TkBStKubJHtfbFBWDQXvk/b4vppq9l+eXO6SlB6e/rfLp0/ut1le/fH0rq53f33+vGpRV8+2OC2Lqripn6XF9nmyKZ6//OWX//78xYvn2w7H85RzUf8u9HZsqdugCqX0TagNOsVlVb9O6uRrUpFZON5sJbDLbVLWH7/+A6V1e/R+L0jH7yOdhwb7PEZdggB57ig0PegZwOnfveOLNtX62p7RPj0D8x9MNDwlw6JKvB0hYqZbUY/UvEyTLCmJoOxQWT8MFtSGjLzImm0+/RZ5Tl378qEiDEj/5rGw3+2xnVVdvT5FBNctvsge59t6m4ljHL7ZY3lVbB6Oq+o4S+jxCYuLL3EYa55mzQYRLYfJsJKdMFyp1IWK56QvP4i8k4KacC4SpxgCsMc/VOaRTl/tMV3hOhMYp//kwIF3xE4BELHfXWalLgtxKtpPbrwi84gLhveoTv4DPfzoVDaLiS9xw/gajQupjJQrdMMLEJ/5bI/rHd4SZt9cFcMBBYtRKrTHe4HyDSqPqi940y6NLFqxzB5rV+PvRS4Mnf3uiu1Lmez6CC8IKVfsipvIww9gpqRCV7yvChp3I6oYscxBu5S4KMlaJmiX8aujdrlKbgEF03510DFNawFcFUdpJmgZrsRJRzdfM1zdAbp5KpDx/f5cWP5FC+O5ZGIIBp9osNiZM8m99sVnB6tmxCQdaNrYNrra81g4sm3jatW8xtUuSx76ADwWE1+ymtkmH2jwaNhE90g8JllZc60T3IZ88ij6Tw4qhhJBHMj4cTWs8a4gncf/Qpu++6HqQMTnoxQscMzDOV0fRBzTVwfTp08MJuJivztgowRBxEzsUy9yGIUyD6wKhO64ALnhCtbD9WPetiBeVySks2FxZdW16sShx8dNRl9dhNl6LLTH+ynH/2zQJSqoA4fHKhTZ4zzNktuzLekPDSSRhw4UO2w+asFibD/sf1OkMD+1xudPY+B0WuayLtsrMlXrhY2wjgkYfZcyI5p5ZD7uGjT0XhYnvsQdI7BqCEUu2zB6vn+eNbc4F/dhbIkLxquiSYFt3fh5NVJwjsotrio8pU8MkQARmwf3m1GsdbWL6waf3kdncU1fV8NBpoSp9tyji7C34Bx99bVyzWmJ0HANXTA5uBIHl1dyf3KPtjvBfch8dsLVL9/dxSsBIVdmj7W99CNgG765HxR1Vzegc6KuZG4J3pfmLrIsUFsTDD4aGqz2GOyRWDq+P3yBmGQs2o8VTn34H/O3RA2et9HowmEYV+Ygr1lW/HjTph+5Kmi+d0F05eIl9g1qLxphc8Lg6FMtnB/zJS4+ng2Ij/2+3G5uj/pmyBAQqnXgzAiWukdVeR4NRFsUMQzfltQ8H5rtV1R+vPnc5UzkUPFFP/GeXcqAEcqIbIYMT3bUo5iPKTsxgFhzKok/cQYaky0u+fvjzX9R2fb9zP3XAPt+OP1eiNZDsyIW9ruD0bob719zXZo+uxjAR7tdWXyX/QzTd4dxloisZZuPubTM8SUObtrdRoGRL1mcS0XmfJUVt/3TJB58qa09E092zV3RUER+qtgCh2glMgT6QI/YK/b73mfpXPcmn42y1tefSVN3jUpqevq8bKRcN3qZcdjvDtiSWnJbDN8cojO7Bw3/E5HtQ50IRyVSoTPeD4Ua7Vi2Lu5mnngMZXQtqll5vmtfwflToUOsWVL1oxHizJjve59H7l00j8kz1J9nxsZGRTRcgUMwjOQjULkFFp4d5hEyj7nR1p53pZdVP1+yP9theK9NHCf7fXUbyDgHFQGbmKV3L28arNi/dCUOVn1FX1AT3RnTVwe3WpeggPOodZ/2cQ9hqENU3jaRDTap1B3zZZLVMNauxMEhu9nifNBEvC+WK3E6soaPjbgChx4OKb1EQnIFSx8ZvUYZkm7CjB/dj57G7CTQ6dNYuK8j5HcJWbdhf4NQtE8vAe3Ku+IW56CLXS51wzzkF1QilwAcZivJmjqRr9Kw35fd3bWXGWX2YT67UU9GNX116FWTZUCnxq9OtssuycWYiOGj+6rYec7hdXEoc/Ci4LK+o3wkOFGmz6uxg6ZkiiF2kCJLpIUZpKw5jxV0kFQ7TDEssmgyShfPUoqsZz47HQnXiHz7jvMUuGAhFDr0Ubqjdux4P60XhBeiFTd8dcb0EsT00gXT3/GObvmTTA6vFoocbOC7IkeQuuUKHOQnuYewMZ+XsGn2tYttZSD02k0vST6bWFXNebS3rNtc9VoboVL171kDwStTkStOOHRPLHNYW34U71Bdo/KsAm43yKUOmO9KhHS4gXKn8ARU4hTELJY56O3hZu3nRNhk8SWP7cqDciWf6aJHvwp0zgtwgRiKVqPjuFU58BYqi8rnLqq+/lyO33jmTwQF+vVrib5jwITmSx6bIO6JuYdz9TC+HrB4xizAVefh5rhxvDE2YOEJWtpFvne1VsD6PxU54OwDd/q6x7J3EYZw0AVFbW5ECeRyc+BWIEr35eeJ4z0k6Ilpojy6Tdj5kGswRIWfw9kWLTS4suY8Crxvjo5EEdExFLmci5VkZG0OgjZvAxgopYBxiEXAFf6aobN8g7/jTZNkmaD3QYBFL5XctRlZFXIvl7p53pWIpcJ9nj8OTIS2xF6TTw2B4n1fihnqqI1PGOKwwBh4q7WdhrucbZgYaF2JEO5GVhepeNlsYQuLKfYyrxToYQj33reBizB9QAivMagbUQJ5eHeiZVSbyzi5/NYIHaQfHOQjyZubJKV5TkqyotWQ61oFY9/Km1pMYNB9cYlreINv6uNEjKdhvzv0p68DGQ1imUsE8z8bXKKP9R0qRxtMiGWGIJxbmMwNGD9X7iC/DdFbRPBTamgcbTYCNlGWjdAuszs8hiTO7vTdwenS1xFnlv3uEFWWZ514Mu81cfFlQLmL/N0Pl+QU+GEId2qc3O9w2TrDXicPFUwZEca9lTZopcUAyZYaysG6SarLhBhbCGYZoNjlNJ6tKR3HSqVOvaaBiEe3JUKybSqXukU9jhXl2Fmg2EUuxwd0RcFkClz0V1/p+CHN0DuU39Z3ogaDIHxbOEclLqR5VMF4tNIaGC0aSRNDEE5xfHd4d5InZP8nKUWuyAWnOqWHWOZ0goOpJCfZUPv4jmb5lw5zFFD7iwQ9q04qibbtJxdn4pjyVWQzocjJJqOO5/w7kVhSmexV7mQmVgK5uDGL9NvfmqR13Yh+TK7I+cCjrX/0PcFZ8hVnEno1lF9L8CBgCId5wLkGu1zqsBsofnRj70M8pcMHoNxpl4RvHlp/x2lRDv17hcjeVNopqQEdDiyS9FubSpu+ryFd1BQLHffb6gdapI23/Vsu6jZbVwiZWrxttvC8wxCuLST3phZECPsWhjqXNRLSq/Ilrhjbl0fKIpMTLkHlDrYR3qChZz0KwTyCABz5CG16DFhcqoFiJy1E1+FXzcOrpq5Fx5Vc6oz5C67uMlzVGvQiiANlOt2bISL+52RfKnsKYQiHwxOyO2yr4lS8JcaVuPhjJVTOOD5mGwDN9NXZO3xMT6whv3BX4LAm71CKkwzoHV/ih3E8nrzCW+DwUgvp12J/gGlsT4Rzj5g/yWtUVhCjQQBOVgBVxBwWBLGPFtDJI2DZng7QaWd6hVEnh4JmFIqc7BtU1Ud1XeKvTY2Oi+1XnLf7fWAcRmCnsRCd2L1hfLTbZVjcO4EA9vi/IHx7Jz5+0n9zoA6w73Xf6X7BGxFJ/8mBXsB43jqPZ1wj9OpFA+bRlk6xKIH2GUEZNaws8m3B4che9DPwhQ4rQNyEsyvK/6OZEfwdlQ+U1SSvp1Dmbsl/yrEUfyCWua6Y1VVS4psb9UUzAcA5QPTjzccS3+JcESjKFrvsNSvUGwyAa0wu9cD8HiVVUyJKVwV2DsKjhaOtHNgmFXrgpX9ocbMADvibfJOh9rhc9i9Lha54z1FJkzHAbkkFiGcblAb6JkYI71EUnXOTrHDakbBgLrYhzs9xexIr+wO5Iieb6bxsj8j76pLFJBavJtBtNOWCIt0GLB6hbuqq88S6ySuq61o69Bg8nRPKnM6gCGenhERSGJNQ5N5TFWKo3B07pC7Fsp8nMLg/o6/IErwrcvk+FFTutO6DWP2wDbPQBse020EVv/IQjjE53Sk0scygmBy2cDVaL9JThwHvHD6+Rw7jJXpQBil6Bii+KmpiayuxAsUuhuHmFjLZps+OuC7rBzGSkv3u4pLHieSGbz+5uJY7LlRF5kLlhxBXPa4uqF11oi2XOmCGzVYvk5WGLl/if4mnAONXz5De6qq4RBlKaxi/Cda9/x+ho0yp0PFo5CLJb8XFjCvYe3j6zE7axxO8u0aXYHzH5+Nw390kTVZ/Jtvc95L9KhWuxhTstWfgTa9+X+9uByprzmMG9s29wnkivj4mFLmc7m2RHM8wfX2M5zGXqKBvgeaSvcsVuJwSfEBC5FD/ySl2r0zyCkuhr1zBPlXAe7TBCeVw4DK4WLYaBcB2LEwLsJg8VIG++jz6gPZb0NTtl9XMTn9yFkdLc7j8r+UurbMjvjIQcwvx6BJzsJewAiWdweQj6drqa3UAxXbbsFRQOSBUMAcnhB7XwVXw+FwFhy3yz75FjuW22fNZcX/E1eU/inFszCAMOEHWYpnJfO6bVh2r+WFjBnLRZEgVCWYB7hRwDJ/kcgUOZx1dzk1Foju51MWj2l9+g1EDxS6Hu1VNlHeraNkXx+Xrj2o4n9bOlZfRIQivFpIHyiDdHTd1KwKUT0vDBIDLrQbMb5ZaZaCbnhHAIWDgvi4TeWfMfF6PSmbiIgN1MYPJRwlrq691S0PqFuVbdP85yRop4oIrcjZt3hWkUNbYbNE+zaWzqnfJi67E8fNqeLxXfV30Hw39i+IFmtD5O4J0ONbvC+rDKWGMUqF7xDUca+1jEMF2z3oi4cIV0YxZ4nBWo3K8GiSsx3Kpw24Gb9DVXbP9mkvvSghF9jj7dH08tvHjnva8P/VedS1KfeTBjiUj63gBewyVb0Q5zwowdQBW2epXGZVeuWEg0mtubIEHvtZ0UiIdS12MlvMSdY5AOaMLV7Q2Po8UcMpj84k7NWJYv92iCq3zC6o7q06Jym2mrGsiX0nFP/EJWT/GeAdlAEJ/pt3HsVlMxmX7rzvoOjBwKAO3aRFucNpePmCs2wisDKP2Z2pbfOtnb3gknX9P8tIaYB3PFDsTDH6piSlzDdsd4j0Ukbtc8U8sVLrJCny8R4PZ5y0fJ3TzSBTcBzv+X/7ltPArJPF3x91tecAlyxWsXDrmkItoEvF4HPTr5NA5V4d51rPTJEX1ZVHWEk6+xBHjEJL1FotOWqDYwaTNN+i+U9v0g8AAculqdEE/5+1jSBFsTYLH37AEK+9bzvc7L5+TEic5mCcrynxp8PvPoxPSmWyawBcSwt8uWOaNhRh5EX+2zGSPNePOUVXh2xxtxpBX0YwAyp3iIB9Nrqqzqs2BLDD29HU/3oLJUv6fW+H0UChy0FMzZONuba6PTf3xpkXR2opQ9lsZZDWrH8s6Yesci8ljRdNX37dtopb1uPceDgFCy1h3s5h08ey4x+ji7Ssp3VpQuT12mk+FfJLepmC/uzDw8JySyMHTd4/liknQrjzaFmB+YtewOOFRZC6CsC0uZTEcVetbquZxRsVfrNbtjFJhPrnfFeUgUAJesWy1Et8fAxG4uLI/4Y2gBXTI1mp2xllr18UlM3BINO5Y2B4DeNM0wzzkY1ubYl5Jbi9nAPc1nJ19R5t/NFUtvyYoFTq47loXmwqxXLpM+ONya3F7PguZx1yBg0MV59+UL8hLhXNeU1jX9rYl5yx73O6EPdpGV4FuVu0q9kGhWiGwg1496NWDXv0T6NXpKegQHTq+keyuL9VV59GNQ3tvGiweWXElDteOqvEt50+lcIojlrn3U0IZiA/KeiiWuejKvEZdpn9RYzIFThf1gHyDPskGT+5J+5XkA2I+u+jGQ+JCU+JC5Ty0oi7im766YpJtBfa7G6d9RqXMIVyBgyTc0VxHWSE4CJnPq9H5zFt8IUp/ROOh9TV1138ABKbb8EixoUuu4p9QJd7N8xiRQMdJljZZG6jVJVQRr6tJxasRk9OibLbnRRXo0x3ReIiJpu48YnJV7HAqohg/urO2iq3dz0Tl01Cn1ff8aLMpUSUeXEyfHca2oovrStH1uPq/TzFrWSyCnLV4fAVNUXkeSWtbFFGMH/cmaZQE0P6aK3BYKruXEoSlcvjodB2m08TiXZj+q4MHAaMfApr+k4PXIKlq2rDkMGC+u2NTzSRU7o69zUkF4u1KDvpvf/ovCVd8vjpvUXX3piyaHajzxpLHHCjyYVzHRC01fN6HwqNiDpp1XMFB+VnyzCEP0kwmYKsCIpiALR5fdaioPI9OXJ8G+7m5W30INE/Ocf8HxPYkhx+IHRyeW3LA4iGD6qrzSOC7JL9tAHc3+91h8yS/W3Dl+mZBm+VQYJbuk4Ot1WQCv3Vf5jWf1d7Sdb9l3L2xUGy7TO5ydO9U5IBztyuL72jT1z2WL/bBEA5rR1GbG1EC7V83xl8Xfu43Tva0ItDj0jJPsqOmviON9jk0LlDa0jJkldBh9lg53NDNs5rEdcWdbKWMsv0nt0NwSpWzDaXJDRatOqjcHXtvG5oaAcDs2/pIJ/aq+IYEMWS/O2I7SlNUVSqcXKnTOeN3TGxk1XsVUPlqpL2/tBsi1t0GwV1+FfXmEdS2MSh7AlfgiE8OKGI+7+9wOY4y6t8q6U/LRJxyqYvB2z2BoUANFDvOC7Ey60bCKxS59xdGK5c6qI7u3REYsVTojLc7X1fqJRWQO8cdN2WJ8vThWHoAGYZwaaGrd0EMRBEzW+Le56vkvl+eoMMvNZRL5CqYr4X57MrXzde6qMnanacZ6RjE3iKEZwsn96YWRgj3Fq5o/fGpKN1YYMjAFrVjgyFdW+xVgmZsIoRnC5qxiBCeLZC6suzBEJ76ieh5TC33JDtFCCSZBXiMtkFiWoDHaBskswW4g6uqqyJslKevjvwBc50Pp8GvnQhFTm4GQqhXpCQXQ9mEItcRU9VwQbqzke6EQ+U+2FVYXbBdoBvSBbSBckuJZS5YfyTl5rzAeV19QSUi3Ci6ChUgLvG0KP1WNNO1JKUnRg8Z0KKclEkB4m5vqBzPULmD2/HmBmcYeEGYK/DYQ+wUe4ids6OV7mSIRHTCd0xYBDKK9JAuwaflBriaMH51wyQbzdNXR0zAmP1G+D6pvqGNnpoqGLc+H3///lLucffVDdPJ/Q6XXchvkYsJ/kAAX/z/iRKAymK5Hwe/xiVK69foKxY9+yogl2PYsdpR2q55b4sMOJJVQYW0BHGQGsqrpVdJ/k3eHIIA3vhlYQUB/PCfHatR0zIvrP1brUrMY7kX9rOviRgiIBa6rwutTdKfLMErBA/hIGkNsXJL/K9WTNv7UUkKPaGggwtvTWZSPWR4ixeokrLNmWBdtOOOXofW0BOGCGkBGpEayim4YrTyNAPSgLkch5fpXVIhpd8YBHDZCWL4RJsrcPdRQjduxDJ3rHSLSER619TMvR2VX9G6kkvQU4akO+rjx58rNIndRl2gbYJzabupALFv421C77L27oUPRT0+lcC3owFz0Htpinb11R0mPU7I5/ZE822Sbz5+lzYBetDVHJoNbolPFdmvvcVVHf7CHYDS55k7OzTzHLGNflpRTpnvjk4R8AjHcXnyFvk9MdcbfNPu2SIyF4DSh7ns0MzDXEPbIhb2u4PWrtDmC67vQCaTCt3wAg/4MJ//BIwbh1cD+HOxC+a9CchwC/A8rRrKnfuhk0qxzGFlBjzE7p7hs2roQZtYPwFyCQEA7mMnm+EdtD+Dyl2srRTvaCIO2Y4VijxwAvFcYpmDLY5yutOQzW3muys2oINcgUuAY1VJD0KNH124aSJ7a3BCaawlgJ9Yq44aI0KQlmeIvqbujMFatEFFgNVUtLw9eR4xq0i8vB30ZYk2UQd4fi6XemAGT8flUhdKqvrr21d1P337CB7Lexy+D3ugbnkFB60A8W0DJIMCxMFkMB7Nhh7JzvE+zpDsDLgvIRS5LFRDVaXZAwC43MJIUT6lkpMTFUrFDn0n+vML8EoW+90hULTJNxmiq4wQIsp8d9avxzQDDqRhuwIn1+EML1b1MYEsAknSZIB1GRXEfAr0K4xofI0KuO6cRkW4AdD2mUPSfXFmnqtiODIDWYct/ontW9aXHcfbJWP0cXZZYZmHU+PG8XdDEHRp/80VCxhVJxS5ecsgHwT7ffkd6KOTIHre1l1XDZGaAYvPY9vKquvW5Fdlkn4jA4OOd8UyB6w0YBOyrrgCxzNYBB8Wi2XuZhGIVir8E0hPuIuFxRQgRUs6WsY2gYiB4buH2waWTWev96PJqP4lyTJUx7FeWFw+douh/kx8tMobjLHWiTjnJF0N0IISijxxntPbjYTkGtwTyD5jZi5QUoluo+Hb8vbe0WaLczCikS9Zjba5QHVT5vQxTxSaqJhD5bVJ0tZft7KJvVzFVV7xzgY60Totym62ILljCl3wttOOWv+mLM9CoTdedYSdFtB93uCEQHKpC6cmNzedm01g1un7EopKTWlGfOFL6AoQlzbolbOrorNLROR82eMM5NzbxqVofx4TlR5j88Jj89rAmFCsez04T8p++yVnYmBLXE9GIIx8icuGbaIxFAwEle/N8RjxpHyOU8FhVISFUUkf/ZKevoAh9rGGH/QdcBE3SOFJ6Dw0ngWOeVQe/Vc4g0rc4r/oaxrk03YHvLIxfHeL2vpng0soWGv47ujxTL5mqFcVMG41lEu/r5L7k3skkYErcAoXOSayc1uU0vNXQpGH6qPvq5VFBql9FYzzmWTENMBnVRv3gETCDl9dQiHCElKuRWFFeLkWxhlDdS36Wq3UumRxQABL6sfDa65hWmFvvrG0Kek1/P7SWqyIAgirX1SBJaZ55E5sXt7ty+WHK1vOXBeX2yKw2WJ6/SHN0DuU30o5PtgCR3znqMSFFAApFDmez7e1xVRYbIGTky9ysvN4llOse9NnOa5xkoECLpb9xHLeTgApeFfchok4g8hDurW15xFspklw/yEX78vrBOcmcfeNP2bmpJijMWibNyuISWEMMzmD2/wSfep/QR3zRUvuJk5y6qgQ+jN+XA0LBes1P322oB4jTb1D31EmXadgvjt548sajEPgS+wx0ldbQIRcgcPCvYMfwd35PIIb93SAjORTKfjyx49O54uoLFEp4eIK9ulpJ7x1K26fh2/2WN7W9Q5K7sN+dwq9Bq4ST19Xo5LGNJaB69mAxmclU9edydiC8+J75cOPtQOAdiTq/cgeY3mnRFfh0bxM0iyf43A9grWeDK3/gcGz25yQ+vguKW/FPbxQ9NOfMRylWYyXmUY0Xoa+su48LN61LeKYvrpikkWG/e5uH10UmTLJ81Dm4gw624jPhg3fVsOGxMaKwYYjGg821NT9c7HhZdYI6QK7L3s5ufV6bHNf+atQjkqcRoovEbH55LMyolg7Z/8HeuieV+YwTV+dMElIXOoDWdmcM7K5BhruiY+v7tAWfU5KTN1LYUzMofLgYEP9edi3bVQ42uk+LWlG/4kYrreKg3Zb7a0kj00WXG+teyvJSeXonLqsMtCVzH53wEZPiWUnHPPZwXFZlCki3SD/H2UZ9eMJeygQwMH9VVTizYz+k5MjrjjHKc1DDvh/2aJ97m7f1tvsVbGR1l/2u8txWF4TwRludH9A9Y+i/CaejsEwTtFSRKAfWmkc3uWT475hGOdWTu7TO2I/oja/uL4xFehqVGffqdAApWFsPoGAyqprVaK6xyb9HpmUU7x55Hejwv6uIIVwJqihyNWfQPTlNqlrLOaAl0uXc3YpBbT5muHqTlydmM/7VKxrukOhHHVBs/mftE/ICfMiFDlwN32SbkojChoQKhjHVj4029coJao3qwD8XKlP/9sILUP/eRjvVl6jvNjiPCEyoG2Jg/Nu7aIRtQYIsJplq1UN/acIhn8P5mv/K6uv3T0S2fH3SHaLKVkJCDtfJdW3OOHZMkYfQ8gKyzwcxTYtTaBQ5nQpq8nlx2OYz/a43ifpHc6RzKpcgWPQLLhy8iUOG1CctzYGgFIocuhlk6YIbeB+CmUOUl+W4qLSf3LZChW3NBjgHJFdtXw9RSh0xwvGIEmFq9Qp8XRJoBJ5PDuqowwngsHWf3Kxrov85H5H+UN+rlcoc/DlSk+5uj7j6hp6qVlddx9zQHa5AodZQ/c1UcSSXmG/u+j6t3izQbmo6oevDpZpkxO90at1wSbli1Yj/dwz6WHiz6HykH9D/XkUANeo6lU+JZBbcKoyOkgqXNsG75FG+PytQQ3atO+xHNU1kb3wC2YgSg9mt8QzD9MzjYuIhCK3HRQxbKirTmZwqdBFQMX7cN0XF+tWjikavjl4l6Qc/a7Z+cNtjfd4i+RVffrqgAltcNLPikgbsWyN4hxNiMNEd7FVqsRkqy8mw5m+OghDWWwFUWi/OJiVhWBUFm5XDnbZg4hi/OhgMgtvih87vSP+KhUsx/aDy+b26z9QKmwdx48O/Sg2wpx2X/bpmL8kPH/V+pWEk+3xsxsuaP8/fXbYjLRrZQo+kyqWOfVw8z7JmyTLHqROMiWrUYLsUMO0IIvJQw3qq8/kPpbf3HN+ba8/ApNXY67ALbpCDq5w0u5FKXqh2i9uV5JyaUDTVxdzq6rk65/TV1fnwWUlztf02Wl8r9FN0mQ10WpkU04vzVfSYCGQ1cjtcbLdJfg2DwxW6LH4BCsoq67VtfYzr7KPdEfdu6+v0JaoytAwbwGZB08bMayVtddhRL8vNijrEh7wO0Dmu8uFharuapZIII9Q5GSod3ZGd39T7ChQ/BjVS7xLI/MYyXPFPPkb38rTmNGX9gJy+wDFPrhf6nG/DMH9qx73r2rce1oSPqAf1TtU16gksjP6sMNWBhinxwJhi2imdQJsXX6YWQe37ObI7ar6gk6JSHnKvxQlfZlMdbcOKF6NnJ0WZbONJ2ISOg/pssAxj2DpxSlEiOImamkJJJ09DB9dzMUdTkU848clBHNfx9Nl+/pzb+cHnk9zuHwOqA0IZuL1SKqP+vlV3C2WuXCmCidfsrxZTrOxCz1qvzgFAaJEnLv+mwuWPmXMq4ejpr4TQ2CAYi/cFyjFOyzFs8EQP7HGeI+SqilRlw061D/BoPLyTmjrr9U3MUcawAt67i9dE8JO3opHl1C7n//XmDRZBRtsIjZ/htSgOPDkT86TZ9tdUdJXSm5waIIDDpUHNxrqr5UVT4tsAyUAZL+7BSdBWYHZ766XYiB8fMnyIcDdZF8g6qbfyLFUQLGDcviGhSwp3RcHWzH5JoZKt18cTijbS4sfc9GRyX63x0a02SlG2Yb+EtytQpE7px0X+Q2+bUogTE8B4sAt93WZyLPLfHawPFsEw/003vTki1wOTqomq8/yG+nsZPrufPWI9EFz+YgpXc0icPmQp3FuIE6IfC586GrPo/5Pot0/vCyaMkVSaiXms2uv5CWF/e4iOHlNdroyOq7AdaRvk+oOGmr33dXBdiZlmZ8+u+K6rEvFDf+hxBXjq6LIIHzddxerNU/hC3BswWrUwsk9XYRfo11WRHiKRcTmE2JmRDGPluhtUjkxyvh5SYMzlgkWd/GbZgUyN+XSxx3MFpp8imYavyqTvNri9h4fRDMVTFgr5jZcjchuwy3fyxDLnFy+3e5JcvoOn10jIOBwEf9YkbYm6JnmS/YdqUF5G39H76X0PlyBkyxKEZzDt5WtW1F8Ghwq7xXr4NNoyUvUQE2UtqwluEJXJ6DcS78bmeTnd0xGp7z0CZS7nMz0qrBnBeFQRihc3hjoGFXpjwGKl9wXaziLMI1k7TOfneaf6mzJCcJ+d+emznUiExQq35fp9vHmpkLCMjZ8cwwKBEIBnUInkzq9u8T/EuSD+ewwA0RU24ymPN3Hr/temo+L7a49idVZKEog1xPbv+PdUZneScFccqkD5gwluZiQevy4GnPgVZJ+O8vJrKff4oVLKZB6mAjWmNYdTnLevRwP7JGHz/sKunp8IQtEgG6SNoFuGelmBYDR57zYCs1ajdrPGP2Qd6nT15/4lPeYTNRtUT7E4SYRm9flMxOKAxetjot6XR6HiQRkXuGXBgwHFlodC/U7yH7qQt92Z3F5veyuRzDbYUJnEb1QWEovvLC9VGB7+VPz03FZVNUlyrIoHCVi81nYjCh+Xq7aW1hl3sR4MW3C4xVKqa4808YtqvdzcEHSccDOya7EJd6+X5eFmPvhq8MlLby5RfXfCzGHIvvdoV+4Fv2v/ScH+Vh1cv7Zrud2p1pXxVEqPnPDlaxKM9Ac/LG0w4TLU0PoEMyjJdpWpYRu3TeXY8SSDIn2XvLvcCXu/nFZf/ElDiOl+QSEgXafltUN7XP1ytSecqmTR7s/pqe/BbmWCn8WbfYK3eIcePmQ/e40Oz9O7ndJvpHe/eJKHPaDxWmRZcUPYU84fnXAhH58wflGQjV9dljPU5Hzui8ujuEHURb6Ty4BKFspo+jwzcGBW1XHWVKJRyPj1+XXm6OqKlLchkPLXpJREDtNf82+V0x6q3GJGGpKDhABngUH1rqNUp1AzV134ZzAQme1SknIoSmnZB57FdzhK/pQObRns+owi8uxs78/B/nBnmWGtq+ZAz7dA2UytPwSWQfTg1hwg4w1kANGhBFmHuhc2Gz3ePY20a9wRiMehumxmW2ximrKXeaaxxlIUwFZzGkXUIcxpnPHjot8g+l8PjmrPjRZ9sfTmySrxBgF0+iDmYcYUG2YwfXRbpdhuoXrT1GwXl/o64lsNEAPJzQW7KRrIHCuRtQRuEnbzcDFoyfW0vpEHhJ7ROvIFUJVFWOwYF7MwbWzav7gexrGIiyu/bPJ6C534pCploo5FF5vK2oP2FfNEmMnw7ihR7M0IwzNDkenya1+QwKBK45hJxibrYeMOHS/4UVQ685FmW2CaW8Gp/UmVFVDZW46bjph9Gvcavwsm8sL9CMpN+cFzuuqf4vx+lOFNl9wfdc743Rn7MbK8qm6VMWCL4wNBc4AjysCn5g7vMZdiokM8RTO5R1uv7tscaU6Mfa4AtJAPhKxxdQ4Iu41MpB5/GYWGryr9P55gnP6DB4PMrpv+y/j72r4cNk9R9Qe8FRTPXqXZJu0BKl2SUqJSyBOcVnVlNO+JhXqQJ4+GY6ZhgPzPoHBP7PjrMuJNgAQwx3foKq+Kr6h/I+nL3958fLpk/ZJR5qWOLt5+uR+m+XVX9N2GpM8L+p26H88vavr3V+fP6/aFqtnW5yWRVXc1M/SYvs82RTPCa5fn7948Rxtts/F6j1aKyy//PcBS1VtuEeImCPO4YiLZol8+kRs7q9n+Qbd//H0fz353zzD/f4fSOKUgYMu0M0TFbP9/lys+DvAsLRjfzzFlN6trL9BhB3aoKzuOhiFQu0Qnj6hPEnvNY18+VyLno2D6JrJvydlepeU/2Wb3P9XFl9dNkZ0QxhET78O41d6vcWxX8P5R4Q+0bzi0zFIjEHmadZs0Fl+iclQk13IOM+q4WI1KahRWqNNCLrplnaEcfaHuhEwtee5ILqnT94n9+9Qflvf/fH0t1/cp6IuCy3Ol7/95sMwUYb9HtVJn2ItDudRhNyxeCSc8WZaCpzx5+ULRJRteVR1AUwhmNgQqAhj7NB9KZNdmxsmV/XNHhc9VefmwH+UrwpqtAZqkfGJPGblccTRDof6OaLoDy5yKkjbTuEatmjYY3C90ZDcj+cBfwLTATIaONX7l19+cUbKx7/Ysp/1FBFLu25vPB+mh9jo7tPzOckasxK1szyH25PRJ5k+mJvhf6FxD/dnmO4pGxvTiHrYtNJfn5z9z2uJWNf0NjV9+/rfnrRS+NcnLwiRXLvDPgYRvUN/8ekQRUtTzPXBcKJgxOnZr0/+t7YC3ws6jEB9OSKca0Qvo3XSW3fYC37PdH8GeTeq9xc+E9UT8LjJ6BGsYflwRv8px/9s0CUqjtuESjrkrhbiaZbcnm1J14dMOZE3mhd1kOEZcVvkYcEubmZ1At8lkrxAVeeh/RMIZcCqN9YcNe8vHqvcQOxZjL8BeUQj8Kyij6icZ80tzhX8bOd2vCqaVC0TMg5rVhbDaf8MbGzlDg50L+ucdFaop612EGJrRuCO8P+kTBA8aaclQsPhWMj6dZXcn9yj7S7ILUiQ9Otgl9ATXAZt1M/w1l+IY6qTlI65/PH4yVuIeiyy7M8gDXtf2WPr5PHplwju2SgmKfWBf8zfFjTJ5W2QEBzRK2VvGlTVxCz4XNRByPwsZcjdlZT0eB21WbQ6PPSRixrTeXWj90m+iYTJe1/ipCCO8uoH0nokfhY1QUfrriK6WitRDx+a7VdUfryhglOFcPzMe8wxtHI4g/v5uYvN7OfGYVPNIC4723GBanZ7S7sN3NFuVxbfw5YQPlOhWjPaOavaN4i8kDnz8J+Jebu3hbuGmtYhiFuUN5jSwXWSxvcNtN4+Z37snwCPi1QVnBML72lRbpPa5UhNjesyyerY/TzabHF+XGy3TMhEYLRZlH3g0c0NzjDh8jDShe8C+0dnORQ6xeC7y+wzMc+10dR2OYyF3iVVrVmEHFbCaw6V0LMX7j3Trz0uHRsxeR27VvW74hbnsfYHBF/L10TjK1G6Un1A6DE+opaaIfI36vmOOTjRgyfakGqzM+alc4QipeIceE8JtKtMy3M8YBFPjt1nhZ5GJ7nf2fYkTx0SoTcecaEDxm6HFNgpDle4+nmFy/rudTL5OvzEc0TDy6ZdD6Sr/W57FAmBaqtiF3Es38Bw646Mwb4/1huAcSX7+e1/terec3C6Ukv7BUArVLMXMnAb4oVJoUk9cRHjsfSINRsrBkk2QY/+//a+tTduHFn0rwzm48XFzpnZs8DiYs4FbMeeGEhij+0k9+yXhtJNt3milnr1iO359ZekXqRUpPiU1N3+MhO3yGJVsVgsFotVt/TZUbK2cMf3urtgcsGFSDsxtNYCv/oE9psXYP/C+9s0L6IYitixuyt4ShME76B2qzd68QjNwYOk7/SpVsEp6PxRG9UmSI1d7+S1teB8T5R7uY5+Tj8gyqzrPEBs28NThpA+/L+bwifrB2V43YNtdcnVvNP4Ejl5F2aMcfN4RybVzZWjzkJh6b9z4He5U9A04WySGRXdt28Z+oHH3R0W58VDiCI9j9MttT5OQX5njy/RO09pgdJ6P6u/Q9c3Bm6Kt75drGFd8O5vmz3hU1r4BtmlmXJ9LLnISBPVW2jVA5PFPYt2RdajOTHFaeW2zlV3Ajq4JpWO4HqJStP9sydebeEkN4hfcI5Ja8Jy/ANvyiiOX10EZ9RcsXnlNUza7+N4T1r7hun9HroRnLpqi9tU+4uDbGD4Ogi9qXDBLGneAxATHT17sU7uIvo4/b7ceTJNvMBrgD0QvR/3iHXEzxdIX+khPG7N999L70sk6nLjkh2mML+GVL7k1UHhjwIDJ9GAA17nf+DH4iLKnM6pDQz3nf0O/bvEGbopnlB2K2QHts1xw+B1RoJasZKDvrmyKunkFHhNjYazzaY3pBP61/m79DmJ08jNjVDDcJuaz0lcLd8GnBNlH5trhZvHATyrYOgayOXLHmdslbyLXmUQdaa1AciCaRhAd+l+H+X3EbFhkI9ZFSFZXNT1+rvc1BHCaKTo2TZDiLf6bOgSAD2gF1+hjHdoXWaZ40VEC+TidR2jSm246Tse3i3KcOq4TFuIbPNnYJ0W1jW7wLlMaBcneaVPkf08TiRKlmUIjeIG2sUTTafaHtHRGu+imCbPJP/KWRbMX/9JzrP00TzZJi1Q9xIHe51f5k4s5BJguQkJMXWoRzP5QZYYAUYM+SdXuSMm3fr7n2VUuwYcNHl1mmLwzn5EmPTFMQfTwZEO4mi1e+HEG70f0ueK1jpq020aiPWPH1/ZCfwqzRr8zhErAmcP9jxaf2eJ72iuW8cwbnq4o/CuKx6Sw0V7LHUyKdj2RWYG78qdj4mp4EUvvuA1MO4LtHeHw/IWZ2lM4TjZJHiDGsxqkM53/mhTQ8TIv71N1jJtfF6+npdFkcrSWujqBdr6K86fYpwX7gBrhRUjsvjI7iO4hKw80ORQwUDhtZOrSgDgfX+8iTdhB6jPUhfssjLQGPd7AieKrQnRujrixmivkR7wzscFEA+7vlbyBLnxxF0mBcpyZ1msVbQAFQUWoFqNTzomORQ9YFStXaeNjVgIKC/OiiLD38oCXaS7bzhhx7qgwkrwbyoP5XXlIRcqviK8fQq3fMWzmHfwX/EmIPT3YXnT7kq+dU4L2K/C8XWhAgfh6CQMaIovNQBW9dPBVfPEr27hlMPT8DmiE36/OTxnbK+oQzPx7yCR3VNIr9TZvbC0we5Xdz/9Ul7eg0KMf6DslS59cyek2NvFBdmcYT4nuB8LoIGH2Nvp0QIbLH+IMvz4KLtC4qOJLR7tsYjKm8ebDG9xYh2S2QFwofc8ylFtczq7CFtYH1GUlxmis6FknvmLx3aIsx0fUObbdmqHof8Qh7K4izgvk02Mqoz/gEPcVblU4G9Rdk2Ulw8HrQCQssEnvPuntPL5ElPG7QYKJ7eYXfdKHVqaZvJtxu7Ya2j6SGkHiTV2+ClEiY3GTtmkVm0Y6H6H9zmngrgmxDlGD7XFagfQvNv/zVD2Ok/zCmCJgbH1fXxOdr19mvAvcKwccgMovl4MNJPEgmTY2nYT1A4ONa4M7gD1H9q9FWNxi+iUp4Nw29B9B/qdpwWZXu9Qo80WtGfsod0Xr100ot3lFI5GryyMDfZ6nfiJX32LEm28OywK3M+FOGwI6rgBmo4uZygaRHyP/5JJrnHIbP6Q3qMYrYs+YIvU7w2IG/GC1ldiQnZIuouS7chNoYV8eAz09usJX2A4rZ/nioHTq/lyCy/ZmfYYlXHxhRwmP9olhNB/d9QcVo/fdqtJPcdJ1JUzICz9xn6w29TI7PKRFxKd5XbNZOOMEC6SLADco5RW+EnGrNK/28Saf0LPLvrlOn/IoiTH/QBTjW26XaUrDojT5Y163TujZPPE+CPa4KiuSW5uyYi9A+Ti4gc4BbVDS7mPaxsPb0dPSZWDjz31r+scH/GHOyYsIR8E/wDqFIQpiE/H+/GZm5Q3B4JfB8LboX8Et8M79L+diY/tTOzkfDG+Ya1vi3YnUvGkIdsicqft6WrS9Pl+V8ZIHpxll7Jnj4LfftYJE+FEaaYewuZdmw9gdygviMJlWpEvsugU9dABvZU+rbaaKg5w9ErFoXqc5ht4w2HJDucIvd5+nDh8+VJkEX+GDOEF5OPvTkHfjZr8/zC3+C/SOM3eoxewbK8r8Hp/rwo+e46F82U7XOe131rb4jT1cFQBYDT66xSEdGYnRx1tZ4tEr7sTKp7eE69DhnrK47imyBymeREaIHEYjomYt4+SnE5a9A3qw1O5+5Zw6fhtANVp3OY/9R3ZqcxeX7cCUsnLKajvjnhb5Vn1ddKcLd8dH+s3YJhx4wbrmsZJVy6qIEf4WvZOKSpxZktBEnul8wSl7elWLzW/Igqy7LJtLczPZiq7p3YDM7P88uy2uDAXer/JMTehLBfBI14zdNs95E2ig0s0zPjKyWWRIk4Jzc0+oVdXlU0ofxymH+XZRELIT1/hHCL6RUMU7DyFtQHT70ssA1QYsS2qMPaGwPzNr68DZfUgWfBUOl6EOEr/Kcj9aYiWriJ23R58WkNX0RoV92lWcIPY0M7gNNE577FboRJGT7Ur0B8MPGumZtpDtD3V9WcY52XK2S9RhqMEzNl0ChwPkJ0dTo0+TcZ3xzCzkEm67NNe6UC3znpl/lYhWAaOQ0r0cZbneJuQFdgEGAZIn/mWr6fnemGJd93ujOY99HeXcP9v56eatbfkxsyOuymLm0cGkhEZwqDh5eEUNtgQcSyKG2I/gSYHHcQSwCg8BUF1VY3N/1umrT5GLHCv050rXgSc3vT1x7LG2pdTimbHID9xdQeci2BUxWmczubdoabNQK7NKY83FhMI0XheSVMdcFKLP0g2FX/eNO91D6dxWvna8RbitBrCunzZp1lRL1Gb0GzrRVlHg9+jNyPS2tkx6xbqOPOnMOtymXefKx6W4/XwRDtHX949PSK1eULgm9zK+7X5HzKNfE02/6nxmR9vgoEsAwjD7aDsHhUwfw3BfMDJd091rM19Nq6H2Pou+WT0Zp9+a6UJMlL20ecpd2qrXNP2fVO2J6ZsJ14qNjUiZAre53HuMLR/V7H2+DV9Q+sfJW4HKhP87xJhBvIRj0VnGz/NydtCvJ8zpxdQABgnn0YDz2fyPOo3RFUudl9P0cCEdVbALl8Ibrkvl9BbzrujzHkHiA1TNeYYNP3qf9DfnfjRgRlaOI7o9MsYWVhJdKV+QRm0vFyL4jzRdEJxurVYttqbYFvO7xR2wZmDGtQJNPQMfXmyE50nakJvH6EuLrSELVV5EcXrMmacqNKtBLAgr9Ks3N2yCq7Hv3ge0j1em8tc3c3tUWYtt/YS7+HS2s8F3fXt2WaTodx/RQblu3qvlr3PVGim9Sy93ZawtcuE8xQWL6PWfPnU3bp6lwe+jOl0G/k+RDVWdV19iPKCan0aMepmEFRVE0zM2l7ZTYuYuE/VlpVrcsDCw4TRs0FtJsB9VPPX8QqihiKROktoVVYxi3PiqAD9am6FnYjO11BH40d/s43hZPaEP7K03FtuDHVf2Dft68Gqe51EzyFRn2qzwUnFgUrYQiVBRvnRa1tP0dRexNzG53hwKbA6NpyCXjwILWQh1YcrqNANozwnuk8OSOuaTXBC/UTM6FPJD/ohSral1X1H19PtaAhVY7Bz8bDclH5A0WqqfiB5yQG5zGrIVcmIdEdjR5yCtM/2+yz9gTY1rAvFy1e9jSQtfIP0WAvCq3I/0QIs2rqcXsFnSRSflcUTVZFVypg7tCYMOwX9Prvr73LH5eF1jqagU3nNBc54BFsbdp6h31DBe0i/Iz/Lh4E7W69RnvsDSv78gckEOxXK0F6RteV8/EuPEQqnAbGbewpPHUc23734bPqlLpVSX+5ZZEnu9Xe7V6grgVhjMwDgEivAehLTsSj7mFiS5QNWXYnFI6gqysBzlZ9GOi/KLEPJ+vXCroQyBLgCeBcVujEY/7RelQ/RS72tAZdvxvHLkpxG9sqMHPILshbi62QdE1SDhaQIg12+TDPYAx2srWw1EYXCoNNQWuuGaSisB5uUMjKQwWI1H0xQY0TxY7o3RPEVQqF5Kh85NIPlI4fmdg3fT5UuJifBBdGl6oyuR4LMwHkUR0nAwMCKWVRB3RFqNlxag4BDBRuCHD0IEWgTuBLgHXqOss1tSnbs/CvKEFkrbq7Eiye0/p6W3Rsn3x6ewQDeMok1Vo3EZ23qdHx8xDF2roDcHmP2XmhkPlV6LqMVG9myvCDzLxpeVtNOoNDefiaCouTN0h7Q51jjOv+ONjLWOWN68ePHb96AXb7scVZFSqdJlxzTI9z/RpEf2nm5fIeJciveISaH9iLJgTlbsw3ufRpvPM3VELhHQeCAn0fJd29nwx5cb0uMh3t94RtkXVjWN9jrb5GnDanW0MwoqO+D/KyJklivGf6LrTT2BC1ai8UxgoD3Jm6yAe5QzuUudNRGe/rI3j9zhoA9Yk0O0q1N5B/125L2zZFv//BthH3d7DZHX/EBjxtPa5D08EUW4b4suCdCnp1371CM+pkGTiM8hz8u3KFdhBN5NQKtvNoRfXRcn9Y/pUVbdMPpun+9Rvvi4QkTTCPyM7sifB8lm5sfJkaufr6B+vz8OSeHhveYyMFpVPRrnY/mqbnbnk7XIayr+eh1N7cnL/ZLVFuu/sCP7IhxanLV0G0+s11Pp8n9nKPNV1w8WcpXr7szKj5LIM0ryacgvY35xYmAVXldGRyXG9tmHtxv6wK7Ja/zBlVW8SByzOvUACNHyb3HA84dIXZP04J4syxbiP7Cmu5RQs8BvjCswPlD7yPKc64Al3PO8GZGmC3p6MqeQDO2C/sUVGNLrNcooxmtwNt58474SNZBq5CwhB2hL4PbgYLf/U5BzSSUhL6rbo5h1WYemmXiaKF5F+z+0f+9Y5O3zvkRwXXegPJiNX0gIp502f6kBqPW1ky0/tewRdTKZBOjd1ER+Qpaphr6giUBCrUi5PXNTK81WBAeDy+An6tyKKenUQ9kRquiYvGIDOuqXBacmTZ3VEs3h3n/8gl5vGaPcq847hQszSD0YrysDNI8eNCTzzOn37sa3t2zcvQ8rkx8Rfql0Z/wvrrhPv5lOeMe8JBF6+842Xq8v2XximFtMHYJi3zdEjfmjCdwU+xfzeo4FY9OS6/VS6Sqp4crwKGDPbgnRVskvkZxjIoTMmagV386AlF1W7XL9LBTEobZO7QcFGGNpwp86GB+YZRb+syQSMdMd6BOwmuTR+oORXnnrbLwB0gMXOeC7GebHU4kIY6G1ckMjoRFmSW0GCw6jXzMHl5Ne9oVZ1eiPi44qsV0lWaVIPlxrNTiiJijVcOxbANUN3DP6LlxL5uO21VwET0+UmeVH3AelQvEW06L+HgcXr3Pekgr807iV5vegTBB0sD7p5RF2l5E2emcc9x18m2U1QdDJ1d/dc1hF1LE93WxMHkJcA8lmn2XmfkWP9xDRrJ0UUbrtYWsx+Fjjz44HTh4hHoKShCKIVOlVdNzdKKXgvy02/t5WkNDwP5d4swxHyL1Y9L29Qr3AfM6f4heLl8QR6kNGALkgkznNs36Fbxs1Q4tcZelsbsi91Wq+Dpn4QsGV9hQ7IJlPkV7FXAy1YUHlFvs3EMQYRPM26imtzq5fle6gctnXWb0JXj9MOyEnOd90s2X1hCCdz/2IgNHRKpPQVYuXtcxqhSc0/RQMLcow6k8xE/PNKEXzAyaUySJbjLqyWwJ6LmvKRoJLnAUW14Kib0X/26RcZz89IFWOD3+ZciRC9jQGtM7AHDYN5+6KTCWKK/011OQ2SohQp3Yfeya0L81blNo5TKhjQ20sPb8n4ieImR+QD9QbFGKI92uWNf//dN1/pm91/o/P13RMa1yGKdZoXNB3b/m0YJOC2t4fVK41ynr+ptFWdeAalovm8sWfc4G/mZx7f/D5qLtEWUZykLA9uomJkK9HUZW6q8H1h1cEI5uh/dFsYcT4/QUsyn7PufwO2CzCsX622qTAPEUlKt1RnY/ieE9HFFMz0lGMbF8sqfjF4Yg3sjJCs2ZHm23CeHeBUFj61a3KLzL/mwdn05lnop6N49OBSOIODdWzF0a29wfCL2dVOc10b1xCL1HLKA3aVuItN3H5dY7UC/3lBa5pvXTLtH5xeuTClLwIYiEhLoormeJIYCtDhz9iVwRQB5OGkJiMcfamKYBctpC/PCEduhLlGEK6hQkmBFsInpaxc0NtaoOTFB8+n4RY6jhJKl+Q3L8EhRkCx1z6Fhty3ls7NYFoNC731FnloWAX6XZGhEcyf/P4ph6zJwOOO/TfBh57/wm50O6TW/xmmarXkb41vtiF5+nG24PdgtmTZOCrIPmMfAnVDyn2XffU32b4V2UvbKV2JRQswkIhqA4BiczkJcvhMpki1h6a1f8YGAGaOoHydXQ37QuEct/2JyS5eUBRdjmkUYhCwTWuuRDSgEYMEX/5oTo5B2ZMi79uCfwHitxe6nN7kstLyXGHqAwpYnkL1nJLv/7IysG1j6IzD3YGwzip3L3rlo1TsFOHXYsesoXdh3EdyhJdziJiDAHiyvtDXlXdmvet/n+MWJ3CaewnyzdpRbunLYmKpPI0EOUfz+h0F+ebIv0GUJvR4/4XZnwhT1GCpy0OHyM1k84QfTvVQvEKRMCB9HKcabG6D88+NBYtKpb6ggGYHWFE2YrOE1dAyQAOr+ax5jcl+s1QhtPb9gvsywdxGvY5vbc0kv3W0TO99KUqyaQ/CTcsNKQp6AaNY5W5iv3LMaR2ty0ClFKk8uXPZUJ4KLe2Zit4lMtFCETHlHb/dNTEKbG6J/QS0GU76ru7rQlENna3ySCMrAxk2uUbNWkjCKLSLzr/D3ekJXhZPSXCVFm9W4X4C5bqKd9ChpHIFhePs5PwKoitMc98Yn3A2b4OJ0/S1SiDasxclYURKhP5UUXR7j5CUDo7GRFUrKINUO9d7xM2mU9GdYddt4Fr7BNzFDVy0VT88nlCS3fcBJlr1YW6KgusXkh8ZFsXrCB4AoYbXBUC4U538XeAVISc4J/CkriNsNp5phw5SpLd94N6ofUO8g7tI9fzeBqWemDItWuEM/Xa98g78tv/4PW6lRoNtqT3s36uZn1eaNwT1bDQ4Ydnw4TIH58Dcz2WFuWNxV7u23FKNl8jJIyiuPXAJYWj+kp6E6wbp24P/7D/Cq1vqIb39RF2FoFkrn6M97wvU0zmdNL7/YvJ7NgSKymtZjnoy9SbQBXx577PHY5+BCy36HHqIwLovmYBHKXfz4zC0W7fYS3ySksSGjNWEagwtulHTCtPXL6a/fwR+/akf2AdkSjnUbEeJCz2OHYwh/TDWLJ37z7wj9EeVFBz9D4Ite0siuLpXo0OYKyxavsxdvcPt6cKIzbqfSRL0MXuNVpHYa/Am4iM2Z3sH7zCOvvhrC0dfcn9Jx/QFQdEjHmPMonoMJByr1WGZacHLSuip7zeIBcdUhsCkeIN2KOxq67hvB7srerB2PEtt8sjtZf04xW2Ar6Au8qzcrdqa3GYGtw9tRWbDpdk+Tt8Xr5+RRvM1Y/uI1rOQGpdU/xT73q9jIq9narHpjao8H3dXNdatjS/2nxJoJmDPdzNqfJwiPHpN515pXz17OyeHILSeGA3aE13mMuNm2pBXs/oigvM9SUlTx+NTHqLbBJmBc2Hd8dnYrQT31C5YKuBewdkdkkPxED6k3GppWx690+zWgVikd8GikGggjYVRpvzBPMaYa1xyyyxcdjGB9wnGPoKom7Q9THvuHDiqxcZ9/x3ulIEn1HLv2rx4A3iZvjjiyJK4ziDf3L/yvARoIu0uQRb8ssgoItrTyyly9FFvFT6PjaPS53SfvKzQPEO5SXcXGdPA5uDuyK/lVPDwh2Tu+U2v4hXp3fvybrU38oqDMVHZtW568VlG46+NSmD0QwbE6AaZmtkW3SIxG9CpYaPTG9rCUf4Q3MlZ2/DvHVJRXo6ryhsqQaL0UYWn+zpxXo6m74siHeR7k6EPk/LRMRXDs9IKtg3BeZL3VfATxPU1moi5aSJ3MU2HFw+UItoHdoH6cnU7ClPmFYFZp8VEavz2f1+8hY4NdC6WTK3dTXinSzSTekE+hmAVcjCZYxTJqP/CGLknyH2aNGd65CEJ387mRtVO6N0ecmFjy9L79VR1vfgA1CWWzmjYHXcsLbou4jhISuBPwDfeQSHFnGZhpFdxruU2++oUl8QzYJGOjip/+qKfe++rXffhrvAVn6AxOuhHxdep3XerGRX4eoMfeNvlpLvrxe8zkiQCEkwufLhid/UqXty0PVCFrl+/LmqvJstt08PubI6VkCi0Z0AXAeFeune/yXkzFyS1Z0lWh2CRGaF+luz+6UzYwN41Mnu2r+F96fEXCuwWcxipIu+bbHvfw8Wn+/TsjsrL+fXEyWe3TLbVUL3er0WnU88LqGU4RTEMl/jFgy3+ztDYZrenuMnk0PhEu4jr4gPN2m2eubAJyoANQK823+T3T+6xNSLQanMP2tifCrFwvlNxcoobV7lub5PYrjt+mdYHoN4gyTsi7XdRw8V7mVtLL2JOWqg7FqPEn0d68JVHnAwpnPF5I2SQa7rdfvOfkr3mxR8a80GYVsnhoSFyOp9m18lGAGe92ZaXvbVDT1lPZe9URVl44eDPu3EA/p2Tq2E3IOgL9AKQqaZsU/KsXHiLLIUkZ7uZVqiTKCMuWn5fCk54oHYlUupvL1mt7iaGcK8OLrDaKrWGF5g1SaxroQZfV9Nf174EK1c8276NdKXkLoWBMEeCAWOJyjLU6EaotWl0FP6fPlyz4iaDrdUH1Kr9I4Tp+dYKDnrzjZuAG5Xo9njzZeIMXryKozrylPi30NC325YnqR5xdxlI+Ggavh6vtb2pVdbYR8dWGbTbEPbzXcJcF+/LhAn/GdFRzY0Fk/BOHFbd/SZOe7r7uHqVFWDVFfjtlMeANCd6LroazmWBjLjpmuc8qjb4ZB1zPETJKtiF2ZNg50m6lsYGiv2Xosq7kUBjPjJNfVbX3y+BuKU9c15Gzyd2JTzCg/3qHO6oAGQ+NO7B5ydhsv6AQTWw91qHPKo291AR9oJmvozaVVtLUymaRzo2pOBrOzk6ZlpRSBBn8rHKrOIS0iVzvY1Cxytn8XYBst3969JSfcLymtnDqr20+DGa6LjBB6luTPNjFDfN8Qk0Czm12kO/by6Vj5X9MHOvssH0dXAHUSAGlV4SCTYOeu7XqGEI7zON2einD4mkvKs9s0twhP7HqGmEuWMo6CJyrXahM9iNlsqbRIe9Z1dUKB8dd/zHBtbN2hHxg9v0fx/rGME0s30UFMpUCwtXHadHdC5WuU1xwPPbHHPptzBW372/urefK2XdB8Vd1rHatHrJVk/jfKWWE9D6A+pYaQZLJ+lufpGrOZbaIgUpp6q0ozcIdylhFh1WS+7Qn/ZbL5iR4/aAL3ukGN0T2KH//W/fixjAu8j/GaoPBfP//6c3/J3CRVPsOfzthbJuprzNfRZsgOQsZGigOAuYgP2EDE7X8NhiTLGGVVCY+LNMmLLCLsHq55nKzxPor7/Og11FQPlNIWZP/LO7RHCQ15UNGtMy6fz3g4fjtMbwbG+PH7L5xQacga/ovFrzLcDkjQeLSHUiZ+PQ4RE2g6CPka3FDyPpN8dQ8sFW6a+72FWR5+nET0lPfPKvzEhkEEcsCSCQRT/z5eMr7eBfwChPUhyraof8jvBEMqCKqJP0EhNRaQuQV0xNM9lXCmcXwYmzPFVBQy9sPBb8GMjMPYdVuH+GqItOlMBZSSCscBBs3PYfZI3Vn0IC01IVq7IGk+m7zcF1GBWDRyQo6aF/QCfBCQw2109Xdhj2t+m0R2BHwFPHpfwmxgEH/CCJFIjtZmVSE3myg1QWMeheg//va3Xwcz10FqQgF5SO1vhy4AYJzjwqdeIbTua3hxwmC+RCcUCQG52QSjDddoU9qMHfybHuA5aqJNph9qDKESWM80BE8gVcrAasmQ6pChmSRr5JRuoCCOVq5M5ngGsZIH3E8tVec4pjnn4McGVjI1sn0Z6b3DlQatsUTmzy8NLA17UqxgIha6edVIg6i0345m82ooMtm8ZpOrJpTpMJx6DbYCDt2PB+/ca0k5DAcfZu89Vh/RBkf0Tb5KfIRG/PSJH4w2qhoB0XXX/BZEGOSkhhGIhhqdoXjc5pOJ+mlHLRog/nYzGMoDLCAsItL7FMYTbDDDrsIk0qMzYt12MeIEPv0aTCY0i6ciThCHliNOXWzhPFZ08/7Uo14aO0w1L4sF47f98dB1CvxwWjb9M2uT9l3l2Z6wnKZnqtHH487Apq8wj92PkyiXwWt1CJfAstWSPIFwqV/nS8ZUv/GdXczG4ndM1MURi5nRlM8hZoosDhOJmfDkfroNTUiuIBzkhA+HvrHJc0hIxlve5saTcFD7m1TE4AZHs88Zy9wS9zpR6ka2OytlcgLSZywJc0ngSOqb2aSwPmkelNqDvBeDb0ej7Ew8FUvUc62Ejai4+X1S88vXhF4pG/G6VaUSmlS6mn/coX+XOEM034H8xn9JuotDGERH+H40OoynykSPze1PbwJjbx5vMrzFyQQBsgZq8PACZE2UTY/1s4sCUQL4B8peH2hRMuky5xsJ61v4sHCJkJM6v1jwuM0tE+dlsokRzVZ0VhQZ/lYWqCpXuuq+jNk7XEtggvmvU97LSSlTIzloHNJKkvE4qIzKadXBoeu9HNGtZfVgroxtF8zhmeh2ci5M54LEzKuAje6Wb1KiHnQh8tHeDkkSiy/rwq+HNCRZR3Td16dIy2ivGy9Grg5mW5tRqKZXVmbRMMtQVYIHX5E5f5mXNgARkKQd4ZUNRJnOsHyHRcnewai0BQjb9KrNPB5iGertfo/W+BGv2af2bHs4wgbjD+Ela3kkAigh7xBEEUb9htUcXOnQxWcsGJcHPUEIlRVFQasGlk3DMFkS7EXINYWKilid8WEAC1Wu+tSGFpyT1M3Owja7wlZRMLfMc8V0xlOHLsWK6HAGnyRxX4/EWuBIMrAQZkysCAiXXmCRZE6ByTwFEdOd8DmlDK5BNq2gfYkyHCVFq1sv0t03nLCGs0cEKHCDREvZ/JjiCFSE6mCxpBADlfwdzNl88YI6/b7rKqNzH9U1xPPPMmI1ND4nWC6jQiNeFsQPR6ke5QxatOjxaC9N/g5XJ+qI5NFqv0NSeb1jdn6P2tPIuFdy0BCY9ak9kXK6lNjxzUKK5aT+RwWRBiK6GL9j34euS15I6ZhUijVwnEh6DSUpiBxbyXCH+NzSLNvsD1rtLs8OmEPhuhgEi9G2fSK+RHHZCqmawjByMa3kMnJ10KwbhpRhK3kKI8oVtQby3Acwt1jPfpCf8OXTTEfywzl3v0ufkziNNvMlM20wEJ3p7Y9Hkc60JUdnrCXlM13dR7t9jGD8bSdxcVrCaHom1BAi82eThQeMMkIfLU4lrd7nWoqRyUQQ66bFXsCF+/Uo6ix29OgMxmO3ALFavpN3HiGa0JFrJj9zu26v0qzcsfT9vutByLeldkwBEvfrwRdy6Ggx0SHzCsFDusfrqaWADToUg/rn45CDipjDEYQV++8fWVrupVLANRlMXv3zJLsJG3CIQiDRkTEmoPBoDdThtQQVAuBtPmMh5WUGpaM/l9NqHNZ8AcaHDG2XqQspQpNbL4bzOqn9wtCaTYg+oeecpdE4iPpTDbYCDt2PB19/qiVFZ6zZ60+1FRL5Y/xBVXUddRGpXEUHaFGDZJnY1vMXYOwJ21g5Bxsf4AkJ3VRuQWfBox3mE77LlwJlSRSflcUThVg9jLtD6zTbHEYpUBUFAl7qhgevAZXkHYSX4SbbBClKrPI2sTEFIPUvh16LuCJDZyCR4TPP/kGonGmFZkoloi81s2uLJuP155ycFN5jgk32uoJTdS80szmPOYiP2OBocpsLZOmMO3tyc1DWgLViriNOQMT0dcpc8sUwnHfjY56qJUtUi+UQg4AuqslkpyPkkARm+WEd84jNhGEdZoIzd1jHH/ixuIjI0f62zNZPUY42X3HxJKHBfhpH4g8bLARg3Y/hNMlUqe9bWrRkApyK2UVEsHVggmynNJCugTAH8ZnA6DGSAE/SZmz3NB2XJWuf+aWwUGtoSaI2mY1kLWfCjM5rNn1KC7R8O5tiOcSg+vWwZagjZPl29h16JtJ+mxIAeaOcDsI/CSAuoAN+P3jfJUTVQXgyITnzugmOmOPLEZfJ1JCtrAjTMt+t7f0T3tPSkIveyRokxRS77Y+HLUAtHcvfxhpUmccIxtt21gJLzsDhIH4Ik+LYZGI9CZG2W6LtMO8lLUVj7/eOfmSLerum/7nH9NlE4GsUkwV+UPawgLKASO/LwdvAIj0HYf2K8jSFvTuvOExmopjLwtzH7aLMElr4HAV4dhrooM2h3DszCV8OXrGI9ByEYmnjqqc3V4xk8/CMFiPB63F/zkNzyhC5iLKCK+McsuT4iJj0Meqddfofj6c0+IA2nTEXUAp8IEIHsUstQcym3KuspGv27WogW8sP7lmCYE0Y6mMlV3NH/Fw8ofX3tOzn2Bz8LNdgg5aCKht+nebJGUiWGrWQWTRH+BlGICUUaqm7ftcZj33rMiMUb2+jV3ancZ1gCtfT1Ybq2kscuHd+6388bGfAgB6tMbmZWIx8NJ4iNUW+5jmY4wAkSolaWLeUnYD4FUoTX1W/72zyScXgB5mJD+l2xf2bTqTc09BrJ3gc+t8mEUhuVBk2ofwWKp6FkTueKJ3heiguQtQO4uQ5n1RNed40FafZj5r+5Sdcctm+6ByHyByMqJw9PuKY/IJWEz3IbwcUAXW/HrrzvCNFa+uZ+bb/bB33koEME5wsNCtNi3pv64knSfmhnQfGefOJLXJ80L7zeTqJkU9TUuNdlL1evqyfomSL7siKuCAWPUrWrwrxqhuIotX8qK9lGAqiu7L6JZBMQHSFkYeKDj3PpHQCliEa7I83mZhBJgTOzycM6ye0KWP0EOXfG68P/5s8hpVvJEyo8GGaO5IhEVKUwnp45KwLJHUAZTrD8v1mk70/S1SizeUuwvFZUUTrJ+aavsIK28e8QksQgQMx79WXAlscfOEXmC6dgem8LkzUZqsWtSz5mbqOlL0MLaKoFIf+qiJirc6HJTQSsiUKHybZKjnkZfIWSMrkrAouZVrD8fgtQbY4PSUlxW1iT2JzNBWD2XQb13PeogD35bd8neGqNvfENWn4sYc5/sWvB++gHtJk4l6asagi/hEV6CPKaY6K1VWW7qaTEnHwXlye+Ong5aNHkJaRzU3GUgTkIX0Tj4WIRzcVswnH9W6fZgVB7ZGcB4O6muRSIuAgQOx9OQofkUiTgXcIJ9tZ/UOXL/OLioBDL9X98YmKSNPBiQoZKE6rAFeQBvd5DVZfQUQfQIj/GOZ8bjz3XoSNo0tnzBq/2QTtPFp/v07ui3T9Peg5KYiYSZAXUJK2OXh7SUbZQRy6ZHK3/AdMyxO6CZ8zucjc3K+aPqKkpE+w6tgk+qfikiQpgUAg/udJhE3EeYAL/ynQVUpL8ATS1SNJZ0TWZTbHQBrHX9KCGIu1TNHp6YAyV1K7k5LfC2rJPaSrfr/RTbbuC9dHa74ZeBX64wvadPBR4yrQabNsKJjCx6DkvNGYc8gX/eEsyZ8VdhnXpD+rzc+TaC4nGfO1MUrYtSDZ6lCctejtRbpjx0xNBcZ1mVp38UP36962vx+RxpKy2mi4icUIrtxrW7o4YPlkQ0nypJeMChtPLD8NbvOdDON0a6iOuC5TqyN+aOEEyP9+ROpIymqj4SYWI/rvYQX63iyyBv0prH6cxq9gLkme1BHMnmXIT4PbvCEkFIMvtAqEpkISOk2tksTBB0En3ZcjUksKhhsNOLlAtX9JpaRrAU7lRNrJUqg8aSgJl5YiS22PGUNUmG/1Dv3A6Pk9ivePZZzQzIm6HilJ/8k9UzI8AH8/0OiINJrejBiNPaccCh/GbnfqVtI5n02i/F7YQMxYoDAJnZchWVZqbVZdpi/Mx6O1DkhVHcC9s6U4Hd4ds7kMTXuzfEn6FK90UZEeKGsiKtINusJZXryLiuhblA+PfbTXPSra0NU9Xv/8U/UzN5n17zQObRf918+bbymZ6+hb3HUZKJwe4OjlIirQlj0KHoLnv4KD8A1GhiL/otcdwDDtF2iI9uMI+A/pOorxX2jTzDgwENAGGhJoNjZ4lGxLFlg9HLP9BA7VftUhD90XGbsxytMyW4Ojgc2kRA5ajmBxi7IdznMi481V3ACDYRNo9GGrkZHF9C6DUcXP0IhiizE60ziGaGM/g/SwLxpQm2tVEHbzUTZC812TV60ZImVX20LFsbaR5rCK8dQDjY7QZnsaDNB+geC3H8cIoK8fQUXYfgHRbz6OKcCCqEqiUn6QrQ2S4d53UBmKTUYG7HzSg7G6T9Aw3dcxiW6MmqE4N19AWW4+joB/h/P6efAAfvcJGqD7Ojbl8s1PvfNpb3u3eF2UGTTf7ReQRc3HEfDiO/zBGOJnaCCxhd58K2jqNVDM/qputPoYsaTn46RGSfkYsT6QjhE/g6QKLTRlj1akwRnawYoUbKWSSKHhGAooxj9Q9vqAdxCvxc/goEILvbnla43Ippdvo5hhvpnp4G3a8CscF/CGOdpFC7VBLz1MFYpj0EK1CJpW2qug7jiyGMBWKjz4lqa43O/RGj/iNTv8cHn6ZVjJ2qvwg/toYwp3v6kDrYdbsbI5uDMre1hhp42XCUa6c/oQQSc1/qNitth3vXG+RBmOkq5KwEW6+4aTSDIvOp0UeCn7jeD7ZxmxXz4nGNoIxM8QDmILO+7os2Rk663+b76O+h3lCOlhYiyXvaWVkwYaOPCNdbDh21vhpY2TCT62UlOXJdEVnbq5wTqqe4yZM22msqEp034CzZj265g3C6PsNsPg6Yr7Bnqyus8jg3CX6YNBuG/QIOxzvtI6WrG2Mr8f/1ExkJ4PkDWWDaKEX7fQgf9HlpZ72SD1R8VIdYuRkbqY0ME43SdoFPp1FPrlC7EPkyg+K4sn6gqudlapI0zdHMJC3WMEu7o6zQCN+ndovPqT5kHoM0200CaQlJ6ExGaqo5DYcgSLP/AjsaizzQgWcDMIC7ilJhaKkdWj6U2jRIa5b9Lp1DptsZafUnD75r5JB6k+jwxyh54JwbcpTopcPmFgK2hgsOGYXV1Xs4cs6fYTaDu3XzVHkMyY+Fk1kta89apHD4brfYfG6zUZnUOhyDAwe8J3eN6EJqP87Nf0A3jabwLztd9qzBM4rDM3dAkO24C+wWEz08FlJpusoRYaemaatCoUMPuSlrAcSBoboqOBhx4C47cNfA2Z4Y0D/xW8deAb6A9Fh1APV7UYGbJqNHqpCFEmpUiLkq6OxpCG7huIffdZQ+vSxf0RFU8ptBn3G8g0L99mdIpiqanHfYOnJdY04j5n8kG4b9Ag3OcxIwYliJx0VIpu2AQ0agatxs5nBAZih8dv4C1u7zt4ThObjF65peA1Rf07fMWWaly7dOn/hyq6/QTfdTZfdVBv3TIwBe1nKSHa/lEoSfxwUKgVODbU0ACFkbHHBx13lVSbgFSD9L6DzhCxyaj/EEz7CjgSwXawRxFsqo+IevjRQcdPzEKK5+EJWfgMnoiFFqN3ubt9hLeQ7637BN/lNl9HL1tZtsEHtNvH8BY3aAFfufYaabg1PqCiQJmYHwZ0ckANZS4PqK2OM2cEDaCNwrVjNHw/jyXg0xQbqNxwvaajkx/lZYa+Irx9gqS59x2eeKGJ3oDvMFnWOczpYRPFsFyrkZF7iSAHw/a+Q2P2mozp/tdkrdjr+K+g5ucbjHrx+gnlAM9dvwnsreu30hpZztXed/mYulyVJpYaDC1tCUYHyRobxG+oVCjYbCyeQ1uZNhfuCgyGTVQRQNojN5ej8oEHLVR3rLrD3iHabCMP1eo3gA/PYpsxJmdpnhPgsXzUYROQyYNWo0qzS7oFqMvuI6wou+8a4/AJtcCx+Aay8fg2htG3IzGx6uY6Ubkr2lT7briBL4/SHLRQRYPWjdD4xUxzoaCIyRk2Ud1MrM72+xijzUNat8cGWIxE5sDN9LDh++gjJF+DgxZ6aNTNxzFoogE1Qko0I0tWXTtjudQMIdcPJRcqluqHUAzSWQ15MmgiiwgXW2mcI9rkEODhof0qOzG0DTRCj+VDCV9lAci6Q/WelMOnke679EDQNdGTauD9p0y4gaYKGQdam2A0hobG2IMBucdN6icpq+5BC9endSyrOvTfYnWP2fqvawgK8pczYk/Fq5kKis4rmF9E8nVZw78gGucL3DoYU6CXUh1HlI+fzNkxsCt4xZmv7mu2Dzmj11FOquy9EqN07A2SEhK0mYBQle+OvLPyIcq2NGjNmJV1RzkDpASrCFwmC8m+qVyPYoMQS5B/X1aRDL0fsyOteiS2qiDCxPFNHJEc9BAfuLX9JE/XzEkUnkCt2rdXQ0LhhgoBF19/VbINP+0S+oHPulhv9Wstc9LrE5GK6H4T/+T2Tnesn+yBnT2JPdbJCRUbep+lCUlvTxXtwVexMcobj2twUHsrCZec7AUAQRkh39bkjb3M4xLYcI5jWmKmhaxgQq9pOBZoyZAD0U1alg60nOpB25BLoPemWgAgeyhtzobmAa7STBk2CmGq9B8Rs57SB8IWJkvzClR4hwrYLVA7OdrQu1iGuuq9q2jMiI9cK0tG8irXgmzhoeyqBQwQDrf0grjYD3zcW3XvffJOfuOAHCcfTi0zJAPCfzHkt68+FfM+aON/xvsO80qbyV62m5Op8qcrjBudbnKi+kkDGFGyhABgT5Atw49h2SM3eXS6eZrzWdkjPElWLBSwnf/FAt3rVDuKKq2AD6kQrn2M1g3YM+TSkTIJbhCcWSarCOzpWR4WyrTmRs9IuPqdQsoVtKkPvoVkjIkg9Tv5t1VmYguQxAQ8j6o7hJQTIFuLAESVfsXaim28bTePNxne4kRhxg6a+nfQGciUA8liahk5vUI7xfwBqW6qiVOksJmebGnamBWf60bKDa3uoyQOM/TwxMqT7qhhSRLuwKDHkuZ4YHDNkvGTobRLsCOi7QR4YYsRQ46QFe2xuDtpSDkxbBviXCTJCMUzwuOpqE/auDwMmgaThmlZ0EtZxZvIUl7I+4Q09IHBIRZ5NvMhkselBWweTGLmYYkkmdg4c0Y6BmOTOkEYD00z65fFvbsiwZkkOxt4K28BRnGrO84XPYZoQBUTOylgS5I1+ZZW9eCmIqyENjGrFroChiHIytA1eetwdxeDQGvh+oL7GoQZiuAzeetRoiTUAGQsgyWKJIO6p2ZtEBMcqDUSNPIcNUmxGITF45u4Tu9gS/TQ2ClmrbTiqQBCziAowSZjhSpx5qGy2EJMp5dNHW75YxGQfHOQp1TBLq3uoyxQ0q5nPcrxUcJUpBz1xdIBP/gxdRmrAjIhUzSga0AOv8B1RHe874RyO68SADPLrqTDarNUDWcSpujAFdK+qYDDqdzszXjNbX7yvTzQdU6TUXg06hBuqPAVOocd9rMkV0a7LAOy/YzfR7t9jDrA8jnvtfSE+oSz3eZ2XokviYYkS1oqbg2cX0UNclaz7vJ01C7kK4zOYSP/ZmZ4UtuHvMrAcaDV+Aybh4wPEoWzrtyvfshlSb7H6e01C0awkLK8oxhORW5J8orPGi6hl28zgrKQm7xDGcw5Puw97Oh3ZmuQymmt2jgjOs9sspXIg1WtWK6dR9QnXrdNMnrl845hIznK9s87+inzWc/uR3+vmHSffKs7hFBbqvwgAhTP75AllGq8dZv86fZcLFJVRFBueXodQ0iTTpUIBs2o2oM561iifo23g2A7OXm2DweF8hSsF1x9wpZQlTD0WoSY9XDkQdUyVl1MrDz4Fu6gOL05B99CtTkEKMpaG55YU/Ndky9Va+d5XQQ72vofch70m/glfFC7pOvnyZro8FccZYeN/B9lw5PaVIxZ3RLD5ynK0eYrLp64EYaEj3XxRo7Qt18Th3WV1ruxZ4Swbjv4cjbAHTwRAvaUrnitYkOeWPOZn3dt/oi9/OqFudjT1ioaUYhckwAKka+z1PUDSyiZkwhURVKaQsr2IQwjRRUoBkSnrJMftoyui/FOfqVjHtY0Bafka6LXwi/R/ZJcrJu03JY9eWz/7+DKqRQbekIc7DnYX5W1v2wPQk1Zm9EjX7/hIZ35hCpmSoUnaRlC1YHV2Vh3ddE1V/KlCxlq5nc1T0eyUEZuZIsDW4bZ3IDyeLXuVlW9c/AVaixvaVv/Kzx0Oqh+Fb+RZ5aq5qNnQC/PKmX1C2t9P1KT0AODVCtD3jjE4pidFQoXgbStf0/B1GyAqz6ugJqUgObQ7auQF1kZzUpwxipjqmENw6M0a1za7DditcjVdYILHMWK04Oqg++TA1wTs956RupcujOjMSmGQ43zRdo3ELlKSPCxS69QqUVy064852pQqnPIOVVzxVYOlxKttnR1fVAZHBkMzyxRbV1guxC71jRkj5EbnMw+eZ5ms6ngulKYp8NG/u3SQRXaqqe0wKyN4MZAHnVYdMGWIa/rB7VqaxGWVYi1ycKdsmSMeBdlr5cv66co2aI7wtquvilgdY12UjFFLLhaMwQupioaYHwN2MrqAku8OjKB/aFNvdj6wMgeFnddibVXAeLH+igIAerCVvQo6r1KIUA7vk61WnM2gdVZV1cY1hKK1oqbZuuAa2U12vsqUl+nuKwvtozF4Wv0khPrHpS/CHatxGq4SiaJbeWEQRV6GT2qyrsy1sgYEkxqhDF0hYbv5JWshUjMoPrueGw43DqE3SktH3zfxtgqa4ravHfhq/yurrJ0p+KHqnkIhsAFjWvvkrI+sTMrHlIDRnCND50NQmniUctF0TqkzQKWYGYg1JWVbWJsTdihaB2SHWDt5GqjUpZEtmVHV9Z51YKWMQNo650QAMawhDUHRlGW2qIaBVzwWbmvjPYJoUVGCl4zQLrVq/2xSXEPMdbF/3XEvCwSCx6v6J9gRnegmcq875d8ro17WTHnQe9hIecWgqI8s115Nr5i64p6YS/SJC+yCNPjHrE22hXSlLV5SFfDSq/Afu0L9vjCtKiBI8qupAJuJcRjVW09sJ0vSKfBSa65miij2nezsoSr5asrKEL53yHXHCGGlzqg+vF989hNVmvYjbHdMzo1u9p2auS1n+nNQDZXsFl39oUaz8DW6AYxvDwBJa6r3VNRu9qNsV3VLTW72nZq5LWres1AtlCRW3f+e2W8Jc4ON6jh5QqsZ965R6Rlyl2ZzD1wHuNc11SDDL1X1DOxoDaZByXXta0nSX9FPhX/Y01gsUnJHB4/xsvXe5wm4YMR08WemqTL6T0c9lmJ2yhr7aBOLLn60+fM5PH8jr2G/o/6/kn//ZcKCGU8mWWUtd9+/4W68nZR/QP5s7pV+5huUJyzX3//5a4kvXeo+usdyvG2A/E7gZmgNR2zA9q0uU4eaVGlPcoYATxGTZPmc+toKKJNVERnWYFp/njyeU3WEjlC/fwTCxGkN1zf0OY6uSmLfVkQktHuWyzc+/7+i3r8338Z4Px7nbDQBwkETUxIQDfJeYnjTYv3VRTnva1PBuKCcP8PRH6v5pIszQJtX1tIn9JEE1DNvndoj5INWXIPaLePCbD8JrmPfiAb3D7n6APaRuvXW1p6m0U8yoCMT4TI9t/f4WibRbu8htH1J38SGd7sXv7v/wcSeHgEbN4JAA== + + + 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 + + + H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2snZFKqm6zPm1VO0ZRpEQ7urBJSjqnX2ihSJBEKzIiOy4S2WPzZfuwn7S/sEBccXHcEZFBVb5IzIDDATjcHQ6Hw/H//T//72//836bPfmOygoX+e9PXzz75ekTlKfFBue3vz9t6pv/8Zen//P//j//j99ONtv7J58HuF8pHKmZV78/vavr3V+fP6/SO7RNqmdbnJZFVdzUz9Ji+zzZFM9f/vLLvz9/8eI5IiieElxPnvx20eQ13qL2B/l5XOQp2tVNkr0vNiir+u+k5LLF+uRDskXVLknR708vt0lZX9ZFiZ69Turk6ZOjDCekG5cou3n6JMnzok5q0sm/fqrQZV0W+e3ljnxIsquHHSJwN0lWob7zf53Abcfxy0s6judTxQFV2lR1sXVE+OLXnjDPxepe5H06Eo6Q7oSQuH6go27J9/vTq2KH06dPxJb+epyVFIoj7XFLXwKG82dtvar779+eCED/NjLFr89ePiP8829Pjpusbkr0e46aukyyf3ty3nzNcPof6OGq+Iby3/Mmy9iekr6SMu4D+XReFjtU1g8X6Kbv/9nm6ZPnfL3nYsWxGlOnG9xZXv/68umTD6Tx5GuGRkZgCNGO6g3KUZnUaHOe1DUqc4oDtaSUWhfaunyoarSlfw9tEv4jcvT0yfvk/h3Kb+u735+SP58+OcX3aDN86fvxKcdE7EilumyQqamzqmusn9KutVdFkaEkB8aoR/a23mYTnWbr86ti83BcVcdZUlXzEyhPs2aDzvJLTMiU7EJpdFadk27/KMoNKahRSvgjFOWAcHZaXOE6m58lL++KsjY19edfoswtUeuaRl7++c+R2HV2or1HdUJUGGWD+WWCNvYaVWmJd90Ks0B7y/DeO7wlYr65KloNXoVK5gXKN6g8qr7gzS2qQ7F1WP5e5PPToWvqS5nsiEVVk1VL6rtNfSLJP7h5Cxv5K8LcqIygL0tclO0yrF/QLZThVXI7vz5svv6DrBNXxVGaRVh9qAlV3blS8bfnkxWotw2T+2Ni9NwW5YOPhZjcP2MwHIxEdVsG8/BPv9itkI4M9BpXuyx5+Egl0UV+rPnnEtV1OxZn3iGa6gbfNmUL/azHc+Agbw56OQ8HfU6yJsYC5thsSyszdb149l2RJhn+F9oMbXpwb4+jY14J4YGN1W110+E2tYDll+S3TXLryCIAHjp1iNDnTVk0u+UV9Nj+vppeSr4/JN/xbctAipl8+uQCZS1AdYd3nV9PlqzrCfy0LLYXRQYJ9Ah1fVk0ZUrHVxhBr5Kytfr9dMrYrUBV0uM5aBB1W4aF8MVM4tLPTE917Uo8R/uk9j8bdImKY4JD13qEPcxpltyebclgT3GGDOSO4tq5qIP3SpF9AZ4br3gmuFZntqq7m4wLVLU6rlIrUAFSrUNVgKNu5NSoEnpQuv7WmYA6ioEm4DxoWLOuC7WuBlrvZ+sytL6nLcxZRaXrPGtucS4pEVPVq6JJIeUTz6oKVwqgbWVUIV5K4RyVW1xR4bxAaXt24qwQLlHaUDfiMxHXQRGo24p0qOm6+bc5ULU97XFse/KGzt6yUniPW95GJRUreFkXefhaqDKJsB5SkmEDeJAQs6h8HIZ99eoZi+ggvd7SO5MEnZYIXRJW3bXthRnPV8n9yT3a7oIP4wii3hCneIQp0Fc9Smv8PfhMbIjc6Jg/DFdU/WirlETNMLNiEncclnrMz7ooiPy7KyRarWr/PSghdVux9hL7NEX6UI3Zz46jOR3oUf7H/C2Rj/PWpA/DdpRlxY83Dapqsi/5XNTBCAN8ItI5EZG714QxP9VjPBz9eYW3xron+cazZqivyW/bRjUNuE3jCmSTjiuFLDit2ie1j/LqB1Fnyk515dedGuW7xRTJKl0oD9bhHbIgTd6hOOhzjY4iVHqcuvxDs/2Kyo83VINVYQOYw6nbiU+YiEGyD4mgS58IuVqHjsboE6CuWWHkO6sAA3WDCjZYT7CIg7QFiyiaznjyKqlQ3wFK3cHQHUL7jNLZkclZRi2WgLlmH2JbE6cEuSCiuB8Oq4S6rYFGbxo8ttr97XrsWZF2TSeQMY4gT8gsZ7O3YhH8H7eh06LcJnXogj1gu0yyevauH222OD8utlsmkHnGGzXRfExHNzc4w0RcQqkdx+P0GmUownWVwXF1lKZFA0SWz+G7isNH75KqPtsdbTZki6a7ZWEbMGLQeCWimvJjDu4nnYNNqvpdcYtz3w0qqd9yEVHVShSuM5dkzXBHcd5oENPlkTgTdorLqjY5UV/GuFJEZ2ORhk7JjJoCzaPMENHOuySPcUhmZ0Z0W7fZWeIVLus7KiBGeVMay726UZzbDeO5ZsAmE1kulSxjAMR1T/cKZxkh36gXdd0UYYG+8iDqDgtwrr0Wd0G6bvcw15O1L/dbhJF2oEpAaPsZdpo7olYelsAQamKbT211PT65p8Z+kh019R0199MWSOcB0NUAp8GqgjQndrVcJ4iYyM32vKhqeGxjMTgQuVTqNQDi1cXuPr66j225upN8MdxLAca1m60/DO5hWwR2ji+R+iUUu3bpApH9N2GRf7bHF2DXOBCwizCE1FUFmHuXfyTl5rzAeV29xQQJjUYB+y3BKXqvhgPGoAF2HckQBmC11kjAgPoTYNQKUAR0VYGXd0Vb/zgp6zOyYYH7LkKB5FcCSbRXQ7oS/kuSkc2fjnk4CLDfMITUaQVYkJN2ZAGPW4XbbZE/6xEcPHTqtqy2c48pEYXNfi5S2gHzfi5OQyafZJxWzJu5WO00eV3KV/+MJ/w1It++4zyVPWuGFpm0AbMNq9c1L5Zq6OXsDf0d76i5mmSGq0aRgl7uihwZN/iRdERyv1BLYe5A9WaykyFwSR9snRFmWsuFIsluEsudzSVWSLWdEyDlLnIAyo7yUGFnggO5nI2N17hEKTV7nvU4DvaGui3DgjnTrc02dK3qfU9R4uCqaLHNP4p3iBLxrFriTufVXYmQbYO/RmiQKFpU4lRozDPMb0iq8zkJPoFaw83Oee+u9kqyO/qNv9LZrQKKHTOnt4GVCgSQd8ogVNCeU1iYAhYDDtNhSVC3pdoQxI0mnGtl+fq1RN+xaQcd52BqDTrLyyb1lHfR7NNrhaBQsPFAKUJEWI/rIPMame9JFSr0NoEdtvfKTQEwR7sdYb1w4YsaIvJpt5llgzl6gmMfsKp89cqTWC+xfpUVt2PsmLNI09rVMwbHOqJ6+85cofv5A+Lo4Kn3J1788IARZCmG1tcT4MROULnESiBQMBt1XQngIYrgsBao24p1NTDW+YJjs5EyRfs4IXpmD98p9itbj++YjfD0NMaLOj7Sq+R2/jTZ+7lKaJkJ3DYHoLmxtWUCjzOyyB6Umbz3zDoFu0e8VkPROaJdMqPcRB2xQrdRpUJwueYhwpIDlcWmSesLshtHP3z2cUmdkB494/Csw/Dru7SWBVLfSke4RYzUi6RmPPB+RHmLst1Nk/0Xqq4Ip2RRkH0ofHCpb9510w9fu2O59XqEZC7cQQDyVTsQyvmeKIulp0VONudEVkusymGkqHMtyLNiRMoa8l1Su2ph10oH+odqn8N+wagNaXPhF9pK0mab9LnNBRxDy37GFSbQZ/kGf8ebJsmyh1A7xHSZYx7Xdvukz5J2Ir23smR7i94wHLgWbXdZhMuBcVO7DHjinWUeNjRxLsG22/zhGke7QkXb7XfW02WzjbbVj4RxQNcaUcKgg/sYD2nMJ34ib1wvvzXzC12SNzdJSk2QkqyjtTGiLk6zb2qsE/A4jZxVb/BNfZyUwYc9A54Y1gq9UoJL9LG+IxTvlpMIj521OCfjx3BFOopSa6htTO9tEdvoaLMR+hA8prPqdfEjz4ok/Jy8xxM6c5/yrBPwAWHwGN8PIawfbyScnsmMejQn9zvcPcf0OnkQcdqhaO+ctyhisP3bpLpMiNWEYs0qj80xmJz0hiYjObotEWINR9/OcMgW8ZqcVRc0CXYZIXhxRHT8kGao61SojmMxnqMSF8HSN+Js1/4WcaCsnLXhnyc5rRIhtUbMXLlEn2IqeUk2YDy+o56Q0X+NUrylvqnzkvzVv/v9l6dPLmnWdrJ+enQ/WuqUs+qkCiYn8xJhKOMQE4eeS+bfiWgSdMTsvwvnRmLhpd/+1iS9ryNIZXfbtRbj0fcEk7o4Y7AGhoeBPfVesHAeceTvih/dqPvsJsHBg0WNbx5ah8BpUQ59fIXI7isM8ask/da+gEqftg/OCER3gxTjWUdLsgMZN73BFkW76SezhLfNNs4kdRiT+3gYByyXNdrFwPRAj1/KIqOYxnWXeqWHhrhyV7sFb5CAJ8rVA7TpsWK0gLFOdAHt4Kvm4VVT15NzJUC3UOgvuLrLcFXHQdorvwwR4SXrGue/8j78JbuTFh1Og/1rHJLoK/DHbDNvA/3G7Lg9hp6pjcsdwZNkjgOxxznGddDDe48IDRZXH+fhiWlw553kNSqrKPzVq20OM5qZKXrFvmibZPN1hVEnk8ELHrEhUFUf1UR1fm1qdFxsv+K8P9aMyISkz0TntVnzaAhxhsN3DF8Qvr2bTxT5fVx09F/wZkbsb+elzbjShOqTEVGYMol3YhPvckncjJPjmXqg08cm42Qc7/GKAvzlqcHfUflAKzv6vQZLllBCPiK3WfCqq6TENzfGY4Jf42QmbO8Gfbz5WOJbnDt2mMZq9Qt9FA/PiO89SqqmRJSGGgpEyc04tnm0ZSNvQ1ezES39g0dtR9om32SoPTw1ODvjyGLX3jkqaSalWD42DimlRmycbAqo8JMCnJ/j9pzO3dV9XrYnpX19566o82N2htBoGIGhYUPpdQ99VUwnRFM4mBpKCgHTgDqnc+QVjC607VqClWLZRBBVfJ4E5xqhxy4A2j7zgHKH2XJlbzkgz2DCTi7UydcksOtekpShkCpQVcSgEt6VZ4ZtdOedNgREDj5sTUynCKIagATn2XH2xF/bdxZQ038ITDUGENZzHL020w6hh9H0XoBQdVwE8+zzGLESLwZYGyAb2t/WXXLTp2Yd99ja/sNVNOPRV1CNz1DLc7zd3SSNQuXgZH3KFCvVKQvjqU0/Exuc2PGQ00PbfU09zQRZ1FLNkk1Vz6kSUTsN3GG0rkP0Hhe7NdPyIA8oMyFbruRCDsg5Q5gQyKHprAgqd5eHUHZYAHPtMutwBLo7FkOcIRVKvCBDBF1AGCxcr8w1vS0+/nW4hKBuy+BIss054X541s5MjLCqTxXdbaZkuBECyIeOyRije2CHplz9Gs5hGvu4B9yHQlZkn7crcjZjmvcxpoRptvREw8y0wcytlIXy6ISJEs81aMvkfbgqDFsx2V/AVtC5ICY4aVnSArsuTiMO895MbleooxsPB2oxJB7ee1STv2cO95B5GJInydrP0qPq5Vl91XtsE64BjAMCVA8FhA7LmNV7EwIuOQ4oDgaGuq1IJ1WOCtfm6ag4a8WC17VeFTXh00VbTDa3pvONiC1d1g/TXTTf0EKcLPA6Xi/4sW5BHm4ZRnollF6DjhU17XfYRK+fXuJ/iWxsEQ85JmC8KsiSitJaRDWaznY9+MjH086WUbDd9l8k+a02jDPODEe+Lhw/pGnFlynXFhgTL+RnzYEqNwkx/D5j9OP9LK9PxD6Z9tgYmk+ngV2kta97OLGH8neO54EDEJO3UyiT83WKAGFJm4YeOO8l3qMNTp719Q8bCY366kj0CudJOd3i6X/ZCJIp9HiLuOsT0BI2x1aFCyO1i2fhQkPtnh1CxSnOUK7fEv0a6dL5B/QjdHE4q67KJK9wjJupMRV6K66Uk6FEn7ZKjUUCnx31OokHZE6OgHL53AgC8jxvdQo1kLUxDGEMNRBUt5dm5knoqZ5ZJAcdrW6LiFXio5q1Quiatcx/LRaSlx0WZdscZnNttZfbUXgZoN6KT3HEr9KPjso6bjyYqa+aHIv2Spo5ZgmQWxbNQWrVbe3HJ7+oz5HhhIOTdl1O2oNbdXVu1YOn8dF7GmP4smM7E70DM8wuRTiQI4ZRx0foyyYdVC4ZSSBQkIkEBEBECY5k8B1MJq0KackVYYsikv2iyZDVrd5ID4Ps0OxBh/0LtZFeRBwTgMVBd4EqQtu0Xe/GlLREah0tkgnNuAme/1UXptHkgTJPl8tryYaH2TDbQbFb7s0Tx3k6uSeKinVMLXD4NoWvzxcqp1wjtYF1fmsPd4ky4C1ZFtFhtVG3ZVgILC/WO0dYZ0X5Ft1/TrJm+dZ7G/1dQdecuZMKxNsQnFX9Eb92N+nqwp6u6oZ7sSdcB4lTtxXJkc1drA5FFi25Zhozc4b6isyenwUxRo4t/AoIzggLsrczA4PV8AZd3TXbr3mCg0PL+sdd1uPo+Rk9NGp3ysAUHY9YJoQQal2zi4QmOYS6mjlRhKauq8tFyGYxbwYMxZmVMWNGxIM2pi2rAdh3PcqJm2mqY9odAuqDGaJuayJacI7lgejRELX7guCH7mkqpu4cJqaDmtWF+9GlRrm10MMhkhzhOpuA6SCn6rYiWfixrqqcVafE6mmmt2f2aI+pU42NHGqRLWoCVqeLGv9QyJ4MOMM675PfytTfmCt8pNAaANtBP8yuH1hy/yF0BM+tlpnZ+Er69GzcD4UcqivMoD9CcszZ9D+mLoFTjoVrFRjvQb/Mrl9gwncne1EeeelsXSitrM09z36U4FXPPUQCm4U5duo/hYBbJgy0HZiOCzxGq0dnSwIdFke6aFEF6UX9UL3Vog7tQSu6qrPgN7XiROVETmQRJb18PLd7lw6fOzoOjypaLjnrAjrLLY2rlfoLU/AgmfTtKvBM9PGoLilzHxwzaPHo+vuguQ86NjAO0mQUxzXRXXNuJSmqL4uyZnC1OoUv8ME6XOF5i6c4hgk1X+rqFsk36L5TLvSD+zG9l1kNr1F7UL7iqhSiwENcC1fJbbgfgSA5KFlvJRvr2p/JaouWX15hUYE56EN4U5PDPZxnNcgPvKxu6/JbM3vI2Jsa68LEIl30ZJy152S2je+KRbqFGfNpSNeHFvXYHN9VNETNOT6jaAjPWOdjZEdVhW9zIkXD1dol3lPeyxN8Z1X7Unp44GIc//nkc/jPbRZh/2LQefHenW8t/49N/fGmRdpuTiKavrbPc+leRzG83GVbVeUrtq4/w5nffK/QeAzW99TA9rUWXduGh1xsq/oM2/j8i7WVyA8i4HYUi+hg+6nb2svtKLurAzFvJ/08V6GipvcShTr+Fuwge+q2IhlOPZpoZ3Q0Rz35tN3Nn6n+rOov1gZfemHWpbwui4xiW2UKNHeLxud5OctF3NtgEXnOZzyg39W2jusI9cdbFkMVEbfHs07PBLY1rpWa10gDbX3bBwX1SGJ4+CIuJodVxGIVWeaVjv0cKi55xzTuAV48U3D1h3YCvpP7XVHW75M2sckMKU2s16T+dPAS6U9JAHibFcqmmkopW9WNqounhiJq5QnpQT97a85YRxCBe4Aolt6coqSw95zEMFCTOAxd6gMrgCYCaCpbahQdhhn0SnSdctAn9qI+TW34LYMVWJJ/inOkFikLepvHaXaSdMegm38QIdqiGVMJfmkPcBdoKOxm+mymdLv5jeEZeofzb0yuwr2kJvKwg9ewgNmt4zZLYEzHdx9sH9v73aI9LGY6zQfS7adYyeK4JQ4LGdDOYSH7Ay1ksqt8LT53yzMIO8e913L2uviRZ0Wy8X6Na0BwWKQ0ctvT6E2Dx1a7v11T4FVowPWpDM47CKCabRUa2prrxUh6aksmg6KdfSwWjzfGaejknoypWuLs4vBMpGIGWvkOXdc6LAZrL44BRXmz17tz36U7vqPv4ZDNy4JHNMbHOgc9o3ytEwSQFmIYKmilvcKo7AP9vXeJI47DaqtuK5IxCj9v4Lpr8n/SIk5suePVEueUeVnadDLYvX3BHS2P3AqA+SeuYygKaoCx1WsedFIBMISkAxRgMUPQpyZGKKib54qgLBkihmvrAn3H6MdblO1umixHVRXu1pJQRtNfT+gtHYbnhqnq7cGnNpqi612oqH9Jqn6A8a5ucB3UbVslAl8LVaWNqqGGamtqqhbEgKdF2WzPi6r+XHhFf7X1q2ccmnVw2tilUC7rKBOLv8ZugbzF0fGagZ2YSQEicY8KLg67RGCVg0Glbuuq2OE0liUUI7h8+TjCs/OjzaZs18KZN3ArejJBXIy835cwGnN69XM9gQGaZyxVK50JxNV44/QWHL8ToCXFWByTNnXqdSu1BsKygBBpp3INcRmgcHXedyZIn7c4Dgpd3VZLpdUodDpbMY6iLpuv/0CpbnH40zy3ET90glAFWnbEfA5E8S6p6hhG5oAn1hQP+LplR1yGDuuhw3rY6VnlgsgXw0pbgPFaEk0d7IHA3rV/6rrWAYSvJYHLyGEFUbfVEuhNWTS7mRO6voyUB0s8jlzwXtCHnq8DlXucVYaq4ij7uZ9xrfljPXw3ybBam1+zQII2Z8pgbc4ChGvzvhNBKr3FcdDrGiXz02vjn1LGpaC8LZ6eaA++kNlvrK6KozTi2UYnl+GqB3SiQLrJS/V8IJuy42LbxYg66x5a+xmDYh2HEX1nrnCtzWgTR96GxuK4bQ3ai1Da67lGJZMOGEE2ZSb2egKcWBUql1ZKEChotZy64s+vhxfKTVZmfttA4WSuzq9IMuievLzcwynKaau6lm412rvYlzURB7oog2u0KQRw41mzTSvRa4jgpfxotyuL72jT4zsGspu6bkuLOj7SyAZMVEvtPaoToop+FKU2s2ukhMSksSWtY9qeSSd5jky5xg66VLnGtkviBMUvsFwhuLryEK7uTmZ9ho1VLytANFq1poKXFUBjx8s8yY6a+o6uad07ARcoJXzrE9DVG5jVMx3ig8mgUUKRHFwnZD7mv68xzHI3uhuszYIet8l+h75gyx8pL7fct0xTR2mKqmqZBsnP73iDysuHiugUg38ndri+9jxLp0jAEy6rCtISYFcraMfVO5OclWqX6bv776A6NUJDCWR8iyGSgNK2gi/qRb08EIrnFc4yQqw+gi04zIJI8E6DzoK8ZCNVN9E6EgfbefJAra+oyLrLDnNqXgXHHDdlifL04ZjUXKDRrrGLZApNNtw0+Yu3LFwl9719ECNk6HNifuIlolq5bL7WRCVmZ3maXVG0M13L4Ro7uV+msSvaGJmblG7/lxoh1+gyI+21zjIj7BtbdGSkIQdRdm+MU45kFcHUREiyU4Tmpqm65bkJrG55bmr3+GfIowrx0OxMOsj6fFcaS0Rm51WSJfmMFyc7YlHldUFGs2HyBc/Y1GxNkJ0EGQTauD7E5dzMj6TcnBc4r6svqEREjsKd+sd3KP1WNFM+lSXdrVLjizxdNdhSsQ6xjm5ucIaT8Kxb44ZnNzsN2lMVuksj6DuRPya8xRuD3ixFMFEM808k7fIi+wqJNvOdQCTVN7RRTcmsIzz+/v3lIg2d3O9w2V2HL/LpecWF2vwvlMxPT1a+uke1XqOvODgvDIPqKG0NgbdFtlmAP+SGF2JMpuFXSf5tkR290OYiKoZt8+x4yeba8+opJ9USTZ59TRYwLvrVtDUAxxi2ueW+ITucEv+r1TRtOqgkpX9OpsHiTS8iMqrGL1DFPMI2o4bf0ZOBZQkuN7rQaC+br6ONvuyQz5syvUsqtOSJxHmCfWOHBpeOkJxntnnpm6MOB6Jwdk3N5PxZ0A3+GmUoQrrV9QZ4szvhC0TPEhkPgtU5zNuEZvzr3VIfCnrQ3h2MBkeYpSna1Vd3mPQvIZ/bcIW3Sb75+N1jZ6U8WebPtMDz5VZGr0XA6TgZKpdOj0Eg1xgi7TF41wJ03s2XKLrmfY9z8KR9qpJb9BZX9NVbOKUeAHjdH3kzefWUUFKwkwbUNdPBG3zT7hKNg4AArz9VaPMF13fSYMzQ0qAsqrgOrq1FI8E0/N0Gikn9F4qkzorlXj37oEyJMRYrejYVwT1jyl17doE2CG3RhtWQJ515D3SUhTIyhRFYGoy5huvw6AqrDkUcSmWy8yVSR4Vin17trLSxBClqOwFAofREKFfd9yXJiIWg0xcchExPoFgiKgQTFNIJa2yPN847NBWo2Q9xSOq2xtPtQD93p35DneVB1qnRLLA3BaYaBmtg+Chn29VCuwp3L6xzWDJWHee1gZekw2aNb3whhO0g5uq2BnqFSii31MdA1j8yNN/J9xwKZaCmvVk+1TBY5MNHSS710K4KRbAIl9hdWA0INj6DFE64kjkoFnVbg/eQ0QrQbWU77RQj/nHmgI2zaujsUVrj70kEn9yA8Lhodgu59i8IMXb0UYtFfJdja8vc87lEOd1wLzGyrqllhvWe7Arby4Izt3NWTdzROlH37XWeyVPmviRb+cekBdx2NLAm1Y7nWlVHHpECVLkqq+DDLxV55nFgLxYdUjnYbMYJkYIv/USx88/3+pSeaPnmuG4fmJg7MntsaPZA7CVGs8hI5g4cH3wbnY04N8n41uam3V6CfZcN8h1esIqQjeOsGpBFM+PfEQHJp8cMHbdAVFt3L7jac4jhimSTbzJEzKxk/nCOTsEftzni55ImlOHvqHygNqMjbfvbdSyGeKf2R1VVpDRSfDOYTvBhUUSbTWWHmmy8cG+zw/kseMoFnN9am8eaN6cm3Ofym1NSoaZr5zHenBrPewOt3A8xn/r5aa3c0FzNzONDy+XvGu77FlOMzCp32AZNYBcPAYobEC/hJWxAPEJQKiAA30EGNXwXKZ9DR/AYOIRrjv4nUzM7kpdy582kGbTxf1CQEBQNqIOT1IYWOP75V+Q4KpvRRDz8GqKfPHThEIg0hl4d9J+6rSg2yFWZpN8IxRcKvm8vTsfda7Y8g3xD+oeNmWf1xW0f65hEUegVIYuukZLq+FkW4nrSAXIHOQBl7CQPFRTox6KMoZQORwBmiWzpFCWuzycBeRzvvVEYYsqBSl5hafGSAyFMN2CnwmE6iIJGFNaUuA42GPYa0tKhnzs9DNfKOU2YR2ZlpYF/UvxKUjGHKVMsPX2Uid4N7MtDN3wvfomSYPtos8W5+XL0nzxb89sS8jcHoM0gDCHpZAVYvIhqj2sS+j5G8XIRzZ1foH82yOtp8N6tzKE5LBnqtqIsGdFMp1hrT5wAik7bnRZlx03LO897/kXt0W2UQ+6wDuivQdtNrfgYxnwRi3Vyc9Nfc5w7OGDedUiaFEa3xUl52+WAuyo6dS4d0Sxogcz0XKLBicsQVOG+hSAAVycIFujfLFp/wDFZwcICCkVMh2VRpzwiLIvnSdlvqR23QV1Ai0dFdopjXDGIdugVJy5yP5kWiUiikvAQjQucbRsXx2J5DKp4Ym6FQ4vXUtcsPOvWUoIBzi01bNRsIFJD0FqiBDL323sPqAspkloBIotUMOYuR4kzkjKt+i+CEqrDKqhuy3CzyPYtMddDQnRfk0/b3fy5yOgNoH82uIzwvCA9tqHwPcPHwntWXSX3J/eIoYYvKoLomLDBbVE+RFuI6ZO1ZZHFsDXive18VrWhzu6hr1Kcc9hDhupFRFRC7d1w+KAThr0GVOKkrm3rSMeh1hWDTkjhViLq9BbfQbGr25IoFhxcupeVojXJjzb/IHzDul6iG+ddCMkCDZ1VBBMRe5RGuKYRoFDtNdfyOks0OZ2Vnee5RNqUlK/7rIvBeV0UCA9aS92WSLKfOZOTOFaF4xLkoWu5MuvKtKsDODctK0aVs3gCdpAsDRM/pBnqluZAaaCIzlGJi+AETG3wZosvMFpe//L7nvYWkbIcn+W4xkm2Zk3GdtFKi13zNdSqiwM06ise2tWDplz/l1bLcppSR33upZZbpief3hW3HhqZ1LqlIa0MloM2VrfFkGlNhzjxXqFYh2ISyAyKMgNzLcFP0qsBk/SSDjbqaQTbEHQQAZVrexvnQFsiYwx1QuEPKkXdVvd+A+nRj6LUPSXxYh5HjcE99HKeVk9yCuxoY1nzcdhSeFgC9a734vYd+o6y8GfGi7I2xyNbR2Y5Nn9KwBfLZrcb87IHC5qnQWEKh7lFn0pd3MaLP0cKkLtBZYnKRRqLGnJBtYP2XuRMjvS3db0zPjP0Iga5PlXGhJW2a1AUI0llHGmNoojG0PD6qsdSMtatpj8Pa4q6rV49Bp9Tx/HceLqR1LsZzfMdI3tcA093SIXyHkCCCL6xz75rFnZFdsJ04H11W/s5n10yQV/E8JHbnND6+I7yuHvQR8wYEoc9dXaB0sIrBfwldRQSdM9GJAdBUrfVkT50Demw7EcoB8OF6vbQcZyR1TSLuIixfVP4tHoeveZBWa8WBAH4tUCwoIWN7DcCpLB4NtY/CODPLICXWXO7fKvRgiKT/LYhO3m3GbB/y4QyBk5DIr3pOUWRPxMxHYRqbqEiI3pTFs1ueeYmLS/fKPeg03KnYR5X06yl7+oObdHnpMQUlYfotfWrZxyag9yp22oJFYFzF9n8hUnDyziXn+fk/j5Ri7vt1m7tuv8O3D47H7peY9SeAsxl41WZ1+kjgIluzw3nJnEk+LQgOyTScfL/UZbR05pg78fbotJmeoiUB+hdcVuc45QK1XpuIb2tt9mrYsNYVfPdVC7ymojgkF3yA6p/FOW32RnmvMRE1z20WuG4KUuUp8E2ZI/z5D69IxsNRB9J8UatuQ6rbAS+IktHeK2txdyVNQHLl2aNNdzv+8ozYx6ZAK4YEgelHwsPGnYFeOyWx5PZJUppTOSzAclhkVa3ZVik/zyTT7KbGMOTkH+eJceR/TtMf/FdTt4VFEE4WW08u2T53pJJb1uYt73Q+7Gi0qICVt3t9eLbnMEdYfk0xDG2uu0k35CZXcDCuiiafDOmeasi2bYt1g/Nthe7wHseUx/bqyMx+zhhfY3yYovzhHD8bLdAhSYvGkZ1tNcre23ZwtGsnC1AxD3w+6Q9Vg/cCvdYDoutuq2f4DBjTn9MShYDwttXSfXN/74trU04UsZ1YEzNtDLkCj8kvmjynLFHfFXx+yS9wznaF6MTXom1ep/ivLV0vDJpXjZpitDGs/ZJWU5L15zvJN7SSLFzRHaH0hOWdnXnSyOtTrXPsD28X5bVyDVfidk1G2DlvbOpQljYHdfNCGr0oD/VbRl30bPoqKMMJ7rNQKyI9yI/ud9REdUH9kXae1D8mlb+sqLLQ6B9tPuYc0rXF9UHdF+TldRD559Vb/GGcG7wXqjJiULvl+F4UV+gnTe38pVeArLV1l7Kt7+E7R3y3NevnnGIDhpY3RZHqO76o9lwnO0SmF0kdKwE4bM4QpaJZP5bgxq0OSFMnx3VNdE0nklgeuOxegYiPAiOui2GYMG3L0lPyCRQ7zfH+VSZCzMigbo6j5Mpdc18psgpBmKmDY5a5ln6VzhP6Npms90Ksi9nulD9ntgaBmtsrpbRBic9j5gmQGln0NkDDQtQR1x34JNJoYaSdnIaUNejXHbkDp3nq5kGwUJbDoar4jooBqXDmLhapiExXy1HxNYI2mlz/Yyych3WK43mL3FRBmfOp+y0/Ib9qli+zQu0yx6iNGxwExzP3sSrNJ29jcvm6z9Qqst6FMm2oLFi80eKxTzQviQCelXi4Px8BI2fK7tV4GlKn1sMNlVRvnmf5E2SZQ/xPBvT6gJfzI68zok+DfuV0XZALMlNI7rmgcGBcDC6dZoHDFqe+W75r88snsMCbRBT3Zbhz7PEdfVRQKadkmXj5jjn5Ud4XpTiyZprSFJFZjIOgZz361VlyEU2U8udO+yyypzVvEy81+gmIVJNVtVWFpgQpshuseNku0vwbR6irwYcB12lbsugLea60me0MWdqOJLNuc+oymXc0r0QXaEtWVO8biWPYiigOkijtzTO5Gv8mTfl7wlk+1rT/GEC75Kq7porUQydanQFdPZ4l6FKO744WRB/LsdArPQQMffkke8XBG3xlRv7Fx5nQlPtl0G1fzXWtl7gPqAf1TtE9TPha+as2n+dgzEeljt1WzDF3jR47EP3t49ILr/PiqNP4jos3YPVZSRfivIbIeHMeXBOi7LZBopii6N6JqE6yKCGRWJKXqyXF9oZDH9PZ4fTlb4Ro03RK/EvmLBXDSW5lTWgQY7l8xJ/J7QY49Z9BZbHc5BWjbTGUOiUh2JJ6lURC5N5e/Gnee5i0+eXl/cJ0ZeYkwivJffZmV89HDX1XXjMNYPwAqV4h5nLLPsxadTRTQwbg3qUVyvXPPykSDVgkibVwbrGBE2iY9N7FlrZ9wnI1HMGMmgJeI+Siiji7sHYoFwVHKbDGqBuy+AknOlFmz0/qHNBSWd5Ad07a4VzAgVXMXlNNF5e+W1uJEkZkR2E5SAsP5OwnG13RVmTVm+wV1JOrv5BONYmHKdFton2Jo7zPfSsDeiPk4EiDqYol5U6nm/NSLThrmD4Hh58w7vArWHyDYVh6BIIfczDjy+I1J1ilG3orwWyBw1sdlzkN/i2KfnbQHMdaZ3cE+3Fzv2M+RezZpuPWXNmbu0CVURBn+U3uuPiOE31F2AJcq9EIP55Frg1S3nJVw0l7fc0oGFZFR7y1D9dEuXOIeHjMwbVYZXWiHWUnEkdf+jzxr+Yx8Fmla5ppgW/zRR7X+9pG9DS/G1S6W5q/ileJsQzx8QzXa2hT3OvGW1jdAGXFnPTZipP/byH9hHy91RZvka7rHjwvH8tojhoNHVb/aoUqtL2I9Sx0iwuZ9RMTBljw2IRQh8p27Y5Yj5OQ8Zc8zEaoS/OXpVJXm1xm0YoxlRAOMeRtDkHWqUEg3nsnDu3liHzQJw5uWy+dn6C2VuyjqKMxAhtexaHnREHFyc6kcoj/o7eMymrA26S+NxH0aR/732FwM5KXJivR+BpX6WCkXZVSsCwC3r3YQ5Prv7B8FC3tWKH50xZPKnapn/1pJpfb1smnoqTcvM7JoTda66rs6pfFAfhDYwmj2Ncdgohpjc4zC8mMyVhxiU2neQnXWeX8L4O7Nj5fBdxwy64h/h4c1OhwMu37fWGMBSvkjq9u8T/CjxYOCdao3v7LFDOIppXx8V210ZeuRik0TJk/B3vjsr0LkYAO601vYkZbtxN1hacUyLIwBMzSBgtwWgef85oU3r81VAK2zS+x/9Vkn47y4m8pN8C4/OPiVLMittnCowHy1XdVpTAX/Jz06ThmipSxO8+YusVrAdG2JtgJQk0VnB/1qydMKeRjHXMA+lBrccxwIdFjJLV4SZp3y4sA66XD7oEQndQJOq29rMN/YzRj0h+w7VFqxFGRLdF+RCBl0VUBz4+8PFifNwr9whsLGA6cPGBixfj4t7nNRpB3kzMIzrwsLqtcVfxItLu5GUYnvlX/LKoKmKDZ+FcJqI68Nla+czhqk/eXKC0DVxw54stzZc1IDhwg7qtqAdNnoc+dKZC3aWTvTWzn/cL3pBN+9+L3NBUlBescK19/TnSAeD63k+OncGpCxO5Ko7SiNES7YvGoE+JMjQt7dTPdcffkx8JKJYc6BBM0EtSPMIAnTohOehVdVstpYIjhUrSGKV3BI91e6gaKXDDMHSavG/2k9RldOPlXXuMZfkIVwxljMo+vJD+1h1bRjpuX5/yf4Vucf6mLDwOP+XZ+3Fyv0vyTfj4PhSnRZYVP4LxoB9fcL4JR3SWmh7RjMH/9YNWyl7EiendMk+GzTaW46o6zpLKcG8x6rvA3cofx0IQT5d0VoSdhdBsGfvgb03SgtGbPmWRdcHNZ9VpltxWIxm8N+gA9mjWA5kconOyBxpVxagdforeo+1XVA5cvcN5TtXU5yRryO9fpBnlwF+TSSUCm4/wL2QKd7TU0Pc0SVF9SVaTluf8CXuJkjK9e9aiq56xWPdI0Le4rugqaUvRFuqIgX+hh3+XfEUZCy/fyeJnjFuM+jq/+s7asLd8i+mVpqhTx6Le4/wd36H029finhocdjPYncXbzt+HZotKnF7Q+6qqObSajyuMynOCCR0nWdp0anV4HDeaslI3sscpalcl29khVmRKtg40XUlf4c/6CkebfxBqdXf2hin9xWN+viRZhurzoqIK6QIlFbVRwiemD/yongH49zgnR5stzq3nhCzfd0mFbGWG7HVxltlqPALd5BuVrpP6QgiGk0yo9CeDUkVfca1iKCvuOG5KsqNNH9qHhukENlkMI2NK6QQ1sEf+aLvxHm92BVHvr1kLwsArXMVPO1uWOcp+JA9VW5lrzcA7TDWmLZ/l0vQ4b/BUC49oqVra45y/yoqvttNMb5UQGUSUZ631QnfiHLCG6m4zhssim5lC3dI+jX9MA8TP25e37KbpPekI3pHunhbllg6Qq2y1GTiqqiLFLQkHk7agicO6PegFqtrN3fWQHFzo/0m+edJt+rS1pi3iw3h1FapAtrztiAgxyZbs96f/lzR82wZHrzXT4DAEoZEX/JhIIx/zLkfqk6P2ZgkNAavSZCNvrwlFN/yXXmjoGkZ2lBVhD6InZacxzlMyb5nLUAQklr5n2smxObHkNdqhnLqNXebQph9sKnm5P2OzAjFNtPvtOcOsFjyM/9WGd7R9s2RgsIqSe1loZ9aFm3p8fKsdx1JMq523R8Gxk3u9d5sNWws6ykrJtfpqEOeKNVwY19AawLwsgKElF2IVWWaWaA4KJEVB12mH4XMIH5Wogl1fQDrBOXgcAkl6fpRXP1B53fKJjikYOBWfdSCu3MYiBvgNYuB18BrQ8YW4DZgLm5Yp/N547ZLsYVB7FE62W9fH9LZp+aDkOBAa4jsO0IX14BYg9d53dXU8qB3BApyonSOb9vsqe2PJ/n6ykRkFOIgNexAXBhSx2rPeL8+eyW4KLxZS9GEB5lHQ9DGxDa96TNPMS0tcFuJxA4yk1ZLx2Qnsz4JMBdLapn2u4t4YbLxqOyVQUHGADAqx1nQx2J63AMwAY9kxrc/YX+GMpmoZGjB2k4ePTgUBvT0pZOnyoEabATavpxvepv6KFXT06GF9yCI1o9ker8+AMo1iAY1lmi+r9ZBJZbAXffUqK26pV97srpAgIb4cgFwYUkb8qFwXyu4vwILKOXkULgza++Ni26a5GRlHxyUisIoDezhXJpTQA3yoYvB18KFqBAuxomp+bJof6uzPoYbbJB7X79EGJ/2xuNqrBgCDrrUOzsmvBqEGOJHr52zbAl1vlvCYaehs0zxbb3+c1V34HMYy8ISSAUBwkLs4SCcmg9uAfLgw8v3rO/0QluBN7TxZOXW7KqthzP46uS3TiNmb5mBMIeOT3Mb6GZMfwh4Yk58nK8acErXtx4vSJyIy6koRENwr9zBOm2QRr71mjLf2qjqxxN5WQdfHoNVe46pNiHp9tCOTQi9D96PBGmecrhLEVAO8C1Np24C8L3aM60AaNlmdUbYgYIgULJwLOUD8+5AzXUcWkDUdnR+nvLEjchE5rt58Usc3A22t7Dk6iE79MutCoqHKfNQZW7C3uSLQZPjjAv2zwSXqsi0bOw3VcqFMVGPRtn8AXQE48yR66Tor0i2g9KxIZNOPof6+N1HDafjHm48lvsW5aRclwmu2UR77Jwn7PiIUDH1ZbiekorVND4Sqe2czop7wd1Q+tEmqTVzAAkdmMA41pNLYfs7OYlBvFuQviM5Wyoupt2/OetXkmwzRxAhHdV3ir02NundSrqcSE8PZ4NDwobK6B4NadUVt4jBjXquDyWWEy8mCCwtYnQuNtdYjIP1QLP2lqnpWghDE+UJ7j9CJahrLPvgankV7Xt63c1UekDsjL8fCPswb07RQd2UvvPcInfl9+6NLeXRrGphAqqDhNh8fv7IZB1fsahSlchTLcalyvqy2WX2d1XCppU4U4Wfm0Ue8lKvGsAcGfbxKlDsr4NzUBg5SVtQwrO/BjrFJR5f7aljYOKLleNk4nzZdYeutirMtlS9UZyF+fsSKWDeOPTHw41XIlzuU4hvcZU8aPR62DKyvrWFluKIHUxt68AjZ225EyzG63Rw/BpaHR/Kxe5tRwZEq9vPABV4h16BxulHu0R3orqaVWO5fVAKGu4DgBPCGTe9gDCtdSLQM7qfb9eTd25Kj7Za1rMGiv3+JCx/73lcsG77xl78Oz76lcFiWr5JbTVIrGTby4TqLWW2CkeKIOas6nJ+TEid5PU7LcbH9ivMW0Cn0wBaPhnAaFB40te7QvmMZXDu6nF5wnVObnq0pAkI3PssNnQWKVXD8I97fOQxrHaLxCHd6FqMaHib5lOMgqWDxrEI0uA4B8sENfJ+LAdTRdXA8NKc2PWPrrY33fVcAD7UfgaF/JgW/Hq3+iFW5sM+qLtG4yzA76xxwaNgcqO7B6VadUHP9ip1yHgNcThRc5t5BLFbjfhP9GRqedWNQDSZ7WQmXE103LKRFLbJrlRuLAe9Neix4wkeGJiz7libVwmm91BgR7Mec+jlWGOvR7d/S+inWFnFw7fs31ypudWRNLTIHKWnxRBAVfX/UYmOS2tVKj9WA9ydJVvzhIFUiin0Ll5Mjytbb5HNmsyq/0Z6dQ4/bA0SfAMyKZGOXChCEBpMQ9IBO6RlA5HvLBqjtzgLspaW1Tftrygd4fZnQB/VGtjApGB48svYSkEOHoAr2ja+74L4sqL1gStt0gK+5Nw4bH2LmXsBSchgMDnHYCOnCYwr0js9xtXy2bxtQP5QFWFQ/VTYdYOutgEFNxyoS5Axs+RgPS5S9X5QJH++ByAX6jtEP21M9Hlqz9naAHiuw0MJjYkXtCJZbtuE5enQs+RZlu5smy+lTJTxTWXGQsrqRaZma3vyrbl3N0LDIrIytjQNbms+N8+zA+F3FvbH/aVE22/a9AOMbOTIoxNYjlAsfA6idXsSJsgVSd2IB/lIT18Wu3C8bXRU7nFryEQ+rZKQWzJmTBOR7YiW4F0vxEkzgx8NM1+2/b8qi2ek5iQFUspEzB7FIAfZh+ra6pVPV/6UYD5gPm6anWmtQYh3XWCiZbshzqK8Os4r5Vsp3QNeXVXjcfFjz3QrML4ZfzFYSM+D4JhiDXMV9IF+vhAUVY1jUhpPnx6b5tsL+WfFzUaPriYeMDMPDaxmSgnoxpdCGii/X+TqcYRRLciY8V9Z6cq+PxH1AP6o2D5/xuUwJEmLKAciFH2XEj+q5TGX3F2BB5ZzYtL335zJp74cXFkfG0XGJCKziQI/nMkH0AB+qGHwdfKgawUKsqJofm+aHOvt/6Jw9T7R465sDj/7UN4/d8YTZ//L8yX2NyjzJjpr6jpK3y21wgdKi3JidUVa1IVLpKrqQz64Dj+p9cKchLSDvTnP8KDxkH8sNTeqOswznt0ebTUnPjVQcBgFDHN3CubAuiBjg1KGD8/hadb1YgLl01LVpnq+5Z44yqkseLCIXPU5NB/d9Ma57lLpreDTqU0UM+reY9KZ8GF+iUkf66mrpnh1jK/g8zgY3qHlHbH1cajWUBZjWag5t+rH3d8fAkXSaz4mfOjFeinu71gDWBXX2SvmWG8S+mJabN5tOtBX2u7i3u1w9jwpwyuXd1W0m4n08LKjo+VIrvDwXj4nZTPGdEuQMDPcYYzqVvV+U7R5hLOcbfFMfJ2RTf066fJdUaPMF13cTB6nYxVAPYsuhigtXmppRqUWI++Pd/LHs1QK8ZzkNVpwIYtg7Y3JGxMhCJn4Ba+mY0tdq1DcIsKdKCvavQ62GsiBPa+fQph9DnXXx8CdWxNwYmau6GDfzrT4eQ9R+MPtianA+bTrDVdyv2fqBhkdY7JEmOKXJ+sEx1EXE+3hYU9HzpYxVeS7Wv0e6QD+I+JwXBEE1yI/R966rBLEhAO/CkNrmHpWX3mYkC3Crzfw9Cg8+NBA7Q8BY01rtgfsfl4b8BEZu1uUNnzu8a4NO9ETiwcCXd3oIp1d2eKyPZ3mBO76AvMLzsP7FZeh3t2ceWMXEFxy0julcfXFwA9CjMgquXg8LgkNYkBPBObJpf0Sw3/AC2o2ddcSKAB0x2EDEbB+zEs/tpu3JUrazisa2PLVbQeTKlyQjusDaiobBIebiIF2YTNHEo7KZ9WNYgD/18/Qo7GR+CHqjD4CdkScj2NoRuGppg05D4/VbdReobsr8Av2zQTbJGmBweNfDQLo5CMAmHpWa049hEaeAbp4ehZqbOm1p3qkqRL+RENPOc9qAFW2zx0nZmeyvmnyTIe0RtKYOvBnjwd02ZOqm1GESzBhmWx4serbIbss4FTa9mGrt0RMgjMS4bChrzM6Ej3P9MA5jH/z6KFcRaRSm6DFVhdk59TGGlJkGsQ82fYQBZudFln0uajKK/vYY/XCUVz80KlVTB0wLKIA7pQPUNAVx69T51TGsxVAW4FmLubNi27HW/oz0O5R+KxrxbQrps9pot0QAGvFgXSeT3rZ1yHqQxrg6bncd3gKs7zrfVkaGWHmP3pS0KQkpbs+Th/Yw5SzHFK/p+FpTC/at8BXc3Cu6xuzPb6PszKw6s4i7xGIGrPrB1FsNFw6uSYltbHlEhcCGN71ChCybB7jVJBr7V8quo9sD+5vm26ZLYt29SQOd1u9k7t8Vt9fM35RnlAKgqQPxPAPiwue6ViCfotD51XG2xXgWYGaLubPphVB1Fexr9LNBwDMx7ON0rOlGsDBvPkp3mhUXmrjPket8uW0VrwvtidEeLYMd3dzgjHxB16bjPgkSVHQDkJOakzAvnn1I2YUllJSKsFbL5p7Dto7STMjA1aaFU69oEDi8ZGbunlMFesckdvvXYvpxLLJw6ubJRbPRevs7jSLbKvrCFt4m5cPJfXqX5LfogojaMdkroTx9UB9LmWqC51O0ktOhlLEVkHX7vs+jCq37tAAbWs+ClftejWYdDNr+cONMrkp8luTR75kXwc4szYQgwR24j6u/P7ZL79CmydBVUn0bnE/sNzXzGSqCLCjXcWJIU5PQHRR2LKtb3W1HtARvW86nTVfYenvj7L81qEGbk22Cs6O6TtK71o96ijU2qboKxM0gtAs/a5pzfTJ634xsHsoCLGyePptOUPiVMS33Hr0bN0Fvuy/AxNCT9myz3IhmCz+179veuBOaH5vOsPXWwK3X3cBSfdJXVQUDZ3ryI98EwIVcn1dnHJhGsizPgvNl0wW23ho4lRE+lsXc9BtLmOW0KtsqwM4aiVkVN+tHtDdVDMypTV+Yavt96uuy+VqlJd7R75aPtoJVlA9+sdDOj37BTe3pJVdtZxZgQDPxXRyre3xqHX9PavQeVTRv2PVpWWyNfKepAz+qzoK7PaWubmh5trPozQJ8Z0F8qy0SU28tzHdVuLLeVGNWxmOa2TvbyX1Znulkstv0Yaq1N4Y72+6KsiZdu2k9CjZOUnUViOU4aBeO0zTj7BKNwnbmDi3Ad2biO/gycX67V2/myb0z86mrwM+feTKfppn9MJ+5Qwswn5n4j475SENZ0UUjD2yi5wm5gprxJlh33gPagZw9Ogbf//7YNJTFeFY9azZd6KvsjVVfJem3s/yyLtJvbvthU0WIdRV1XDjY2OyjCvK1Hc0CzGw7n49io60ajOlKvaHewjz9GO/ZW45ljwz9CG/dv0d5QzMG9IGF9KeShQFYiG15MBduhRoATyzzBka+fybVDGEBxtTMkE3rbfVlOPGE1KkfSJ2a1EDlYGRvk7L++PUfKK1pEbonE5+2Gj/J86Jusfz1U4WOs5LyQ/X707psZNuXor5E9eiv2OG0evqk+87wUVsAsKNQPbk/Tmp0W5QYgVjG8gcjLvIXTXgAoemLjCjeFWmS4X+hTT99cKdEKHPX3iX5bZPcwtj6MrvOocu6bLM6VC3nKbsnwBmRn6Nyi6sKD89LQ4hFGCNS/pV3GSEfkm7qYZFlYK/Id6vKXSILFYohn4jlkHTDMSLprzCANBlvfZg6Qs+ZFVLTlVlIDBF6RJj4O9GAICIOwJo2rXbJax2JehAjyldZcXteVCCuocw8+Z0ZAM78YI8ZUAyPaUI4pvdzTfTRaTprNXeO07opQRx9kREFG1QD4eGjluyoq+sWB2HuXZI3N0kLC4oZW249cTRRJS7RVsGXAJgZNcrwd1Q+XOEtOGy23JaKU+49DSHZjIauaMcMJqc4qxXa0FDHtlEtu/MwFlzfwZt4AwCzRX25Qym+wWlrB41D1jQCVzArXbDax3aTA+pgDbxnY/bN2BLvKgHtrqnUFtHnpMRJPiXaOS62X3GeqIhjrmVs+G9N0n75lGNQNbDlvqNw6LptEza4/ZH27EgAbNBP0L4NWTfiOwNtEiiHaegTfZmWgD5WHVT/Yxy7aQ+EUUk2obAFNhYa0bSxNipbaSy0Q6Pc1U2lNogSFQa7Xrwpi2an7EVbakT0Af2oVMvpUGZEcnJPlr08yY6a+o7uxzslqd4o6eCNjbWZnSCsfZosS4OHeeIQXoGh9+GN2IEHFGHs8BuYlth1CO3op5py5lliGzT0iT4lmu6hRAMa+WkumF7gE16mxb1/HAdczsc3iSyRqCjGv55kQMY+VACPU3gHwkg9Jsk8TDbuDQDjWPlMrvB4xYy7pl2dmBsQ3N3JqRxd0SpXMFWSSiNtoZRcKu5UpFxzbMMGt9l7MqW4AR0obOYhe1Sty1OPrsuJZfTSgb2y6s2QFgPsx5RnxEKiKQe/R/VdAepSHsKCSpl6rWOyVhjQfCo1aMZC8yKBckQMIq24iTBmG+yO7PlbG/Ar7KrkACwcawXsCukvpxsdae1taYWjaLyAbtOJ90k708q+9OVmZNK1Wbh34NVnB9wmnGb7v9MiaubnACx2h8AVCnibCF6FsUdvQGo2Upn7R6BRyl0CM7oqt7sE34Ibs6HMws3YRsJeoe0uU2g1AcTKnH+HamJes2fDKuNehrTbeJhwA7dEjLtSNjYY3ofycdhG2iZVU6IvCN/egdPNAdiie40J11aKUYswRqRcYCqEUQgENqmJhzzVqbOp2GJDx0eAwZs4MV7PCqlmuEJknunoA46DAE9CVLEsDp52rZQCcLanHQ86xCKMtbtRg1MAsbBRKdhGc0jEQ5gHXhZVRSpmGpQijIWADmEZsGhOQRsWiKYoBhUyNsREQMjEHOjPpq+nk22mjuaQeqoghkiwqRo19caAmXHQylNzKQrDtokhZIZtYjrdF+NeeGLZEpKNLTBTEYY2jA+spKSfGBJhoh6MfWbSieEJ10L4gUw+Qw31IPUVITIC8RUaIhrwA4QUxhpOzCLLtKzHA2iGwsKBlOniOnTU4FDMzUXTQyldZAk8dBZE33MGUjX8MTLFQAQWFUAGkJIeJOCiPq7HYBKZEDCgegwgPEQUMTBFQxcYJyQhU8BMMIWGtKUa2ogg6hEIkBA9mIAhDSlERAsRQQgSUpOCBzSPg5/aYLLw6ADi6LnOg0JjDD7TUZk8AJR6MDIwRBgmSk1DGAAXQBUlkUMI8gpnGfPgqo4qAqjFcPgaEegjIFyISH2M3HSlR0MlCdY8KrGKjk5TRJ8FuSTEGnMlBr2GOECtuSIDqQciwUKkYSITNTSRUc1svtAGj4tte61tCpCE6SHB6cchggczDIgUoI+S1D7mXReEyKeag2w8CE5jnQHgoLU3RknqTD0IGUAXIYwznDZcnOb1GK8JUAeG1AwJrABSSAwn1REKxgqZxgp0Eag0OHDMVIIusWnHI9xei0Ql4XaajDXKItZ79nRcJMFo1hYBFNQ9TLypbpkSUc3LLkOYzfXRbpdhtLkq2H7KRNHCq0elqwYRiwlo19BKixVa1pVT4EE51uusYyMQTj0mCByikBBqrKESiHFprhK6a8NYfBUXLuBqxmQvHjG05ulmJQohJyVoQ8MR2mWUQ6WYlBtxzqvXx4bhixUakoEVLEYI1YtAOBAtQDt4nLGsiMFX8/HmY4lvca4xIyRQ44ov1tAYElYWhIRvZgfT0Cx/kUZNIA7OPBoWPJg0HDKIjfjLQrFoo7ykc81eElKSzKq6cfA2WDQE1l1OMlPeqnG1TuQuXMWflb4T5k2CsooDAfiaVhR3JLHQwrxLjdy6Exl9CDgn6RYl2rjJmS7YKWkmwxqHJVXRUMxyW6bEPPc2Q2zYzGQSqP2ozAzmSa5FuYvb8PB3LZVEU9cxDlJZVUNG+92bsZEFdiFQH8x8CIK7DdTMj2GEXJQt4SusFoQ0VDQOWV9fQ1zl9VwzmQ1tzktw3Z3ka9VNYiB+wAONJhLAHRsYe6C/n60LRfDoAHQMbccU0eVEf9HcVXi02Hy5W0/hBcVM2xHrOVUwVcBWeQh+ba/jq3fKLJh5b8tAB++TWVxqHdX1PxZVNNkBbH0L1iiMFLDFpKG0PkeCeRKsu7CcH0PXJfP6bVM7iCrmlXzOOVnUiNJ1hE+F4TUdHIogqrCY9jQxXBeA2RFSi8w1RR4S4i8WHrLgRedluR5IZXItFmnIa1XdSAIbLBqyw8lbzJS3alY9C3NYqKJFpe6p/aTokLjSSIPLYYI8JkfXsMUUaTgjvjqyERxzXW/FYSMyUTXVvoQFTF10rSh0mAs9HmcyadE5zNGQB8N9ovQ9UE+akUsCNmyWJpWj3WRrHNnt2ZY2c4bMWcYgTRhQc/gPwYOxBFNeL10sAYhuuUDN68tku8vQlGhMzT4CpHnO+QrBLCSggzaWKpJ70GdMmsbdm4Poo4BUDwiuANGHTeumoZAC4QJX/KaWNfsHGchmKJo9gjNZFt0HXKDvGP2w2FAJgEYJ4OGDQ+dhrAuS6C3KdjdNltP7MFyBkWbqmpbDVSKIS1V1MxrZVDXjQe4xZaP2AhAApR6fDAyRjE0kqSEWgGzmiz5T8kkzSQQwwzB4aCVRhrSYJqoI6JYgyzWbE1NBExbGMAIGVEGNxEwGFglAAy7HZ0zm6FOMajmjg7Gax24scXiiwwUTQ6JokNJgM7Xq1AYDZyPrzFhiqA4GnYpDYAKH0OZzQbZ7TEZcDXkEUIsh8TWClyQVXhW1Yt2TG3Ljai9VykDqgUiwEGmYbL0amsioZr5USRscLipOSYNhekhw+nGI4MEMAyIF6KMkdci1bmPGFQWkejhwhQh3uhfPqKJLCq21Z+wqqodsVR+iqCHttYbKdk3ObBe1SZotMg2AcOqxQeAQ9YY83hoygahmzi7QtanjNwHC1H0dB1nTYCmegJKeX08P+KivL8EVNL5BXT3d9SUxs7rFXSa4Dc1dptko2aeotyRjB+04vo5fZiRg1wBAPZiZfUWwXZeV9BJBDPIzQSql0GxWiZiWIYHG1SYD2XRe42JzJsQyjrXhaYPrc9Lju6RCmy+4vmOeKpBJY6qiHpyhJkQ25lkGDdVMiFXsFMsIhV6cuJ5ejVDTEK5gHihYT0c/B82kbwMgpXKOYlHyEzuh1uTkazmOl6s8J2H5hmbXe/ThEIPqZ0AMemqCVGq8/hUTk8ZjMM1JAuClE605qoVXD0lXDaKU4qEWDdG0Lcxsx0JtGyXUXMltsJ5i44jXd658rrX0b92ohVOAUI+LBwTvmUyv9+julPB45hRM9qWf6+n9IDUReEDzGDh4HUnMJhqMErp4oaKy99Z5eDzF6FEQAU17YAE+aD8t4prZqcC96qTV5gpI9XjgChBxpMenNFRSYJ1Za/OtKpUMBGY7FKW68afOEiYB84qXwRgAIXXrC1QBXlT4t8a0ixaIdGbumTpv1kBKWAvPuIUecvS1L6yNxCfcDLlQdOC6FUhZC17apKfntMubGvlSuUykPujkUg3sMEiddAbSbykZlRrWuLeUsA7j0ji7Aim2UExZkWWfi7p9aaM9HJrS3Ksy76vA1WPT1AoPGdPgVqToV2T791kRwJcfr4HHJoEVwrauRr9bogCJrHreUree2LYHCTnwSmcEu4V/uvL6LMc1TjLNDlxXQWdwaOrBxoz08KbWntGhn9fyAx8WvZYfBTUTU1nXfuAqFDYktnUaWbYIUN04qT6POEzPm15LT53KNNeBqwetqQU+68A98Kohpg4vZGNKr71GJZ/OPgLh7Aams4q8KLWULWSiiSUtTDQwjn3JMY+v9F5rNmcykGbiRFiQDZiHg3VMIOGaeSM2vg1svIejgNQxNFQBlpHMJmRJgXCBwDB6MYxec8HbpHw4uU/vkvwWXZBpml76BTYPxkoaW99UF36SqTAlmjfjBak5vXUcl5TtD2sa8tCWg+QqxaAaj3B2csmPNV/zjy0DRDPV0YzUUBUkIPzutI6eplagUwL+BepgwoKPQV+fYlj5aaDVw1RXgsioesZaQ0dNAzNf34RbNl0GtqjlOljTNeFoVF36AjHTi2v+XXAtXXlYq0FyVQw0tKYcjxSgl/AW+kwcyb3ObsuQbCVXNmFJMCc7su2A9/3VU+Z7L4d9t9p8uw+GVg9TXUl5i0l4c9t0RQdGPvNug38z/vq0LLY60unANR5OdS04LYDw0r32vq0a9bKkuyocCMcAW49tqhOZaAzimUl2tmWerzfaiRpo9djUlSCqcdB6omkQL2AOnty7UE4DrR6guhJ8s8eachrEi1HuNdplRecW7TuiphsAaxqcXEVNswnWhmwAZsha0c6GzyOQSfrtLCf2YvrNel011lGP1VQVfD4TrqMnqrGhuV8aVbSvOeQ1VXEfrObINypRlzkAfo/yhp439743+hN84w0AUw8JgAZfeOPA9FSCUIIbtbxRoNMQ57fnHQp64pzgHJVj2W/PqUbdJv2H354TkBTt6ibJ3hcblFVDwfukPb6vppr9lyeXuySlh6f/4/Lpk/ttlle/P72r691fnz+vWtTVsy1Oy6IqbupnabF9nmyK5y9/+eXfn7948Xzb4Xieci7q34Teji11G1ShlL4JtUGnuKzq10mdfE0qMgvHm60EdrlNyvrj13+gtG6P3u8F6fhtpPPQYJ/HqEsQIM8dhaYHPQM4/bt3fNGmWl/bM9qnZ2D+g4mGp2RYVIm3I0TMdCvqkZqXaZIlJRGUHSrrh8GC2pCRF1mzzaffIs+pa18+VIQB6d88Fva7PbazqqvXp4jgusUX2eN8W28zcYzDN3ssr4rNw3FVHWcJPT5hcfElDmPN06zZIKLlMBlWshOGK5W6UPGc9OUHkXdSUBPOReIUQwD2+IfKPNLpqz2mK1xnAuP0nxw48I7YKQAi9rvLrNRlIU5F+8mNV2QeccHwHtXJf6CHH53KZjHxJW4YX6NxIZWRcoVueAHiM5/tcb3DW8Lsm6tiOKBgMUqF9ngvUL5B5VH1BW/apZFFK5bZY+1q/L3IhaGz312xfSmTXR/hBSHlil1xE3n4AcyUVOiK91VB425EFSOWOWiXEhclWcsE7TJ+ddQuV8ktoGDarw46pmktgKviKM0ELcOVOOno5muGqztAN08FMr7fngvLv2hhPJdMDMHgEw0WO3Mmude++Oxg1YyYpANNG9tGV3seC0e2bVytmte42mXJQx+Ax2LiS1Yz2+QDDR4Nm+geicckK2uudYLbkE8eRf/JQcVQIogDGT+uhjXeFaTz+F9o03c/VB2I+HyUggWOeTin64OIY/rqYPr0icFEXOx3B2yUIIiYiX3qRQ6jUOaBVYHQHRcgN1zBerh+zNsWxOuKhHQ2LK6suladOPT4uMnoq4swW4+F9ng/5fifDbpEBXXg8FiFInucp1lye7Yl/aGBJPLQgWKHzUctWIzth/1vihTmp9b4/GkMnE7LXNZle0Wmar2wEdYxAaPvUmZEM4/Mx12Dht7L4sSXuGMEVg2hyGUbRs/3z7PmFufiPowtccF4VTQpsK0bP69GCs5RucVVhaf0iSESIGLz4H4zirWudnHd4NP76Cyu6etqOMiUMNWee3QR9haco6++Vq45LREarqELJgdX4uDySu5P7tF2J7gPmc9OuPrlu7t4JSDkyuyxtpd+BGzDN/eDou7qBnRO1JXMLcH70txFlgVqa4LBR0OD1R6DPRJLx/eHLxCTjEX7scKpD/9j/paowfM2Gl04DOPKHOQ1y4ofb9r0I1cFzfcuiK5cvMS+Qe1FI2xOGBx9qoXzY77ExcezAfGx35fbze1R3wwZAkK1DpwZwVL3qCrPo4FoiyKG4duSmudDs/2Kyo83n7uciRwqvugn3rNLGTBCGZHNkOHJjnoU8zFlJwYQa04l8SfOQGOyxSV/f7z5byrbvp+5/x5g3w+n3wvRemhWxMJ+dzBad+P9a65L02cXA/hotyuL77KfYfruMM4SkbVs8zGXljm+xMFNu9soMPIli3OpyJyvsuK2f5rEgy+1tWfiya65KxqKyE8VW+AQrUSGQB/oEXvFft/7LJ3r3uSzUdb6+jNp6q5RSU1Pn5eNlOtGLzMO+90BW1JLbovhm0N0Zveg4X8hsn2oE+GoRCp0xvuhUKMdy9bF3cwTj6GMrkU1K8937Ss4fyp0iDVLqn40QpwZ833v88i9i+YxeYb688zY2KiIhitwCIaRfAQqt8DCs8M8QuYxN9ra8670surnS/ZnOwzvtYnjZL+vbgMZ56AiYBOz9O7lTYMV+5euxMGqr+gLaqI7Y/rq4FbrEhRwHrXu0z7uIQx1iMrbJrLBJpW6Y75MshrG2pU4OGQ3W5wPmoj3xXIlTkfW8LERV+DQwyGll0hIrmDpI6PXKEPSTZjxo/vR05idBDp9Ggv3dYT8LiHrNuxvEIr26SWgXXlX3OIcdLHLpW6Yh/yCSuQSgMNsJVlTJ/JVGvb7sru79jKjzD7MZzfqyaimrw69arIM6NT41cl22SW5GBMxfHRfFTvPObwuDmUOXhRc1neUjwQnyvR5NXbQlEwxxA5SZIm0MIOUNeexgg6SaocphkUWTUbp4llKkfXMZ6cj4RqRb99xngIXLIRChz5Kd9SOHe+n9YLwQrTihq/OmF6CmF66YPo73tEtf5LJ4dVCkYMNfFfkCFK3XIGD/CT3EDbm8xI2zb52sa0MhF676SXJZxOrqjmP9pZ1m6teayNUqv49ayB4ZSpyxQmH7ollDmvLj+IdqmtUnlXA7Qa51AHzXYmQDjdQ7hSegEqcgpjFMge9Pdys/ZwImyy+5LFdeVCu5DNd9OhXgc55AS4QQ9FqdBy3KgfeQmVR+dxF1defy/Ebz/yJoEC/fi3RdwyY0HzJYxPEPTH3cK4extcDFs+YBbjqPNwcN443xgYsPEFLu8j3rtYKWP+nIgecfeBOX/dY9i7CEA66oKjNjSiBXG4O3ApE6b78PHG8hwQ9MU2UR7cJOx9yDYao8HM426KFBlfWnEeB983RkSgiOoYil3OxkoyszUHQ5m0AA6UUMA6xCLjCXzN0lm/wd7xpkiwT9D4IsOilkrs2I6tC7uVSN8+7ErFUuM/zx4GJ0JbYa/KpIVC870sxQx218QlDHBYYA2+1ttNwl7MNEwOtKxHC3cjqIhUvmy1sYTHFXuaVAj0M4d77NnARpg8I4TUGdSNKIA/vTrSManMZJ5ffGqGD9IODfCR5c5OkNM9JSVa0GnJdq2DsW3lTiwkMui8ucQ1v8E19nIjxNOx3h/70dSCjQSxziWD+Z4NL9LG+Q+VogwmxzBCEcwuTuQHj58od5LcheosIfkoNjaPNRsAmyrIR2mV2h8eQxNmdvjs4Xfo64syy3x2iyvKsE0/mvSYuvgwod5G/++GSnAI/DOFOjZP7HS5bZ9jr5KGCKSPCuLfSBq20GCDZUkM5WDdJdZkQYwvBLAMUu5zGszWl41ip1KnXNBDx6LZESLZN5VK3qMexohw7CxS7yOX4gK4omEyBi/7qKx0/pBl6h/Lb+k7UYBCEbwvnqMSFNI8qGI9WWgOjRSNpYgjCKY7vDu9O8oTs/ySlyBW54FSn9BDLnE5wMJXkJBtqH9/RLP/SYY4Can+RoGfVSSXRtv3k4kwcU76KbCYUOdlk1PGcfycSSyqTvcqdzMRKIBc3ZpF++1uTtK4b0Y/JFTkfeLT1j74nOEu+4kxCr4byawkeBAzhMA8412CXSx12A8WPbux9iKd0+ACUO+2S8M1D6+84Lcqhf68Q2ZtKOyU1oMOBRZJ+a1Np0/c1pIuaYqHjflv9QIu08bZ/y0XdZusKIVOLt80WnncYwrWF5N7Ugghh38JQ57JGQnpVvsQVY/vySFlkcsIlqNzBNsIbNPSsRyGYRxCAIx+hTY8Bi0s1UOykheg6/Kp5eNXUtei4kkudMX/B1V2Gq1qDXgRxoEynezNExP+c7EtlTyEM4XB4QnaHbVWcirfEuBIXf6yEyhnHx2wDoJm+OnuHj+mJNeQX7goc1uQdSnGSAb3jS/wwjseTV3gLHF5qIf1a7A8wje2JcO4R8yd5jcoKYjQIwMkKoIqYw4Ig9tECOnkELNvTATrtTK8w6uRQ0IxCkZN9g6r6qK5L/LWp0XGx/Yrzdr8PjMMI7DQWohO7N4yPdrsMi3snEMAe/xeEb+/Ex0/6bw7UAfa97jvdL3gjIuk/OdALGM9b5/GMa4RevWjAPNrSKRYl0D4jKKOGlUW+LTgc2Yt+Br7QYQWIm3B2Rfl/NDOCv6PygbKa5PUUytwt+U85luIPxDLXFbO6Skp8c6O+aCYAOAeIfrz5WOJbnCsCRdlil71mhXqDAXCNyaUemN+jpGpKROmqwM5BeLRwtJUD26RCD7z0Dy1uFsABf5NvMtQel8v+ZanQFe85KmkyBtgtqQDxbIPSQN/ECOE9iqJzbpIVTjsSFszFNsT5OW5PYmV/IFfkZDOdl+0ReV9dspjE4tUEuo2mXFCk24DFI9RNXXWeWDd5RXVdS4ceg6dzQpnTGRTh7JSQSApjEorce6pCDJW7Y4fUpVj28wQG92f0FVmCd0Uu34eCyp3WfRCrH7ZhFtrgmHY7qOJXHsIxJqc7hSaWGRSTwxauRutFeuow4J3Dx/fIYbxED8ogRc8AxVdFTWxtJVag2MUw3NxCJtv02RHXZf0gRlKy311c8jiR3PDtJxfXcseFqshcqPwQ4qrH1QW1q0605VIHzLDZ6mWy0tDlS/wv8RRg/OoZ0ltdFZcoQ2kN4zfBuvf/I3SUKRU6Ho1cJPmtuJhxBXsPT5/ZSft4gnfX6BKM7/h8HO67m6TJ6s9km/tesl+lwtWYgr32DLzp1e/r3e1AZc15zMC+uVc4T8TXx4Qil9O9LZLjGaavj/E85hIV9C3QXLJ3uQKXU4IPSIgc6j85xe6VSV5hKfSVK9inCniPNjihHA5cBhfLVqMA2I6FaQEWk4cq0FefRx/Qfguauv2ymtnpT87iaGkOl/+13KV1dsRXBmJuIR5dYg72ElagpDOYfCRdW32tDqDYbhuWCioHhArm4ITQ4zq4Ch6fq+CwRf7Zt8ix3DZ7Pivuj7i6/Ecxjo0ZhAEnyFosM5nPfdOqYzU/bMxALpoMqSLBLMCdAo7hk1yuwOGso8u5qUh0J5e6eFT7y28waqDY5XC3qonybhUt++K4fP1RDefT2rnyMjoE4dVC8kAZpLvjpm5FgPJpaZgAcLnVgPnNUqsMdNMzAjgEDNzXZSLvjJnP61HJTFxkoC5mMPkoYW31tW5pSN2ifIvuPydZI0VccEXOps27ghTKGpst2qe5dFb1LnnRlTh+Xg2P96qvi/6joX9RvEATOn9HkA7H+n1BfTgljFEqdI+4hmOtfQwi2O5ZTyRcuCKaMUsczmpUjleDhPVYLnXYzeANurprtl9z6V0JocgeZ5+uj8c2ftzTnven3quuRamPPNixZGQdL2CPofKNKOdZAaYOwCpb/Sqj0is3DER6zY0t8MDXmk5KpGOpi9FyXqLOEShndOGK1sbnkQJOeWw+cadGDOu3W1ShdX5BdWfVKVG5zZR1TeQrqfgnPiHrxxjvoAxA6M+0+zg2i8m4bP91B10HBg5l4DYtwg1O28sHjHUbgZVh1P5MbYtv/ewNj6Tz70leWgOs45liZ4LBLzUxZa5hu0O8hyJylyv+iYVKN1mBj/doMPu85eOEbh6Jgvtgx//Lv5wWfoUk/u64uy0PuGS5gpVLxxxyEU0iHo+Dfp0cOufqMM96dpqkqL4sylrCyZc4YhxCst5i0UkLFDuYtPkG3Xdqm34QGEAuXY0u6Oe8fQwpgq1J8PgblmDlfcv5fuflc1LiJAfzZEWZLw1+/3l0QjqTTRP4QkL42wXLvLEQIy/iz5aZ7LFm3DmqKnybo80Y8iqaEUC5Uxzko8lVdVa1OZAFxp6+7sdbMFnK/7kVTg+FIgc9NUM27tbm+tjUH29aFK2tCGW/lUFWs/qxrBO2zrGYPFY0ffV92yZqWY977+EQILSMdTeLSRfPjnuMLt6+ktKtBZXbY6f5VMgn6W0K9rsLAw/PKYkcPH33WK6YBO3Ko20B5id2DYsTHkXmIgjb4lIWw1G1vqVqHmdU/MVq3c4oFeaT+11RDgIl4BXLVivx/TEQgYsr+xPeCFpAh2ytZmectXZdXDIDh0TjjoXtMYA3TTPMQz62tSnmleT2cgZwX8PZ2Xe0+UdT1fJrglKhg+uudbGpEMuly4Q/LrcWt+ezkHnMFTg4VHH+TfmCvFQ45zWFdW1vW3LOssftTtijbXQV6GbVrmIfFKoVAjvo1YNePejVP4BenZ6CDtGh4xvJ7vpSXXUe3Ti096bB4pEVV+Jw7aga33L+VAqnOGKZez8llIH4oKyHYpmLrsxr1GX6FzUmU+B0UQ/IN+iTbPDknrRfST4g5rOLbjwkLjQlLlTOQyvqIr7pqysm2VZgv7tx2mdUyhzCFThIwh3NdZQVgoOQ+bwanc+8xRei9Ec0HlpfU3f9B0Bgug2PFBu65Cr+CVXi3TyPEQl0nGRpk7WBWl1CFfG6mlS8GjE5Lcpme15UgT7dEY2HmGjqziMmV8UOpyKK8aM7a6vY2v1MVD4NdVp9z482mxJV4sHF9NlhbCu6uK4UXY+r//sUs5bFIshZi8dX0BSV55G0tkURxfhxb5JGSQDtr7kCh6WyeylBWCqHj07XYTpNLN6F6b86eBAw+iGg6T85eA2SqqYNSw4D5rs7NtVMQuXu2NucVCDeruSg//an/5Jwxeer8xZVd2/KotmBOm8secyBIh/GdUzUUsPnfSg8KuagWccVHJSfJc8c8iDNZAK2KiCCCdji8VWHisrz6MT1abCfm7vVh0Dz5Bz3f0BsT3L4gdjB4bklByweMqiuOo8Evkvy2wZwd7PfHTZP8rsFV65vFrRZDgVm6T452FpNJvBb92Ve81ntLV33W8bdGwvFtsvkLkf3TkUOOHe7sviONn3dY/liHwzhsHYUtbkRJdD+dWP8deHnfuNkTysCPS4t8yQ7auo70mifQ+MCpS0tQ1YJHWaPlcMN3TyrSVxX3MlWyijbf3I7BKdUOdtQmtxg0aqDyt2x97ahqREAzL6tj3Rir4pvSBBD9rsjtqM0RVWlwsmVOp0zfsfERla9VwGVr0ba+0u7IWLdbRDc5VdRbx5BbRuDsidwBY745IAi5vP+DpfjKKP+rZL+tEzEKZe6GLzdExgK1ECx47wQK7NuJLxCkXt/YbRyqYPq6N4dgRFLhc54u/N1pV5SAblz3HFTlihPH46lB5BhCJcWunoXxEAUMbMl7n2+Su775Qk6/FJDuUSugvlamM+ufN18rYuarN15mpGOQewtQni2cHJvamGEcG/hitYfn4rSjQWGDGxROzYY0rXFXiVoxiZCeLagGYsI4dkCqSvLHgzhqZ+InsfUck+yU4RAklmAx2gbJKYFeIy2QTJbgDu4qroqwkZ5+urIHzDX+XAa/NqJUOTkZiCEekVKcjGUTShyHTFVDRekOxvpTjhU7oNdhdUF2wW6IV1AGyi3lFjmgvVHUm7OC5zX1RdUIsKNoqtQAeIST4vSb0UzXUtSemL0kAEtykmZFCDu9obK8QyVO7gdb25whoEXhLkCjz3ETrGH2Dk7WulOhkhEJ3zHhEUgo0gP6RJ8Wm6AqwnjVzdMstE8fXXEBIzZb4Tvk+ob2uipqYJx6/Px9+8v5R53X90wndzvcNmF/Ba5mOAPBPDF/18oAagslvtx8GtcorR+jb5i0bOvAnI5hh2rHaXtmve2yIAjWRVUSEsQB6mhvFp6leTf5M0hCOCNXxZWEMAP/9mxGjUt88Lav9WqxDyWe2E/+5qIIQJiofu60Nok/ckSvELwEA6S1hArt8T/asW0vR+VpNATCjq48NZkJtVDhrd4gSop25wJ1kU77uh1aA09YYiQFqARqaGcgitGK08zIA2Yy3F4md4lFVL6jUEAl50ghk+0uQJ3HyV040Ysc8dKt4hEpHdNzdzbUfkVrSu5BD1lSLqjPn78uUKT2G3UBdomOJe2mwoQ+zbeJvQua+9e+FDU41MJfDsaMAe9l6ZoV1/dYdLjhHxuTzTfJvnm43dpE6AHXc2h2eCW+FSR/dpbXNXhL9wBKH2eubNDM88R2+inFeWU+e7oFAGPcByXJ2+R3xNzvcE37Z4tInMBKH2Yyw7NPMw1tC1iYb87aO0Kbb7g+g5kMqnQDS/wgA/z+Q/AuHF4NYA/F7tg3puADLcAz9Oqody5HzqpFMscVmbAQ+zuGT6rhh60ifUTIJcQAOA+drIZ3kH7M6jcxdpK8Y4m4pDtWKHIAycQzyWWOdjiKKc7DdncZr67YgM6yBW4BDhWlfQg1PjRhZsmsrcGJ5TGWgL4ibXqqDEiBGl5huhr6s4YrEUbVARYTUXL25PnEbOKxMvbQV+WaBN1gOfncqkHZvB0XC51oaSqv759VffTt4/gsbzH4fuwB+qWV3DQChDfNkAyKEAcTAbj0Wzokewc7+MMyc6A+xJCkctCNVRVmj0AgMstjBTlUyo5OVGhVOzQd6I/vwCvZLHfHQJFm3yTIbrKCCGizHdn/XpMM+BAGrYrcHIdzvBiVR8TyCKQJE0GWJdRQcynQL/CiMbXqIDrzmlUhBsAbZ85JN0XZ+a5KoYjM5B12OKf2L5lfdlxvF0yRh9nlxWWeTg1bhx/NwRBl/bfXLGAUXVCkZu3DPJBsN+X34E+Ogmi523dddUQqRmw+Dy2ray6bk1+VSbpNzIw6HhXLHPASgM2IeuKK3A8g0XwYbFY5m4WgWilwj+A9IS7WFhMAVK0pKNlbBOIGBi+e7htYNl09no/mozqX5IsQ3Uc64XF5WO3GOrPxEervMEYa52Ic07S1QAtKKHIE+c5vd1ISK7BPYHsM2bmAiWV6DYavi1v7x1ttjgHIxr5ktVomwtUN2VOH/NEoYmKOVRemyRt/XUrm9jLVVzlFe9soBOt06LsZguSO6bQBW877aj1b8ryLBR641VH2GkB3ecNTggkl7pwanJz07nZBGadvi+hqNSUZsQXvoSuAHFpg145uyo6u0REzpc9zkDOvW1civbnMVHpMTYvPDavDYwJxbrXg/Ok7LdfciYGtsT1ZATCyJe4bNgmGkPBQFD53hyPEU/K5zgVHEZFWBiV9NEv6ekLGGIfa/hB3wEXcYMUnoTOQ+NZ4JhH5dF/hTOoxC3+i76mQT5td8ArG8N3t6itfza4hIK1hu+OHs/ka4Z6VQHjVkO59PsquT+5RxIZuAKncJFjIju3RSk9fyUUeag++r5aWWSQ2lfBOJ9JRkwDfFa1cQ9IJOzw1SUUIiwh5VoUVoSXa2GcMVTXoq/VSq1LFgcEsKR+PLzmGqYV9uYbS5uSXsPvL63FiiiAsPpFFVhimkfuxObl3b5cfriy5cx1cbktApstptcf0gy9Q/mtlOODLXDEd45KXEgBkEKR4/l8W1tMhcUWODn5Iic7j2c5xbo3fZbjGicZKOBi2U8s5+0EkIJ3xW2YiDOIPKRbW3sewWaaBPcfcvG+vE5wbhJ33/hjZk6KORqDtnmzgpgUxjCTM7jNL9Gn/hfUMV+05G7iJKeOCqE/48fVsFCwXvPTZwvqMdLUO/QdZdJ1Cua7kze+rME4BL7EHiN9tQVEyBU4LNw7+BHcnc8juHFPB8hIPpWCL3/86HS+iMoSlRIurmCfnnbCW7fi9nn4Zo/lbV3voOQ+7Hen0GvgKvH0dTUqaUxjGbieDWh8VjJ13ZmMLTgvvlc+/Fg7AGhHot6P7DGWd0p0FR7NyyTN8jkO1yNY68nQ+h8YPLvNCamP75LyVtzDC0U//RnDUZrFeJlpRONl6CvrzsPiXdsijumrKyZZZNjv7vbRRZEpkzwPZS7OoLON+GzY8G01bEhsrBhsOKLxYENN3T8WG15mjZAusPuyl5Nbr8c295W/CuWoxGmk+BIRm08+KyOKtXP2f6CH7nllDtP01QmThMSlPpCVzTkjm2ug4Z74+OoObdHnpMTUvRTGxBwqDw421J+HfdtGhaOd7tOSZvQfiOF6qzhot9XeSvLYZMH11rq3kpxUjs6pyyoDXcnsdwds9JRYdsIxnx0cl0WZItIN8v9RllE/nrCHAgEc3F9FJd7M6D85OeKKc5zSPOSA/5ct2ufu9m29zV4VG2n9Zb+7HIflNRGc4Ub3B1T/KMpv4ukYDOMULUUE+qGVxuFdPjnuG4ZxbuXkPr0j9iNq84vrG1OBrkZ19p0KDVAaxuYTCKisulYlqnts0u+RSTnFm0d+Nyrs7wpSCGeCGopc/QlEX26TusZiDni5dDlnl1JAm68Zru7E1Yn5vE/FuqY7FMpRFzSb/0n7hJwwL0KRA3fTJ+mmNKKgAaGCcWzlQ7N9jVKierMKwM+V+vS/jdAy9J+H8W7lNcqLLc4TIgPaljg479YuGlFrgACrWbZa1dB/imD492C+9r+y+trdI5Edf49kt5iSlYCw81VSfYsTni1j9DGErLDMw1Fs09IECmVOl7KaXH48hvlsj+t9kt7hHMmsyhU4Bs2CKydf4rABxXlrYwAohSKHXjZpitAG7qdQ5iD1ZSkuKv0nl61QcUuDAc4R2VXL11OEQne8YAySVLhKnRJPlwQqkcezozrKcCIYbP0nF+u6yE/ud5Q/5Od6hTIHX670lKvrM66uoZea1XX3MQdklytwmDV0XxNFLOkV9ruLrn+LNxuUi6p++OpgmTY50Ru9WhdsUr5oNdLPPZMeJv4cKg/5N9SfRwFwjape5VMCuQWnKqODpMK1bfAeaYTP3xrUoE37HstRXRPZC79gBqL0YHZLPPMwPdO4iEgocttBEcOGuupkBpcKXQRUvA/XfXGxbuWYouGbg3dJytHvmp0/3NZ4j7dIXtWnrw6Y0AYn/ayItBHL1ijO0YQ4THQXW6VKTLb6YjKc6auDMJTFVhCF9ouDWVkIRmXhduVglz2IKMaPDiaz8Kb4sdM74q9SwXJsP7hsbr/+A6XC1nH86NCPYiPMafdln475S8LzV61fSTjZHj+74YL2/9Nnh81Iu1am4DOpYplTDzfvk7xJsuxB6iRTsholyA41TAuymDzUoL76TO5j+c0959f2+iMweTXmCtyiK+TgCiftXpSiF6r94nYlKZcGNH11MbeqSr7+OX11dR5cVuJ8TZ+dxvca3SRNVhOtRjbl9NJ8JQ0WAlmN3B4n212Cb/PAYIUei0+wgrLqWl1rP/Mq+0h31L37+gptiaoMDfMWkHnwtBHDWll7HUb0+2KDsi7hAb8DZL67XFio6q5miQTyCEVOhnpnZ3T3N8WOAsWPUb3EuzQyj5E8V8yTv/GtPI0ZfWkvILcPUOyD+6Ue98sQ3L/qcf+qxr2nJeED+lG9Q3WNSiI7ow87bGWAcXosELaIZlonwNblh5l1cMtujtyuqi/olIiUp/xLUdKXyVR364Di1cjZaVE223giJqHzkC4LHPMIll6cQoQobqKWlkDS2cPw0cVc3OFUxDN+XEIw93U8XbavP/d2fuD5NIfL54DagGAmXo+k+qifX8XdYpkLZ6pw8iXLm+U0G7vQo/aLUxAgSsS567+5YOlTxrx6OGrqOzEEBij2wn2BUrzDUjwbDPETa4z3KKmaEnXZoEP9EwwqL++Etv5afRNzpAG8oOf+0jUh7OSteHQJtfv5f41Jk1WwwSZi82dIDYoDT/7kPHm23RUlfaXkBocmOOBQeXCjof5aWfG0yDZQAkD2u1twEpQVmP3ueikGwseXLB8C3E32BaJu+o0cSwUUOyiHb1jIktJ9cbAVk29iqHT7xeGEsr20+DEXHZnsd3tsRJudYpRt6C/B3SoUuXPacZHf4NumBML0FCAO3HJfl4k8u8xnB8uzRTDcT+NNT77I5eCkarL6LL+Rzk6m785Xj0gfNJePmNLVLAKXD3ka5wbihMjnwoeu9jzq/yTa/cPLoilTJKVWYj679kpeUtjvLoKT12SnK6PjClxH+jap7qChdt9dHWxnUpb56bMrrsu6VNzwH0pcMb4qigzC1313sVrzFL4AxxasRi2c3NNF+DXaZUWEp1hEbD4hZkYU82iJ3iaVE6OMn5c0OGOZYHEXv2lWIHNTLn3cwWyhyadopvGrMsmrLW7v8UE0U8GEtWJuw9WI7Dbc8r0MsczJ5dvtniSn7/DZNQICDhfxjxVpa4Keab5k35EalLfxd/ReSu/DFTjJohTBOXxb2boVxafBofJesQ4+jZa8RA3URGnLWoIrdHUCyr30u5FJfn7HZHTKS59AucvJTK8Ke1YQDmWEwuWNgY5Rlf4YoHjJfbGGswjTSNY+89lp/qnOlpwg7Hd3bupcJzJBofJ9mW4fb24qJCxjwzfHoEAgFNApdDKp07tL/C9BPpjPDjNARLXNaMrTffy676X5uNju2pNYnYWiBHI9sf073h2V6Z0UzCWXOmDOUJKLCanHj6sxB14l6beznMx6+i1euJQCqYeJYI1p3eEk593L8cAeefi8r6CrxxeyQAToJmkT6JaRblYAGH3Oi63QrNWo/YzRD3mXOn39iU95j8lE3RblQxxuErF5XT4zoThw0eq4qNflcZhIQOYVfmnAcGCh1bFQv4Pspy70bXcWl9fL7noEsx0mdBbRC4Wl9MIL20sFtpc/NT8dl0VVXaIsi8JRIjafhc2I4uflqr2FVeZNjBfTJjxeoZTqyjNt3KJ6PwcXJB0H7JzsSlzi7ft1WYi5H746XNLCm1tU/70Qcyiy3x36hWvR/9p/cpCPVSfnn+16bneqdVUcpeIzN1zJqjQDzcEfSztMuDw1hA7BPFqibVVK6NZ9czlGLMmQaO8l/w5X4u4fl/UXX+IwUppPQBho92lZ3dA+V69M7SmXOnm0+2N6+luQa6nwZ9Fmr9AtzoGXD9nvTrPz4+R+l+Qb6d0vrsRhP1icFllW/BD2hONXB0zoxxecbyRU02eH9TwVOa/74uIYfhBlof/kEoCylTKKDt8cHLhVdZwllXg0Mn5dfr05qqoixW04tOwlGQWx0/TX7HvFpLcal4ihpuQAEeBZcGCt2yjVCdTcdRfOCSx0VquUhByackrmsVfBHb6iD5VDezarDrO4HDv723OQH+xZZmj7mjng0z1QJkPLL5F1MD2IBTfIWAM5YEQYYeaBzoXNdo9nbxP9Cmc04mGYHpvZFquoptxlrnmcgTQVkMWcdgF1GGM6d+y4yDeYzueTs+pDk2W/P71JskqMUTCNPph5iAHVhhlcH+12GaZbuP4UBev1hb6eyEYD9HBCY8FOugYC52pEHYGbtN0MXDx6Yi2tT+QhsUe0jlwhVFUxBgvmxRxcO6vmD76nYSzC4to/m4zucicOmWqpmEPh9bai9oB91SwxdjKMG3o0SzPC0OxwdJrc6jckELjiGHaCsdl6yIhD9xteBLXuXJTZJpj2ZnBab0JVNVTmpuOmE0a/xq3Gz7K5vEA/knJzXuC8rvq3GK8/VWjzBdd3vTNOd8ZurCyfqktVLPjC2FDgDPC4IvCJucNr3KWYyBBP4Vze4fa7yxZXqhNjjysgDeQjEVtMjSPiXiMDmcdvZqHBu0rvnyc4p8/g8SCj+7b/Mv6uhg+X3XNE7QFPNdWjd0m2SUuQapeklLgE4hSXVU057WtSoQ7k6ZPhmGk4MO8TGPwzO866nGgDADHc8Q2q6qviG8p/f/ryl1/+8vRJ+6QjTUuc3Tx9cr/N8uqvaTuNSZ4XdTv035/e1fXur8+fV22L1bMtTsuiKm7qZ2mxfZ5siucvf3nx6/MXL56jzfa5WL1Ha4Xll38fsFTVhnuEiDniHI64aJbIp0/E5v56lm/Q/e9P/9eT/80z3G//gSROGTjoAt08UTHbb8/Fir8BDEs79vtTTOndyvobRNihDcrqroNRKNQO4ekTypP0XtPIl8+16Nk4iK6Z/HtSpndJ+d+2yf1/Z/HVZWNEN4RB9PTrMH6l11sc+zWcf0ToE80rPh2DxBhknmbNBp3ll5gMNdmFjPOsGi5Wk4IapTXahKCbbmlHGGd/qBsBU3ueC6J7+uR9cv8O5bf13e9P//yL+1TUZaHF+fLPf/ZhmCjDfo/qpE+xFofzKELuWDwSzngzLQXO+PPyBSLKtjyqugCmEExsCFSEMXbovpTJrs0Nk6v6Zo+Lnqpzc+A/ylcFNVoDtcj4RB6z8jjiaIdD/RxR9AcXORWkbadwDVs07DG43mhI7sfzgD+A6QAZDZzq/dMvvzgj5eNfbNnPeoqIpV23N54P00NtdGekn5OsMStRO8tzuD0ZfZLpg7kZ/hca93B/hOmesrExjaiHTSv99cnZf15LxLqmt6np29f/9qSVwr8+eUGI5Nod9jGI6B36k0+HKFqaYq4PhhMFI07Pfn3yv7UV+F7QYQTqyxHhXCN6Ga2T3rrDXvB7pvsjyLtRvb/wmaiegMdNRo9gDcuHM/pPOf5ngy5RcdwmVNIhd7UQT7Pk9mxLuj5kyom80byogwzPiNsiDwt2cTOrE/gukeQFqjoP7R9AKANWvbHmqHl/8VjlBmLPYvwNyCMagWcVfUTlPGtuca7gZzu341XRpGqZkHFYs7IYTvtHYGMrd3Cge1nnpLNCPW21gxBbMwJ3hP8HZYLgSTstERoOx0LWr6vk/uQebXdBbkGCpF8Hu4Se4DJoo36Gt/5CHFOdpHTM5Y/HT95C1GORZX8Eadj7yh5bJ49Pv0Rwz0YxSakP/GP+tqBJLm+DhOCIXil706CqJmbB56IOQuZnKUPurqSkx+uozaLV4aGPXNSYzqsbvU/yTSRM3vsSJwVxlFc/kNYj8bOoCTpadxXR1VqJevjQbL+i8uMNFZwqhONn3mOOoZXDGdzPz11sZj83DptqBnHZ2Y4LVLPbW9pt4I52u7L4HraE8JkK1ZrRzlnVvkHkhcyZh/9IzNu9Ldw11LQOQdyivMGUDq6TNL5voPX2OfNj/wR4XKSq4JxYeE+LcpvULkdqalyXSVbH7ufRZovz42K7ZUImAqPNouwDj25ucIYJl4eRLnwX2D86y6HQKQbfXWafiXmujaa2y2Es9C6pas0i5LASXnOohJ69cO+Zfu1x6diIyevYtarfFbc4j7U/IPhaviYaX4nSleoDQo/xEbXUDJG/Uc93zMGJHjzRhlSbnTEvnSMUKRXnwHtKoF1lWp7jAYt4cuw+K/Q0Osn9zrYneeqQCL3xiAsdMHY7pMBOcbjC1c8rXNZ3r5PJ1+EnniMaXjbteiBd7Xfbo0gIVFsVu4hj+QaGW3dkDPb9sd4AjCvZz2//q1X3noPTlVraLwBaoZq9kIHbEC9MCk3qiYsYj6VHrNlYMUiyCXr0/7f3rb1x48iif2UwHy8uds7MngUWF3MuYDv2xEASe2wnuWe/NJRuus0TtdSrR2zPr78k9SKlIsWnpO72l5m4RRarisVisVisuqXPjpK1hTu+190FkwsuRNqJobUW+NUnsN+8APsX3t+meRHFUMSO3V3BU5ogeAe1W73Ri0doDh4kfadPtQpOQeeP2qg2QWrseievrQXne6Lcy3X0c/oBUWZd5wFi2x6eMoT04f/dFD5ZPyjD6x5sq0uu5p3Gl8jJuzBjjJvHOzKpbq4cdRYKS/+dA7/LnYKmCWeTzKjovn3L0A887u6wOC8eQhTpeZxuqfVxCvI7e3yJ3nlKC5TW+1n9Hbq+MXBTvPXtYg3rgnd/2+wJn9LCN8guzZTrY8lFRpqo3kKrHpgs7lm0K7IezYkpTiu3da66E9DBNal0BNdLVJrunz3xagsnuUH8gnNMWhOW4x94U0Zx/OoiOKPmis0rr2HSfh/He9LaN0zv99CN4NRVW9ym2l8cZAPD10HoTYULZknzHoCY6OjZi3VyF9HH6fflzpNp4gVeA+yB6P24R6wjfr5A+koP4XFrvv9eel8iUZcbl+wwhfk1pPIlrw4KfxQYOIkGHPA6/wM/FhdR5nRObWC47+x36N8lztBN8YSyWyE7sG2OGwavMxLUipUc9M2VVUknp8BrajScbTa9IZ3Qv87fpc9JnEZuboQahtvUfE7iavk24Jwo+9hcK9w8DuBZBUPXQC5f9jhjq+Rd9CqDqDOtDUAWTMMAukv3+yi/j4gNg3zMqgjJ4qKu19/lpo4QRiNFz7YZQrzVZ0OXAOgBvfgKZbxD6zLLHC8iWiAXr+sYVWrDTd/x8G5RhlPHZdpCZJs/A+u0sK7ZBc5lQrs4ySt9iuzncSJRsixDaBQ30C6eaDrV9oiO1ngXxTR5JvlXzrJg/vpPcp6lj+bJNmmBupc42Ov8MndiIZcAy01IiKlDPZrJD7LECDBiyD+5yh0x6dbf/yyj2jXgoMmr0xSDd/YjwqQvjjmYDo50EEer3Qsn3uj9kD5XtNZRm27TQKx//PjKTuBXadbgd45YETh7sOfR+jtLfEdz3TqGcdPDHYV3XfGQHC7aY6mTScG2LzIzeFfufExMBS968QWvgXFfoL07HJa3OEtjCsfJJsEb1GBWg3S+80ebGiJG/u1tspZp4/Py9bwsilSW1kJXL9DWX3H+FOO8cAdYK6wYkcVHdh/BJWTlgSaHCgYKr51cVQIA7/vjTbwJO0B9lrpgl5WBxrjfEzhRbE2I1tURN0Z7jfSAdz4ugHjY9bWSJ8iNJ+4yKVCWO8tiraIFqCiwANVqfNIxyaHoAaNq7TptbMRCQHlxVhQZ/lYW6CLdfcMJO9YFFVaCf1N5KK8rD7lQ8RXh7VO45SuexbyD/4o3AaG/D8ubdlfyrXNawH4Vjq8LFTgIRydhQFN8qQGwqp8OrponfnULpxyehs8RnfD7zeE5Y3tFHZqJfweJ7J5CeqXO7oWlDXa/uvvpl/LyHhRi/ANlr3Tpmzshxd4uLsjmDPM5wf1YAA08xN5OjxbYYPlDlOHHR9kVEh9NbPFoj0VU3jzeZHiLE+uQzA6AC73nUY5qm9PZRdjC+oiivMwQnQ0l88xfPLZDnO34gDLftlM7DP2HOJTFXcR5mWxiVGX8BxzirsqlAn+LsmuivHw4aAWAlA0+4d0/pZXPl5gybjdQOLnF7LpX6tDSNJNvM3bHXkPTR0o7SKyxw08hSmw0dsomtWrDQPc7vM85FcQ1Ic4xeqgtVjuA5t3+b4ay13maVwBLDIyt7+Nzsuvt04R/gWPlkBtA8fVioJkkFiTD1raboHZwqHFlcAeo/9DurRiLW0SnPB2E24buO9DvPC3I9HqHGm22oD1jD+2+eO2iEe0up3A0emVhbLDX68RP/OpblGjj3WFR4H4uxGFDUMcN0HR0OUPRIOJ7/JdMco1DZvOH9B7FaF30AVukfm9A3IgXtL4SE7JD0l2UbEduCi3kw2Ogt19P+ALDaf08VwycXs2XW3jJzrTHqIyLL+Qw+dEuIYT+u6PmsHr8tltN6jlOoq6cAWHpN/aD3aZGZpePvJDoLLdrJhtnhHCRZAHgHqW0wk8yZpX+3SbW/BN6dtEv1/lDFiU57geYamzT7SpdcUCcLm/U694ZJZsnxh/RBkd1TXJzS0bsHSAXFz/AKagdWsp9XNt4eDt6SqocfOypf13n+Ig/3DFhCfkg+AdQpyBMQXw63o/P3KS8ORD8OhDeDv0juB3eof/tTHxsZ2In54vxDWt9W7Q7kYonDdkWkTttT1eTps/3uzJG8uAsu5Q9exT89rNOmAgnSjP1EDbv2nwAu0N5QRQu04p8kUWnqIcO6K30abXVVHGAo1cqDtXjNN/AGw5LdjhH6PX248Thy5cii/gzZAgvIB9/dwr6btTk/4e5xX+Rxmn2Hr2AZXtdgdf7e1Xw2XMsnC/b4Tqv/dbaFqeph6MKAKPRX6cgpDM7OepoO1sket2dUPH0nngdMtRTHsc1ReYwzYvQAInDcEzEvH2U5HTSom9QH57K3beES8dvA6hO4zb/qe/ITmX2+roVkEpeTkF9d8TbKs+qr5PmbPnu+Fi/AcOMGzdY1zROunJRBTnC17J3SlGJM1sKktgrnScobU+3eqn5FVGQZZdta2F+NlPZPbUbmJnll2e3xYW50PtNjrkJZbkIHvGaodvuIW8SHVyiYcZXTi6LFHFKaG72Cb26qmxC+eMw/SjPJhJCfvoK5xDRLxqiYOcprA2Yfl9iGaDCiG1RhbE3BOZvfn0dKKsHyYKn0vEixFH6T0HuT0O0dBWx6/bg0xq6itaouE+zghvEhnYGp4nOeY/dCpUweqpdgf5g4FkzNdMeou2prj/DOC9Tzn6JMhwlYM6mU+B4gOzscGr0aTK+O4aZhUzSZZ/2Sge6ddYr87cKwTJwHFKij7M8x9uErMAmwDBA+sy3fD091wtLvOt2ZzTvob+7hPt/Oz/VrL0lN2Z23E1Z3DwykIzIEAYNLw+nsMGGiGNR3BD7CTQ56CCWAEbhKQiqq2ps/t8ybfUxYoF7ne5c8SLg9KavP5Y11r6cUjQ7BvmJqzvgXASjKk7jdDbvDjVtBnJtTnm8sZhAiMbzSprqgJNa/EGyqfjzpnmveziN08rXjrcQp9UQ1uXLPs2KeonahGZbL8o6GvwevRmR1s6OWbdQx5k/hVmXy7z7XPGwHK+HJ9o5+vLu6RGpzRMC3+RW3q/N/5Bp5Guy+U+Nz/x4EwxkGUAYbgdl96iA+WsI5gNOvnuqY23us3E9xNZ3ySejN/v0WytNkJGyjz5PuVNb5Zq275uyPTFlO/FSsakRIVPwPo9zh6H9u4q1x6/pG1r/KHE7UJngf5cIM5CPeCw62/hpTt4W4v2cOb2AAsA4+TQaeD6T51G/Iapysft6igYmrLMCdvlCcMt9uYTect4dZc47QGyYqjHHoOlX/4P+7sSPDszQwnFEp1/GyMJKoiv1C8qg5eVaFOeJphOK063FstXeBNtyfqewC84c1KBOoKFn6MuTneg8URN6+wh1caElbKnKiyhelzHjRJVuJYAFeZVm5e6WVXA9/sXzkO7x2lzm6m5ujzJrubWXeA+X1n4u6K5vzzabDOX+KzIo39V7tex9pkIzrWfp7baErV0mnKeweBm15sun7tbVuzzwZUyn28j3IaqxquvqQ5QXVOvTiFE3g6CqmmBi1vbKblrExH2qtqxckwMWHiaMng1qMwHuo5q/jlcQNRSJ1FlCq7KKWZwTRwXoV3Mr7ER0voY6Gj/6m20MJ7Mn/JGl5d5yY6j7wr5pXw9W3eskeg6J+lSbDU4qDlTCFioJMsqPXtt6iqb2IuY2PseDS4HVseEU9OJBaCELqT5cQYVuGOU50X1yQFrXbIIT6idiRp9KftAPUbItre47up5uR0OoGoOdi4flpvQDilZT9QPJSw7IZVZDrkpGpDsaO+IUpH2232fpD7SpYV0oXr7qbSRp4Rukx1oQXpX7iRZg0dbl9Ao+S6L4rCyeqIqsUsbcoTVh2Cno99ldf5c7Lg+vczQFncprLnDGI9jasPMM/YYK3kP6HflZPgzc2XqN8twfUPLnD0wm2KlQhvaKrC3n4196jFA4DYjd3FN46jiy+e7FZ9MvdamU+nLPIktyr7/bvUJdCcQamwEAl1gB1pOYjkXZx8SSLB+w6kosHkFVUQaeq/w00nlRZhlK1q8XdiWUIcAVwLuo0I3B+Kf1qnyIXuptDbh8M45fluQ0sldm5JBfkLUQXyfrmKAaLCRFGOzyZZrBHuhgbWWriSgUBp2G0lo3TENhPdiklJGBDBar+WCCGiOKH9O9IYqvEArNU/nIoRksHzk0t2v4fqp0MTkJLoguVWd0PRJkBs6jOEoCBgZWzKIK6o5Qs+HSGgQcKtgQ5OhBiECbwJUA79BzlG1uU7Jj519RhshacXMlXjyh9fe07N44+fbwDAbwlkmssWokPmtTp+PjI46xcwXk9hiz90Ij86nScxmt2MiW5QWZf9Hwspp2AoX29jMRFCVvlvaAPsca1/l3tJGxzhnTix8/fvMG7PJlj7MqUjpNuuSYHuH+N4r80M7L5TtMlFvxDjE5tBdJDszZmm1w79N442muhsA9CgIH/DxKvns7G/bgeltiPNzrC98g68KyvsFef4s8bUi1hmZGQX0f5GdNlMR6zfBfbKWxJ2jRWiyOEQS8N3GTDXCHci53oaM22tNH9v6ZMwTsEWtykG5tIv+o35a0b458+4dvI+zrZrc5+ooPeNx4WoOkhy+yCPdlwT0R8uy8e4di1M80cBrhOfxx4Q7tIpzIqxFo5dWO6KPj+rT+KS3aohtO1/3rNdoXD0+YYBqRn9kV4fso2dz8MDFy9fMN1Ofnzzk5NLzHRA5Oo6Jf63w0T83d9nS6DmFdzUevu7k9ebFfotpy9Qd+ZEeMU5Orhm7zme16Ok3u5xxtvuLiyVK+et2dUfFZAmleST4F6W3ML04ErMrryuC43Ng28+B+WxfYLXmdN6iyigeRY16nBhg5Su49HnDuCLF7mhbEm2XZQvQX1nSPEnoO8IVhBc4feh9RnnMFuJxzhjczwmxJR1f2BJqxXdinoBpbYr1GGc1oBd7Om3fER7IOWoWEJewIfRncDhT87ncKaiahJPRddXMMqzbz0CwTRwvNu2D3j/7vHZu8dc6PCK7zBpQXq+kDEfGky/YnNRi1tmai9b+GLaJWJpsYvYuKyFfQMtXQFywJUKgVIa9vZnqtwYLweHgB/FyVQzk9jXogM1oVFYtHZFhX5bLgzLS5o1q6Ocz7l0/I4zV7lHvFcadgaQahF+NlZZDmwYOefJ45/d7V8O6elaPncWXiK9Ivjf6E99UN9/Evyxn3gIcsWn/Hydbj/S2LVwxrg7FLWOTrlrgxZzyBm2L/albHqXh0WnqtXiJVPT1cAQ4d7ME9Kdoi8TWKY1SckDEDvfrTEYiq26pdpoedkjDM3qHloAhrPFXgQwfzC6Pc0meGRDpmugN1El6bPFJ3KMo7b5WFP0Bi4DoXZD/b7HAiCXE0rE5mcCQsyiyhxWDRaeRj9vBq2tOuOLsS9XHBUS2mqzSrBMmPY6UWR8QcrRqOZRuguoF7Rs+Ne9l03K6Ci+jxkTqr/IDzqFwg3nJaxMfj8Op91kNamXcSv9r0DoQJkgbeP6Us0vYiyk7nnOOuk2+jrD4YOrn6q2sOu5Aivq+LhclLgHso0ey7zMy3+OEeMpKlizJary1kPQ4fe/TB6cDBI9RTUIJQDJkqrZqeoxO9FOSn3d7P0xoaAvbvEmeO+RCpH5O2r1e4D5jX+UP0cvmCOEptwBAgF2Q6t2nWr+Blq3Zoibssjd0Vua9Sxdc5C18wuMKGYhcs8ynaq4CTqS48oNxi5x6CCJtg3kY1vdXJ9bvSDVw+6zKjL8Hrh2En5Dzvk26+tIYQvPuxFxk4IlJ9CrJy8bqOUaXgnKaHgrlFGU7lIX56pgm9YGbQnCJJdJNRT2ZLQM99TdFIcIGj2PJSSOy9+HeLjOPkpw+0wunxL0OOXMCG1pjeAYDDvvnUTYGxRHmlv56CzFYJEerE7mPXhP6tcZtCK5cJbWyghbXn/0T0FCHzA/qBYotSHOl2xbr+75+u88/svdb/+emKjmmVwzjNCp0L6v41jxZ0WljD65PCvU5Z198syroGVNN62Vy26HM28DeLa/8fNhdtjyjLUBYCtlc3MRHq7TCyUn89sO7ggnB0O7wvij2cGKenmE3Z9zmH3wGbVSjW31abBIinoFytM7L7SQzv4Yhiek4yionlkz0dvzAE8UZOVmjO9Gi7TQj3LggaW7e6ReFd9mfr+HQq81TUu3l0KhhBxLmxYu7S2Ob+QOjtpDqvie6NQ+g9YgG9SdtCpO0+LrfegXq5p7TINa2fdonOL16fVJCCD0EkJNRFcT1LDAFsdeDoT+SKAPJw0hASiznWxjQNkNMW4ocntENfogxTUKcgwYxgE9HTKm5uqFV1YILi0/eLGEMNJ0n1G5Ljl6AgW+iYQ8dqW85jY7cuAIXe/Y46sywE/CrN1ojgSP5/FsfUY+Z0wHmf5sPIe+c3OR/SbXqL1zRb9TLCt94Xu/g83XB7sFswa5oUZB00j4E/oeI5zb77nurbDO+i7JWtxKaEmk1AMATFMTiZgbx8IVQmW8TSW7viBwMzQFM/SK6G/qZ1iVj+w+aULC8PKMI2jzQKWSCw1iUfUgrAgCn6NydEJ+/IlHHpxz2B91iJ20ttdl9qeSkx9gCFKU0kf8lKdvnfH1kxsPZBZO7B3mAQP5W7d9WqcQp26rBj0VO+sOsgvkNJusNJRIQ5WFxpb8i7slvzvs33jxG7SziF/WTpLrVw57Q1UZlEhh6i/PsJhf7yZFukzxB6O3rE78qEL+wxUuCkxeFjtH7CCaJ/r1ogTpkQOIhWjjM1Rv/hwYfGolXdUkcwAKsrnDBbwWnqGiAB0PnVPMbkvlyvEdp4esN+mWXpIF7DNrfnll663yJyvpemXDWB5CfhhpWGPAXVqHG0Ml+5ZzGO1OamVYhSmly+7KlMABf1zsZsFZ9qoQiZ8Ija7p+egjA1Rv+EXgqifFd1d6ctgcjW/iYRlIGNmVyjZKsmZRRZROJd5+/xhqwMJ6O/TIgyq3e7AHfZQj3tU9A4AsHy8nF+AlYVoT3uiU+8HzDDx+n8WaISbViNkbOiIEJ9Ki+6OMLNTwBCZycrkpJFrBnqveNl0i7rybDusPMueIVtYoaqXi6amk8uT2j5hpMoe7WyQEd1ic0LiY9k84INBFfAaIOjWijM+S72DpCSmBP8U1AStxlOM8eEK1dZuvNuUD+k3kHeoX38agZXy0ofFKl2hXi+XvsGeV9++x+0VqdCs9Ge9G7Wz82szxuFe7IaHjLs+HSYAPHja2C2x9qyvKnY220rRsnmY5SUURy/BrC0eExPQXeCdevE/fEf5lep9RXd+KYuwtYqkMzVn/GG722ayZxeerd/OZkFQ2I1rcU8H32RagO4Ovbc57HLwYeQ/Q49RmVcEM3HJJC7/POZWSja7SO8TU5hQUJrxjICFd4u7YBp7ZHTX7uHP3rXjuwHtCMa7TQixoOcxQ7HFv6YbhBL/ubdF/4hyosKeobGF7mmlV1ZLNWjyRGULV5lL97m9vHmRGHcTqWPfBm6wK1O6zD8FXATmTG7g/WbR1h/N4Slrbs/oef8A6LqkIgx51E+ARUOUu61yrDk5KB1VfScxwPkqkNiUzhCvBFzNHbdNYTfk71dPRgjtv1mcbT+mma0wlbQF3hXaVbuTm01BluDs6e2YtPpmiRvj9fLz6d4m7H6wW1cywlIrXuKf+pVt5dRsbdb9cDUHg2+r5vrUsOW/k+LNxE0Y7ifszlNFh45JvWuM6+cv56VxZNbSAoH7A6t8R5zsWlLLdj7EUV5maGmrOTxq4lRb4FNwryw6fju6FSEfuoTKhd0LWDviMwm+YkYUG8yNq2MXe/2aUarUDzi00gxEETArtJ4Y55gTjOsPWaRLT4ew/iA4xxDV0ncHaI+9g0fVmTlOvuO905Hkug7culfPQa8Sdwcd2RJXGEUb+hf/l8BNhJ0kSaPeFtmERRsaeWRvXwpsoifQsfX7nG5S9pXbh4g3qG8jIvr5HFwc2BX9K96ekCwc3qn1PYP8er8/jVZn/pDQZ2p6Ni0On+toHTTwac2fSCCYXMCTMtsjWyTHonoVbDU6InpZS35CG9gruz8dYivLqlAV+cNlSXVeCnC0PqbPa1AV3fDlw3xPsrVgcj/aZmI4NrpAVkF477IfKn7CuB5mspCXbSUPJmjwI6DyxdqAb1D+zg9mYIt9QnDqtDkozJ6fT6r30fGAr8WSidT7qa+VqSbTbohnUA3C7gaSbCMYdJ85A9ZlOQ7zB41unMVgujkdydro3JvjD43seDpffmtOtr6BmwQymIzbwy8lhPeFnUfISR0JeAf6COX4MgyNtMoutNwn3rzDU3iG7JJwEAXP/1XTbn31a/99tN4D8jSH5hwJeTr0uu81ouN/DpEjblv9NVa8uX1ms8RAQohET5fNjz5kyptXx6qRtAq35c3V5Vns+3m8TFHTs8SWDSiC4DzqFg/3eO/nIyRW7Kiq0SzS4jQvEh3e3anbGZsGJ862VXzv/D+jIBzDT6LUZR0ybc97uXn0fr7dUJmZ/395GKy3KNbbqta6Fan16rjgdc1nCKcgkj+Y8SS+WZvbzBc09tj9Gx6IFzCdfQF4ek2zV7fBOBEBaBWmG/zf6LzX5+QajE4helvTYRfvVgov7lACa3dszTP71Ecv03vBNNrEGeYlHW5ruPgucqtpJW1JylXHYxV40miv3tNoMoDFs58vpC0STLYbb1+z8lf8WaLin+lyShk89SQuBhJtW/jowQz2OvOTNvbpqKpp7T3qiequnT0YNi/hXhIz9axnZBzAPwFSlHQNCv+USk+RpRFljLay61US5QRlCk/LYcnPVc8EKtyMZWv1/QWRztTgBdfbxBdxQrLG6TSNNaFKKvvq+nfAxeqnWveRb9W8hJCx5ogwAOxwOEcbXEiVFu0ugx6Sp8vX/YRQdPphupTepXGcfrsBAM9f8XJxg3I9Xo8e7TxAileR1adeU15WuxrWOjLFdOLPL+Io3w0DFwNV9/f0q7saiPkqwvbbIp9eKvhLgn248cF+ozvrODAhs76IQgvbvuWJjvffd09TI2yaoj6csxmwhsQuhNdD2U1x8JYdsx0nVMefTMMup4hZpJsRezKtHGg20xlA0N7zdZjWc2lMJgZJ7mubuuTx99QnLquIWeTvxObYkb58Q51Vgc0GBp3YveQs9t4QSeY2HqoQ51THn2rC/hAM1lDby6toq2VySSdG1VzMpidnTQtK6UINPhb4VB1DmkRudrBpmaRs/27ANto+fbuLTnhfklp5dRZ3X4azHBdZITQsyR/tokZ4vuGmASa3ewi3bGXT8fK/5o+0Nln+Ti6AqiTAEirCgeZBDt3bdczhHCcx+n2VITD11xSnt2muUV4YtczxFyylHEUPFG5VpvoQcxmS6VF2rOuqxMKjL/+Y4ZrY+sO/cDo+T2K949lnFi6iQ5iKgWCrY3TprsTKl+jvOZ46Ik99tmcK2jb395fzZO37YLmq+pe61g9Yq0k879RzgrreQD1KTWEJJP1szxP15jNbBMFkdLUW1WagTuUs4wIqybzbU/4L5PNT/T4QRO41w1qjO5R/Pi37sePZVzgfYzXBIX/+vnXn/tL5iap8hn+dMbeMlFfY76ONkN2EDI2UhwAzEV8wAYibv9rMCRZxiirSnhcpEleZBFh93DN42SN91Hc50evoaZ6oJS2IPtf3qE9SmjIg4punXH5fMbD8dthejMwxo/ff+GESkPW8F8sfpXhdkCCxqM9lDLx63GImEDTQcjX4IaS95nkq3tgqXDT3O8tzPLw4ySip7x/VuEnNgwikAOWTCCY+vfxkvH1LuAXIKwPUbZF/UN+JxhSQVBN/AkKqbGAzC2gI57uqYQzjePD2JwppqKQsR8OfgtmZBzGrts6xFdDpE1nKqCUVDgOMGh+DrNH6s6iB2mpCdHaBUnz2eTlvogKxKKRE3LUvKAX4IOAHG6jq78Le1zz2ySyI+Ar4NH7EmYDg/gTRohEcrQ2qwq52USpCRrzKET/8be//TqYuQ5SEwrIQ2p/O3QBAOMcFz71CqF1X8OLEwbzJTqhSAjIzSYYbbhGm9Jm7ODf9ADPURNtMv1QYwiVwHqmIXgCqVIGVkuGVIcMzSRZI6d0AwVxtHJlMscziJU84H5qqTrHMc05Bz82sJKpke3LSO8drjRojSUyf35pYGnYk2IFE7HQzatGGkSl/XY0m1dDkcnmNZtcNaFMh+HUa7AVcOh+PHjnXkvKYTj4MHvvsfqINjiib/JV4iM04qdP/GC0UdUIiK675rcgwiAnNYxANNToDMXjNp9M1E87atEA8bebwVAeYAFhEZHepzCeYIMZdhUmkR6dEeu2ixEn8OnXYDKhWTwVcYI4tBxx6mIL57Gim/enHvXS2GGqeVksGL/tj4euU+CH07Lpn1mbtO8qz/aE5TQ9U40+HncGNn2Feex+nES5DF6rQ7gElq2W5AmES/06XzKm+o3v7GI2Fr9joi6OWMyMpnwOMVNkcZhIzIQn99NtaEJyBeEgJ3w49I1NnkNCMt7yNjeehIPa36QiBjc4mn3OWOaWuNeJUjey3VkpkxOQPmNJmEsCR1LfzCaF9UnzoNQe5L0YfDsaZWfiqViinmslbETFze+Tml++JvRK2YjXrSqV0KTS1fzjDv27xBmi+Q7kN/5L0l0cwiA6wvej0WE8VSZ6bG5/ehMYe/N4k+EtTiYIkDVQg4cXIGuibHqsn10UiBLAP1D2+kCLkkmXOd9IWN/Ch4VLhJzU+cWCx21umTgvk02MaLais6LI8LeyQFW50lX3Zcze4VoCE8x/nfJeTkqZGslB45BWkozHQWVUTqsODl3v5YhuLasHc2Vsu2AOz0S3k3NhOhckZl4FbHS3fJMS9aALkY/2dkiSWHxZF349pCHJOqLrvj5FWkZ73XgxcnUw29qMQjW9sjKLhlmGqhI8+IrM+cu8tAGIgCTtCK9sIMp0huU7LEr2DkalLUDYpldt5vEQy1Bv93u0xo94zT61Z9vDETYYfwgvWcsjEUAJeYcgijDqN6zm4EqHLj5jwbg86AlCqKwoClo1sGwahsmSYC9CrilUVMTqjA8DWKhy1ac2tOCcpG52FrbZFbaKgrllniumM546dClWRIcz+CSJ+3ok1gJHkoGFMGNiRUC49AKLJHMKTOYpiJjuhM8pZXANsmkF7UuU4SgpWt16ke6+4YQ1nD0iQIEbJFrK5scUR6AiVAeLJYUYqOTvYM7mixfU6fddVxmd+6iuIZ5/lhGrofE5wXIZFRrxsiB+OEr1KGfQokWPR3tp8ne4OlFHJI9W+x2Syusds/N71J5Gxr2Sg4bArE/tiZTTpcSObxZSLCf1PyqINBDRxfgd+z50XfJCSsekUqyB40TSayhJQeTYSoY7xOeWZtlmf9Bqd3l2wBwK18UgWIy27RPxJYrLVkjVFIaRi2kll5Grg2bdMKQMW8lTGFGuqDWQ5z6AucV69oP8hC+fZjqSH865+136nMRptJkvmWmDgehMb388inSmLTk6Yy0pn+nqPtrtYwTjbzuJi9MSRtMzoYYQmT+bLDxglBH6aHEqafU+11KMTCaCWDct9gIu3K9HUWexo0dnMB67BYjV8p288wjRhI5cM/mZ23V7lWbljqXv910PQr4ttWMKkLhfD76QQ0eLiQ6ZVwge0j1eTy0FbNChGNQ/H4ccVMQcjiCs2H//yNJyL5UCrslg8uqfJ9lN2IBDFAKJjowxAYVHa6AOryWoEABv8xkLKS8zKB39uZxW47DmCzA+ZGi7TF1IEZrcejGc10ntF4bWbEL0CT3nLI3GQdSfarAVcOh+PPj6Uy0pOmPNXn+qrZDIH+MPqqrrqItI5So6QIsaJMvEtp6/AGNP2MbKOdj4AE9I6KZyCzoLHu0wn/BdvhQoS6L4rCyeKMTqYdwdWqfZ5jBKgaooEPBSNzx4Dagk7yC8DDfZJkhRYpW3iY0pAKl/OfRaxBUZOgOJDJ959g9C5UwrNFMqEX2pmV1bNBmvP+fkpPAeE2yy1xWcqnuhmc15zEF8xAZHk9tcIEtn3NmTm4OyBqwVcx1xAiKmr1Pmki+G4bwbH/NULVmiWiyHGAR0UU0mOx0hhyQwyw/rmEdsJgzrMBOcucM6/sCPxUVEjva3ZbZ+inK0+YqLJwkN9tM4En/YYCEA634Mp0mmSn3f0qIlE+BUzC4igq0DE2Q7pYF0DYQ5iM8ERo+RBHiSNmO7p+m4LFn7zC+FhVpDSxK1yWwkazkTZnRes+lTWqDl29kUyyEG1a+HLUMdIcu3s+/QM5H225QAyBvldBD+SQBxAR3w+8H7LiGqDsKTCcmZ101wxBxfjrhMpoZsZUWYlvlube+f8J6Whlz0TtYgKabYbX88bAFq6Vj+NtagyjxGMN62sxZYcgYOB/FDmBTHJhPrSYi03RJth3kvaSkae7939CNb1Ns1/c89ps8mAl+jmCzwg7KHBZQFRHpfDt4GFuk5COtXlKcp7N15xWEyE8VcFuY+bhdlltDC5yjAs9NAB20O5d6ZSfhy8IpFpOcgFEsbVz29uWIkm4dntBgJXo/7cx6aU4bIRZQVXBnnkCXHR8Skj1HvrNP/eDylwQe06Yy5gFLgAxE6iF1qCWI25V5lJV2zb1cD2Vp+cM8SBGvCUB8ruZo74ufiCa2/p2U/x+bgZ7kGG7QUVNnw6zRPzkCy1KiFzKI5ws8wAimhUEvd9bvOeOxblxmheHsbvbI7jesEU7ierjZU117iwL3zW//jYTsDBvRojcnNxGLko/EUqSnyNc/BHAcgUUrUwrql7ATEr1Ca+Kr6fWeTTyoGP8hMfEi3K+7fdCLlnoZeO8Hj0P82iUByo8qwCeW3UPEsjNzxROkM10NxEaJ2ECfP+aRqyvOmqTjNftT0Lz/hksv2Rec4ROZgROXs8RHH5Be0muhBfjugCKj79dCd5x0pWlvPzLf9Z+u4lwxkmOBkoVlpWtR7W088ScoP7TwwzptPbJHjg/adz9NJjHyakhrvouz18mX9FCVbdEdWxAWx6FGyflWIV91AFK3mR30tw1AQ3ZXVL4FkAqIrjDxUdOh5JqUTsAzRYH+8ycQMMiFwfj5hWD+hTRmjhyj/3nh9+N/kMax8I2FChQ/T3JEMiZCiFNbDI2ddIKkDKNMZlu83m+z9WaISbS53EY7PiiJaPzHX9BVW2D7mFVqCCByIea++FNji4Au/wHTpDEzndWGiNlu1qGXJz9R1pOxlaBFFpTj0VxURa3U+LKGRkC1R+DDJVskhL5O3QFImZ1VwKdMajsdvCbLF6SkpKW4TexKbo6kYzKbbuJ7zFgW4L7/l6wxXtbknrknDjz3M8S9+PXgH9ZAmE/fSjEUV8Y+oQB9RTnNUrK6ydDedlIiD9+LyxE8HLx89grSMbG4yliIgD+mbeCxEPLqpmE04rnf7NCsIao/kPBjU1SSXEgEHAWLvy1H4iESaDLxDONnO6h+6fJlfVAQceqnuj09URJoOTlTIQHFaBbiCNLjPa7D6CiL6AEL8xzDnc+O59yJsHF06Y9b4zSZo59H6+3VyX6Tr70HPSUHETIK8gJK0zcHbSzLKDuLQJZO75T9gWp7QTficyUXm5n7V9BElJX2CVccm0T8VlyRJCQQC8T9PImwizgNc+E+BrlJagieQrh5JOiOyLrM5BtI4/pIWxFisZYpOTweUuZLanZT8XlBL7iFd9fuNbrJ1X7g+WvPNwKvQH1/QpoOPGleBTptlQ8EUPgYl543GnEO+6A9nSf6ssMu4Jv1ZbX6eRHM5yZivjVHCrgXJVofirEVvL9IdO2ZqKjCuy9S6ix+6X/e2/f2INJaU1UbDTSxGcOVe29LFAcsnG0qSJ71kVNh4YvlpcJvvZBinW0N1xHWZWh3xQwsnQP73I1JHUlYbDTexGNF/DyvQ92aRNehPYfXjNH4Fc0nypI5g9ixDfhrc5g0hoRh8oVUgNBWS0GlqlSQOPgg66b4ckVpSMNxowMkFqv1LKiVdC3AqJ9JOlkLlSUNJuLQUWWp7zBiiwnyrd+gHRs/vUbx/LOOEZk7U9UhJ+k/umZLhAfj7gUZHpNH0ZsRo7DnlUPgwdrtTt5LO+WwS5ffCBmLGAoVJ6LwMybJSa7PqMn1hPh6tdUCq6gDunS3F6fDumM1laNqb5UvSp3ili4r0QFkTUZFu0BXO8uJdVETfonx47KO97lHRhq7u8frnn6qfucmsf6dxaLvov37efEvJXEff4q7LQOH0AEcvF1GBtuxR8BA8/xUchG8wMhT5F73uAIZpv0BDtB9HwH9I11GM/0KbZsaBgYA20JBAs7HBo2RbssDq4ZjtJ3Co9qsOeei+yNiNUZ6W2RocDWwmJXLQcgSLW5TtcJ4TGW+u4gYYDJtAow9bjYwspncZjCp+hkYUW4zRmcYxRBv7GaSHfdGA2lyrgrCbj7IRmu+avGrNECm72hYqjrWNNIdVjKceaHSENtvTYID2CwS//ThGAH39CCrC9guIfvNxTAEWRFUSlfKDbG2QDPe+g8pQbDIyYOeTHozVfYKG6b6OSXRj1AzFufkCynLzcQT8O5zXz4MH8LtP0ADd17Epl29+6p1Pe9u7xeuizKD5br+ALGo+joAX3+EPxhA/QwOJLfTmW0FTr4Fi9ld1o9XHiCU9Hyc1SsrHiPWBdIz4GSRVaKEpe7QiDc7QDlakYCuVRAoNx1BAMf6BstcHvIN4LX4GBxVa6M0tX2tENr18G8UM881MB2/Thl/huIA3zNEuWqgNeulhqlAcgxaqRdC00l4FdceRxQC2UuHBtzTF5X6P1vgRr9nhh8vTL8NK1l6FH9xHG1O4+00daD3cipXNwZ1Z2cMKO228TDDSndOHCDqp8R8Vs8W+643zJcpwlHRVAi7S3TecRJJ50emkwEvZbwTfP8uI/fI5wdBGIH6GcBBb2HFHnyUjW2/1f/N11O8oR0gPE2O57C2tnDTQwIFvrIMN394KL22cTPCxlZq6LImu6NTNDdZR3WPMnGkzlQ1NmfYTaMa0X8e8WRhltxkGT1fcN9CT1X0eGYS7TB8Mwn2DBmGf85XW0Yq1lfn9+I+KgfR8gKyxbBAl/LqFDvw/srTcywapPypGqluMjNTFhA7G6T5Bo9Cvo9AvX4h9mETxWVk8UVdwtbNKHWHq5hAW6h4j2NXVaQZo1L9D49WfNA9Cn2mihTaBpPQkJDZTHYXEliNY/IEfiUWdbUawgJtBWMAtNbFQjKweTW8aJTLMfZNOp9Zpi7X8lILbN/dNOkj1eWSQO/RMCL5NcVLk8gkDW0EDgw3H7Oq6mj1kSbefQNu5/ao5gmTGxM+qkbTmrVc9ejBc7zs0Xq/J6BwKRYaB2RO+w/MmNBnlZ7+mH8DTfhOYr/1WY57AYZ25oUtw2Ab0DQ6bmQ4uM9lkDbXQ0DPTpFWhgNmXtITlQNLYEB0NPPQQGL9t4GvIDG8c+K/grQPfQH8oOoR6uKrFyJBVo9FLRYgyKUValHR1NIY0dN9A7LvPGlqXLu6PqHhKoc2430Cmefk2o1MUS0097hs8LbGmEfc5kw/CfYMG4T6PGTEoQeSko1J0wyagUTNoNXY+IzAQOzx+A29xe9/Bc5rYZPTKLQWvKerf4Su2VOPapUv/P1TR7Sf4rrP5qoN665aBKWg/SwnR9o9CSeKHg0KtwLGhhgYojIw9Pui4q6TaBKQapPcddIaITUb9h2DaV8CRCLaDPYpgU31E1MOPDjp+YhZSPA9PyMJn8EQstBi9y93tI7yFfG/dJ/gut/k6etnKsg0+oN0+hre4QQv4yrXXSMOt8QEVBcrE/DCgkwNqKHN5QG11nDkjaABtFK4do+H7eSwBn6bYQOWG6zUdnfwoLzP0FeHtEyTNve/wxAtN9AZ8h8myzmFOD5sohuVajYzcSwQ5GLb3HRqz12RM978ma8Vex38FNT/fYNSL108oB3ju+k1gb12/ldbIcq72vsvH1OWqNLHUYGhpSzA6SNbYIH5DpULBZmPxHNrKtLlwV2AwbKKKANIeubkclQ88aKG6Y9Ud9g7RZht5qFa/AXx4FtuMMTlL85wAj+WjDpuATB60GlWaXdItQF12H2FF2X3XGIdPqAWOxTeQjce3MYy+HYmJVTfXicpd0abad8MNfHmU5qCFKhq0boTGL2aaCwVFTM6wiepmYnW238cYbR7Suj02wGIkMgdupocN30cfIfkaHLTQQ6NuPo5BEw2oEVKiGVmy6toZy6VmCLl+KLlQsVQ/hGKQzmrIk0ETWUS42ErjHNEmhwAPD+1X2YmhbaAReiwfSvgqC0DWHar3pBw+jXTfpQeCromeVAPvP2XCDTRVyDjQ2gSjMTQ0xh4MyD1uUj9JWXUPWrg+rWNZ1aH/Fqt7zNZ/XUNQkL+cEXsqXs1UUHRewfwikq/LGv4F0Thf4NbBmAK9lOo4onz8ZM6OgV3BK858dV+zfcgZvY5yUmXvlRilY2+QlJCgzQSEqnx35J2VD1G2pUFrxqysO8oZICVYReAyWUj2TeV6FBuEWIL8+7KKZOj9mB1p1SOxVQURJo5v4ojkoIf4wK3tJ3m6Zk6i8ARq1b69GhIKN1QIuPj6q5Jt+GmX0A981sV6q19rmZNen4hURPeb+Ce3d7pj/WQP7OxJ7LFOTqjY0PssTUh6e6poD76KjVHeeFyDg9pbSbjkZC8ACMoI+bYmb+xlHpfAhnMc0xIzLWQFE3pNw7FAS4YciG7SsnSg5VQP2oZcAr031QIA2UNpczY0D3CVZsqwUQhTpf+ImPWUPhC2MFmaV6DCO1TAboHaydGG3sUy1FXvXUVjRnzkWlkykle5FmQLD2VXLWCAcLilF8TFfuDj3qp775N38hsH5Dj5cGqZIRkQ/oshv331qZj3QRv/M953mFfaTPay3ZxMlT9dYdzodJMT1U8awIiSJQQAe4JsGX4Myx65yaPTzdOcz8oe4UmyYqGA7fwvFuhep9pRVGkFfEiFcO1jtG7AniGXjpRJcIPgzDJZRWBPz/KwUKY1N3pGwtXvFFKuoE198C0kY0wEqd/Jv60yE1uAJCbgeVTdIaScANlaBCCq9CvWVmzjbbt5vMnwFicKM3bQ1L+DzkCmHEgWU8vI6RXaKeYPSHVTTZwihc30ZEvTxqz4XDdSbmh1HyVxmKGHJ1aedEcNS5JwBwY9ljTHA4NrloyfDKVdgh0RbSfAC1uMGHKErGiPxd1JQ8qJYdsQ5yJJRiieER5PRX3SxuVh0DSYNEzLgl7KKt5ElvJC3iekoQ8MDrHIs5kPkTwuLWDzYBIzD0skycTGmTPSMRib1AnCeGiaWb8s7t0VCc4k2dnAW3kLMIpb3XG+6DFEA6qY2EkBW5Ksybe0qgc3FWEltIlZtdAVMAxBVoauyVuHu7sYBFoL1xfc1yDMUASfyVuPEiWhBiBjGSxRJBnUPTVrg5jgQK2RoJHnqEmKxSAsHt/EdXoHW6KHxk4xa6UVTwUQcgZBCTYZK1SJMw+VxRZiOr1s6nDLH4uA5JuDPKUKdml1H2WBknY961GOjxKmIuWoL5YO+MGPqctYFZAJmaIBXQNy+AWuI7rjfSeU23mVAJhZdiUdVpulajiTMEUHrpD2TQUcTuVmb8ZrbvOT7+WBrnOajMKjUYdwQ4Wv0DnssJ8luTLaZRmQ7Wf8PtrtY9QBls95r6Un1Cec7Ta380p8STQkWdJScWvg/CpqkLOadZeno3YhX2F0Dhv5NzPDk9o+5FUGjgOtxmfYPGR8kCicdeV+9UMuS/I9Tm+vWTCChZTlHcVwKnJLkld81nAJvXybEZSF3OQdymDO8WHvYUe/M1uDVE5r1cYZ0Xlmk61EHqxqxXLtPKI+8bptktErn3cMG8lRtn/e0U+Zz3p2P/p7xaT75FvdIYTaUuUHEaB4focsoVTjrdvkT7fnYpGqIoJyy9PrGEKadKpEMGhG1R7MWccS9Wu8HQTbycmzfTgolKdgveDqE7aEqoSh1yLErIcjD6qWsepiYuXBt3AHxenNOfgWqs0hQFHW2vDEmprvmnypWjvP6yLY0db/kPOg38Qv4YPaJV0/T9ZEh7/iKDts5P8oG57UpmLM6pYYPk9RjjZfcfHEjTAkfKyLN3KEvv2aOKyrtN6NPSOEddvBl7MB7uCJELCndMVrFRvyxJrP/Lxr80fs5VcvzMWetlbRiELkmgRQiHydpa4fWELJnESgKpLSFFK2D2EYKapAMSA6ZZ38sGV0XYx38isd87CmKTglXxO9Fn6J7pfkYt2k5bbsyWP7fwdXTqXY0BPiYM/B/qqs/WV7EGrK2owe+foND+nMJ1QxUyo8ScsQqg6szsa6q4uuuZIvXchQM7+reTqShTJyI1sc2DLM5gaUx6t1t6rqnYOvUGN5S9v6X+Gh00H1q/iNPLNUNR89A3p5VimrX1jr+5GahB4YpFoZ8sYhFsfsrFC4CKRt/XsKpmYDXPVxBdSkBDSHbl+FvMjKaFaCM1YZUw1rGB6lWePSZr8Rq0WurhNc4ChWnB5UHXyfHOCamPXWM1Ln0p0ZjUkxHGqcL9K+gchVQoKPXXqFSi2Sm3blOVeDUp1DzqmaK7ZyuJRotaWr64PK4MhgeGaJausC24XYtaYhe4zc4GT2yfM0m00F15XCPB028m+XDqrQVj2lBWZtBDcG8qjDogu2DHldP6hVW4uwrEKsTRbulCVjxLsoe718WT9FyRbdEdZ29U0Bq2u0k4opYsHVmiFwMVXRAONrwFZWF1ji1ZEJ7A9t6sXWB0b2sLjrSqy9ChA/1kdBCFAXtqJHUe9VCgHa8XWq1ZqzCazOurrCsJZQtFbcNFsHXCur0d5Xkfo6xWV9sWUsDl+jl5xY96D8RbBrJVbDVTJJbCsnDKrQy+hRVd6VsUbGkGBSI4yhKzR8J69kLURiBtV3x2PD4dYh7E5p+eD7NsZWWVPU5r0LX+V3dZWlOxU/VM1DMAQuaFx7l5T1iZ1Z8ZAaMIJrfOhsEEoTj1ouitYhbRawBDMDoa6sbBNja8IOReuQ7ABrJ1cblbIksi07urLOqxa0jBlAW++EADCGJaw5MIqy1BbVKOCCz8p9ZbRPCC0yUvCaAdKtXu2PTYp7iLEu/q8j5mWRWPB4Rf8EM7oDzVTmfb/kc23cy4o5D3oPCzm3EBTlme3Ks/EVW1fUC3uRJnmRRZge94i10a6QpqzNQ7oaVnoF9mtfsMcXpkUNHFF2JRVwKyEeq2rrge18QToNTnLN1UQZ1b6blSVcLV9dQRHK/w655ggxvNQB1Y/vm8duslrDboztntGp2dW2UyOv/UxvBrK5gs26sy/UeAa2RjeI4eUJKHFd7Z6K2tVujO2qbqnZ1bZTI69d1WsGsoWK3Lrz3yvjLXF2uEENL1dgPfPOPSItU+7KZO6B8xjnuqYaZOi9op6JBbXJPCi5rm09Sfor8qn4H2sCi01K5vD4MV6+3uM0CR+MmC721CRdTu/hsM9K3EZZawd1YsnVnz5nJo/nd+w19H/U90/6779UQCjjySyjrP32+y/UlbeL6h/In9Wt2sd0g+Kc/fr7L3cl6b1D1V/vUI63HYjfCcwEremYHdCmzXXySIsq7VHGCOAxapo0n1tHQxFtoiI6ywpM88eTz2uylsgR6uefWIggveH6hjbXyU1Z7MuCkIx232Lh3vf3X9Tj//7LAOff64SFPkggaGJCArpJzkscb1q8r6I47219MhAXhPt/IPJ7NZdkaRZo+9pC+pQmmoBq9r1De5RsyJJ7QLt9TIDlN8l99APZ4PY5Rx/QNlq/3tLS2yziUQZkfCJEtv/+DkfbLNrlNYyuP/mTyPBm9/J//z+HrYpZbN4JAA== + + + 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 + + + H4sIAAAAAAAEAOy9224dO7Ig+D7A/IPhp+7BaXvbVQWcLuw9DUmWtoXji0qS7T71IqRzURLLuTJX5cWWqtFfNg/zSfMLQ+aVl+CdmSvlvbCBba1kMEgGI4LBYDD4//0//++v/+Nhmz37jsoKF/lvz1+9+OX5M5SnxQbnd789b+rb//bvz//H//1//h+/nm62D88+D3B/onCkZl799vy+rnd/ffmySu/RNqlebHFaFlVxW79Ii+3LZFO8fP3LL//95atXLxFB8Zzgevbs18smr/EWtT/Iz5MiT9GubpLsfbFBWdV/JyVXLdZnH5ItqnZJin57frVNyvqqLkr04k1SJ8+fHWU4Id24Qtnt82dJnhd1UpNO/vVTha7qssjvrnbkQ5JdP+4QgbtNsgr1nf/rBG47jl9e03G8nCoOqNKmqoutI8JXf+oJ81Ks7kXe5yPhCOlOCYnrRzrqlny/Pb8udjh9/kxs6a8nWUmhONKetPQlYDh/0darun/+7ZkA9G8jU/zpxWvy3y//9uykyeqmRL/lqKnLJPu3ZxfN1wyn/4Eer4tvKP8tb7KM7SnpKynjPpBPF2WxQ2X9eIlu+/6fb54/e8nXeylWHKsxdbrBnef1n14/f/aBNJ58zdDICAwh2lH9jnJUJjXaXCR1jcqc4kAtKaXWhbauHqsabenfQ5uE/4gcPX/2Pnl4h/K7+v635+TP58/O8APaDF/6fnzKMRE7UqkuG2Rq6rzqGuuntGvtuCgylOTAGPXI3tbbbKLTbH0+LjaPJ1V1kiVVNT+B8jRrNug8v8KETMkulEbn1QXp9o+i3JCCGqWEP0JRDghnp8U1rrP5WfLqvihrU1N/+SXK3BK1rmnk9V/+EoldZyfae1QnRIVRNphfJmhjb1CVlnjXrTALtLcM773DWyLmm+ui1eBVqGReonyDyqPqC97coToUW4fl70U+Px26pr6UyY5YVDVZtaS+29QnkvyDm7ewkR8T5kZlBH1Z4qJsl2H9gm6hDK+Tu/n1YfP1H2SduC6O0izC6kNNqOrelYq/vpysQL1tmDycEKPnrigffSzE5OEFg+FgJKrbMpiHf/7FboV0ZKA3uNplyeNHKoku8mPNP1eortuxOPMO0VS3+K4pW+gXPZ4DB3lz0Ot5OOhzkjUxFjDHZltamanrxbPvijTJ8L/QZmjTg3t7HB3zSggPbKxuq5sOt6kFLL8kv2uSO0cWAfDQqUOEPr+XRbNbXkGP7e+r6aXk+0PyHd+1DKSYyefPLlHWAlT3eNf59WTJupnAz8pie1lkkECPUDdXRVOmdHyFEfQ6KVur30+njN0KVCU9noMGUbdlWAhfzSQu/cz0VNeuxHO0T2r/s0FXqDghOHStR9jDnGXJ3fmWDPYMZ8hA7iiuncs6eK8U2RfgufGKZ4JrdWarurvJuERVq+MqtQIVINU6VAU46kZOjSqhB6Xrb50JqKMYaALOg4Y167pQ62qg9X62LkPre9rCnFdUui6y5g7nkhIxVb0umhRSPvGsqnClANpWRhXipRQuULnFFRXOS5S2ZyfOCuEKpQ11I74QcR0UgbqtSIearpt/mwNV29Mex7Ynb+jsLSuF96TlbVRSsYKXdZGHb4QqkwjrISUZNoAHCTGLysdh2FevXrCIDtLrLb0zSdBZidAVYdVd216Y8XydPJw+oO0u+DCOIOoNcYpHmAJ91aO0xt+Dz8SGyI2O+cNwRdWPtkpJ1AwzKyZxx2Gpx/ysi4LIv7tCotWq9v8HJaRuK9ZeYp+mSB+qMfvZcTSnAz3K/5i/JfJx0Zr0YdiOsqz48XuDqprsSz4XdTDCAJ+IdE5E5O4NYcxP9RgPR39e462x7mm+8awZ6mvy27ZRTQNu07gC2aTjSiELTqv2Se2jvPpB1JmyU135TadG+W4xRbJKF8qDdXiHLEiTdygO+lyjowiVnqYu/9Bsv6Ly4y3VYFXYAOZw6nbiEyZikOxDIujSJ0Ku1qGjMfoEqBtWGPnOKsBA3aCCDdYTLOIgbcEiiqYznh0nFeo7QKk7GLpDaJ9ROjsyOcuoxRIw1+xDbGvilCAXRBT3w2GVULc10Oj3Bo+tdn+7HntWpF3TCWSMI8hTMsvZ7K1YBP/HbeisKLdJHbpgD9iukqyevetHmy3OT4rtlglknvFGTTQf09HtLc4wEZdQasfxOL1BGYpwXWVwXB2ladEAkeVz+K7i8NG7pKrPd0ebDdmi6W5Z2AaMGDReiaim/JiD+0nnYJOqflfc4dx3g0rqt1xEVLUShevMJVkz3FGcNxrEdHkkzoSd4bKqTU7U1zGuFNHZWKShMzKjpkDzKDNEtPMuyWMcktmZEd3WbXaWOMZlfU8FxChvSmO5VzeKc7thPDcM2GQiy6WSZQyAuO7pjnGWEfKNelHXTREW6CsPou6wAOfaa3EXpOt2D3MzWftyv0UYaQeqBIS2n2GnuSNq5WEJDKEmtvnUVtfj0wdq7CfZUVPfU3M/bYF0HgBdDXAarCpIc2JXy3WCiIncbC+KqobHNhaDA5FLpV4DIF5d7O7jq/vYlqs7yRfDvRRgXLvZ+sPgHrZFYOf4EqlfQrFrly4R2X8TFvlne3wBdo0DAbsIQ0hdVYC5d/lHUm4uCpzX1VtMkNBoFLDfEpyi92o4YAwaYNeRDGEAVmuNBAyoPwFGrQBFQFcVeHVftPVPkrI+JxsWuO8iFEh+JZBEezWkK+G/JBnZ/OmYh4MA+w1DSJ1WgAU5aUcW8LhVuN0W+YsewcFDp27Lajv3lBJR2OznIqUdMO/n4jRk8knGacW8mYvVTpPXpXz1z3jCXyPy7TvOU9mzZmiRSRsw27B6XfNqqYZez97Q3/GOmqtJZrhqFCno5b7IkXGDH0lHJA8LtRTmDlRvJjsZApf0wdYZYaa1XCiS7Cax3NlcYoVU2zkBUu4iB6DsKA8VdiY4kMvZ2HiDS5RSs+dFj+Ngb6jbMiyYM93abEPXqt73FCUOrooW2/yjeIcoEc+rJe50Xt+XCNk2+KcIDRJFi0qcCo15hvkNSXU+J8EnUGu42Tnv3dVeSXZHv/FXOrtVQLFj5vQ2sFKBAPJOGYQK2nMKC1PAYsBhOiwJ6rZUG4K40YRzrSxfv5boOzbtoOMcTK1BZ3nZpJ7yLpp9eq0QFAo2HihFiAjrcR1kXiPzPalChd4msMP2XrkpAOZotyOsFy58UUNEPu02s2wwR09w7ANWla9eeRLrJdbHWXE3xo45izStXb1gcKwjqrfvzDV6mD8gjg6een/ixQ8PGEGWYmh9MwFO7ASVS6wEAgWzUdeVAB6iCA5rgbqtWFcDY50vODYbKVO0jxOiZ/bwnWK/svX4TtgIT09jvKjjI71O7uZPk72fq4SWmcBtcwCaG1tbJvA4I4vsQZnJe8+sU7B7xGs1FJ0j2iUzyk3UESt0G1UqBJdrHiIsOVBZbJq0viS7cfTDZx+X1Anp0QsOzzoMv75La1kg9a10hFvESL1MasYD70eUtyjb3TbZf6LqmnBKFgXZh8IHl/rmXTf98LU7lltvRkjmwh0EIF+1A6Gc74myWHpa5GRzTmS1xKocRoo6N4I8K0akrCHfJbWrFnatdKB/qPY57BeM2pA2F36hrSRttkmf21zAMbTsZ1xhAn2eb/B3vGmSLHsMtUNMlznmcW23T/osaSfSeytLtrfoDcOBa9F2l0W4HBg3tcuAJ95Z5mFDE+cSbLvNH65xtCtUtN1+Zz1dNdtoW/1IGAd0rRElDDq4j/GQxnziJ/LG9epbM7/QJXlzm6TUBCnJOlobI+riNPt7jXUCHqeR8+p3fFufJGXwYc+AJ4a1Qq+U4BJ9rO8JxbvlJMJjZy3OyfgxXJGOotQaahvTe1vENjrabIQ+BI/pvHpT/MizIgk/J+/xhM7cpzzrBHxAGDzG90MI68dbCadnMqMezenDDnfPMb1JHkWcdijaO+ctihhs/zaprhJiNaFYs8pjcwwmJ72hyUiO7kqEWMPRtzMcskW8JufVJU2CXUYIXhwRnTymGeo6FarjWIwXqMRFsPSNONu1v0UcKCvnbfjnaU6rREitETNXLtGnmEpekg0YT+6pJ2T0X6MUb6lv6qIkf/Xvfv/782dXNGs7WT89uh8tdcp5dVoFk5N5iTCUcYiJQ88l8+9ENAk6Yvbfh3MjsfDSb39rkt7XEaSyu+1ai/Hoe4JJXZwxWAPDw8Ceei9YOI848nfFj27UfXaT4ODBosa3j61D4Kwohz4eI7L7CkN8nKTf2hdQ6dP2wRmB6G6QYjzvaEl2IOOmN9iiaDf9ZJbwttnGmaQOY/IQD+OA5apGuxiYHunxS1lkFNO47lKv9NAQV+5qt+ANEvBEuXqANj1WjBYw1okuoB08bh6Pm7qenCsBuoVCf8HVfYarOg7SXvlliAgvWdc4/5X34S/ZnbTocBrsX+OQRF+BP2abeRvoN2Yn7TH0TG1c7QieJHMciD3OMa6DHt57RGiwuPo4D09MgzvvNK9RWUXhr15tc5jRzEzRK/ZF2ySbr2uMOpkMXvCIDYGq+qgmqvNrU6OTYvsV5/2xZkQmJH0mOq/NmkdDiDMcvmP4gvDd/XyiyO/joqP/gjczYn87L23GlSZUn4yIwpRJvBObeJdL4macHM/UA50+Nhkn43iPVxTgL08N/o7KR1rZ0e81WLKEEvIRuc2CV10nJb69NR4T/ClOZsL2btDH248lvsO5Y4dprFa/0Efx8Iz43qOkakpEaaihQJTcjGObR1s28jZ0NRvR0j941HakbfJNhtrDU4OzM44sdu1doJJmUorlY+OQUmrExsmmgAo/KcD5BW7P6dxd3Rdle1La13fuijo/ZmcIjYYRGBo2lN700NfFdEI0hYOpoaQQMA2oczpHXsHoQttuJFgplk0EUcXnSXCuEXrsAqDtMw8od5gtV/aWA/IMJuzkQp18TQK76SVJGQqpAlVFDCrhXXlm2EZ33mlDQOTgw9bEdIogqgFIcJ4dZ0/8tX1nATX9h8BUYwBhPcfRazPtEHoYTe8FCFXHRTDPPo8RK/FigLUBsqH9bd0lt31q1nGPre0/XEUzHn0F1fgMtTzH291N0ihUDk7Wp0yxUp2yMJ7a9DOxwYkdDzk9tN3X1NNMkEUt1SzZVPWcKhG108AdRus6RO9xsVszLQ/ygDITsuVKLuSAnDOECYEcms6KoHJ3eQhlhwUw1y6zDkegu2MxxBlSocQLMkTQBYTBwvXKXNPb4uNfh0sI6rYMjiTbnBPuh2ftzMQIq/pU0d1mSoYbIYB86JiMMboHdmjK1a/hHKaxj3vAfShkRfZ5uyJnM6Z5H2NKmGZLTzTMTBvM3EpZKI9OmCjxXIO2TN6H68KwFZP9BWwFnQtigpOWJS2w6+I04jDvzeR2hTq68XCgFkPi4b1HNfl75nAPmYcheZKs/Sw9ql6e1Ve9xzbhGsA4IED1UEDosIxZvTch4JLjgOJgYKjbinRS5ahwbZ6OirNWLHhd67ioCZ8u2eLwQs07nH/TWYlRXsc6TjZ3psOUSISkLV3Vj9PFN984Rpws8BRfr2ViXbk8XGmM9CQpvXMdK0Tb72SL3nW9wv8S2dgi+HLM9nhdkPUbpbWIarTT7XrwkQ/enS19YetjuEzyO23MaJwZjnw3OX781Ipvbq4tCidefNGao2JuE2Jlfsbox/tZnrqIfQzusQs1H4UDW1Zrx/oQHgAlCx0PHwcgJkmoUCYnBxUBwjJEDT1w3ri8RxucvOjrH3YtGvXVkegY50k5XRnqf9kIkinOeYu4uxrQEjbHvoiLWbULnuHiUO3eOELFGc5Qrt9//SnSDfcP6Efo4nBeXZdJXuEY12BjKvRWXCknQ1lFbZUaiwQ+qOp1Eg/IHFMB5fIhFQTkebjrFNcga2MYwhjXIKhuL83Mk9BTPbNIDjpa3RYRq8RHNWuF0DVFmv9aLGRKOyzKtgnT5tpqL7ej8DJAvRWfIp5ApR8dlXXc4DNTXzUJHe2VNHOmEyC3LJqD1Krb2s8BwKI+R4YTDk7adTlpD27V1blVD57GJ+9pjOHLju1M9I4CMbsU4aiRGEYdfx1ANumgcslIAoGCTCQg2iJKJCaD72AyaVVIS64IWxSR7JdNhqyuEEd6hWSHZo9w7J/DjfT84phtLA66S1QR2qbtejfmvyVS62iRTGjGTfD8T8gwjSaPlHm6xGFLNjzMhtkOit1yb544ztPpA1FUrGNqgcO3KVZ+vrg85RqpjeLzW3u4G5sBD9eyiA6rjbotw0JgeYvfOZw7K8q36OFzkjXLt97b6O8KuubMncEg3obgvOqP+LW7SVcX9nQvONyLPeE6SJy6rUiObO4WdyiyaJk805hpOtT3cfb8BokxcmzhJ0dwRliQvQoaGKyGN+j6vtl+zRMcHFrWvySzHkfPz+ihUbtTBqboeMQy+4RQ64ZdJDSZKNTVzFkpNHVdXS5C6ox5020ozqyM6TkiHrQxbVkNwL7rUU7cTFMd0+4QUB/MEHVbE9GCEzoPRI+GqN0XhGI7p3mfunOYmA5qVhfuR5ca5dZCD4dIcoS7cwKmg5yq24pk4ce6qnJenRGrp5keutmjPabOazZyqEVqqglYnZtq/EMhezLgDOu8TzItU39jrvCRQmsAbAf9MLt+YMn9h9ARPLdapoHjK+lzwXE/FHKorjCD/ghJaGfT/5i6BM5vFq5VYLwH/TK7foEJ353sRXlRprN1oRy2Nvc8+1GCVz33EAlsFubYeQYVAm6ZndB2YDou8BitHp0tCXRYHOmiRRWkF/VD9VaLOrQHreiqzoIf8IoTlRM5kUWUXPbx3O5d7n3u6Dg8qmi5TLAL6Cy3nLFW6i9MwYNk0rerwDPRx6O6pMx9cMygxaPr74PmPujYwDhIk1Ec10R3TfCVpKi+KsqawdXqFL7AB+twhectnuIYJtR8qatbJN+gh0650A/ux/ReZjW8Ru1B+YqrUogCD3EtXCd34X4EguSgZL2VbKxrfyarLVoye4VFBSa8D+FNTcL4cJ7VID/wsrqtq2/N7CFjv9dYFyYW6aIn46y9ILNtfMQs0i3MmO9Qur7qqMfm+IijIWrO8c1GQ3jGOl8+O6oqfJcTKRqu1i7xePNe3vs7r9pn2cMDF+P4zyefw//cZhH2LwadF++R+9by/9jUH29bpO3mJKLpa/sWmO4pFsMzYbZVVb5i6/oznPnN9+SNx2B9Tw1sn4bRtW14Nca2qs+wjW/NWFuJ/CACbkexiA62n7qtvdyOov+/yJoy0en5ma5GWd1aiHkx6ue5hRU1s5ioT+Lv/g5ir24rks3Wo4l2PEjT45NP2938SfLPq/5Ob/B9G2ZJzOuyyCi2VWZfczemfJ7Rs7QfvG0lked8xgO6fG3ruI5Qf7JmMVQRcXsy7PQcYlvjRql5jTTQ1rd9OFGPJIZzMeJiclhFLFaRZR4I2c955pLXW+OeHcYzBVd/XijgO33YFWX9PmlzqsyQTcV6TeoPJq+Q/oAGgLdZoWyqqZSyVd2ounhqKKJWnpAe9LO35ox1+hG4B4hi6c0pSgp7z0kMAzWJw9ClPrACaCKAprKlRtFhmEGvRNcpB31iL+rT1IZfcFiBJfnnOKd5kRKwtymkZidJdwK7+QcRoi2aMYvhl/bseIGGwi7Fz2ZKt5vfGJ4h+lYlkyYxDJnnAaiHHbyGBcxuHbdZAmM6vvs4/9je7xbtYTHTaT6Qbj/FShbHLXFYyIB2DgvZH2ghk13la/G5W55B2DnuvZazN8WPPCuSjfdDYAOCwyKlkdueRr83eGy1+9s1+16FBlyfyuCUhwCq2Vahoa25Hqukp7ZkMija2cdi8W5kvJft82qJs4vDC5WKGWjlO3Rd67AYrL04BhTlzV7vzn2N7+SePsVDNi8LHtEY3wkd9IzyoVAQQFqIYaiglfYao7K/Y+C9SxxxHFZbdVuRjFH4ZQXXXZP/axpxwtodb7U4Z+vL0qaTwe7ZDe5oeeRWAMw/Zx5DUVADjK3e8KCTCoAhJB2gAIsZ/T41MUJB3bxQBGXJEDFcW5foO0Y/3qJsd9tkOaqqcLeWhDKa/npGLwgxPDdMVW8PPrfRFF3vQkX9S1L1A4x3a4TroG7bKhH4RqgqbVQNNVRbU1O1IAY8K8pme1FU9efCK/qrrV+94NCsg9PGLoVyWUeZWPw1dgvkLY6ONwzsxEwKEIl7VHBx2CUCqxwMKnVb18UOp7EsoRjB5cvHEZ5fHG02ZbsWzryBW9FrDeJi5P20hdGY06ufmwkM0DxjqVrpTCCuxhunt+D4nQAtKcbimLSpU69bqTUQlgWESDuVa4jLAIWr874zQfq8xXFQ6Oq2WiqtRqHT2YpxFHXVfP0HSnWLw59nugjZCUIVaNkR8zkQxbukqmMYmQOeWFM84OuWHXEZOqyHDuthp2eVCyJfDCttAcZrSTR1sAcCe9f+qetaBxC+lgQuI4cVRN1WS6Dfy6LZzZxL9nWkFFziceSC94I+9HwdqNzjrDJUFUfZz/2Ma80f6829SYbV2vyGBRK0OVMGa3MWIFyb950IUuktjoNe1yiZn14b/5QyLgXlbfH0Onzwhcx+Y3VdHKURzzY6uQxXPaATBdJNXqrnA9mUnRTbLkbUWffQ2i8YFOs4jOg7c41rbUabOPI2NBbHbWvQXoTSXi9FKpl0wAiyKTOxNxPgxKpQubRSgkBBq+XUFX9+PTyObrIy87sGCidzdX5FkkH3vOnlHk5RzlrVtXSr0Z7kvqqJONBFGVyjTSGAG8+abVqJXkMEL+VHu11ZfEebHt8JkFjVdVta1PGRRjZgolpq71GdEFX0oyi1SWUj5UImjS1pHdP2TDrJc2TKNXbQpco1tl0SJyh+geUKwdWVh3B1dzLrM2yselkBotGqNRW8rAAaO17mSXbU1Pd0TeueKLhEKeFbn4Cu3sCsXugQH0wGjRKK5OA6JfMx/32NYZa70d1ibQL2uE32O/QFW/5IebnlvmWaOkpTVFXLNEh+fscbVF49VkSnGPw7scP1tedZOkUCnnBZVZCWALtaQTuu3pnkrFS7JOPdPwfVqREaSiDjMxCRBJS2FXxRL+rlgVA8xzjLCLH6CLbgMAsiwTsNOgvyko1U3UTrSBxsF8kjtb6iIusuO8ypeRUcc9KUJcrTxxNSc4FGu8Yukyk02XDT5N+9ZeE6eejtgxghQ58T8+syEdXKVfO1JioxO8/T7JqinelaDtfY6cMyjV3TxsjcpHT7v9QIuUaXGWmvdZYZYd/YoiMjDTmIsntjnHIkqwimJkKSnSE0N03VLc9NYHXLc1O7xz9DHlWIh2Zn0kHW57vSWCIyO8dJluQzXpzsiEWV1yUZzYbJFzxjU7M1QXYSZBBo4/oGmHMzP5Jyc1HgvK6+oBIROQp36p/co/Rb0Uz5VJZ0t0qNL/Jq1mBLxTrEOrq9xRlOwrNujRue3ew0aE9V6C6NoO9E/oTwFm8MerMUwUQxzD+RtMuL7Csk2sx3ApFU39BGNSWzjvDk+/fXizR0+rDDZXcdvsinlx0XavM/UTI/PVn56t7zeoO+4uC8MAyqo7Q1BN4W2WYB/pAbXogxmYaPk/zbIjt6oc1FVAzb5vnJks2159VTTqolmjz/mixgXPSraWsAjjFsc8t9Q3Y4Jf5Xq2nadFBJSv+cTIPFm15EZFSNX6KKeYRtRg2/oycDyxJcbnSh0V41X0cbfdkhXzRlep9UaMkTiYsE+8YODS4dITnPbPPSN0cdDkTh7JqayfmzoBv8DcpQhHSr6w3wZnfCl4ieJTIeBKtzmLcJzfjXu6U+FPSgvTsYDY4wS1O0q6/vMelfQj634Qpvk3zz8bvHzkp5ssyfaYHny62M3oiA03EyVC6dHoNArjFE2mPwrgXovJsvUXTN+x7n4En7VCV36C2u6IO7cEo9APCmP/Jm8uopoaRgJw2oa6aD3/Ftu0s0DgICvPlUoc0XXN9LgzFDS4OyqOI6uLYWjQTT8HcbKCb1XyiSOiuWe/XsgzIlxlis6NlUBPeMKXft2SXaILRFG1ZDnnbmPdBRFsrIFEZgaTDmGq7DoyusOhRxKJXJzpdIHRWKfXq1s9LGEqSo7QQAhdIToVx135ckIxaCTl9wEDI9gWKJqBBMUEgnrLE9nlfv0FSgZj/EIanbGk+3A/3cnfoNdZYHWadGs8DeFJhqGKyB4aOcbVcL7SrcvbDOYclYdZzXBl6SDps1vvGFELaDmKvbGugVKqHcUh8DWf/I0Hwn33MolIGa9mb5VMNgkQ8fJbnUQ7sqFMEiXGJ3YTUg2PgMUjjhSuagWNRtDd5DRitAt5XttFOM+MeZAzbOq6GzR2mNvycRfHIDwpOi2S3k2r8kxNjRRy0W8V2OrS1zz+cK5XTDvcTIuqaWGdZ7sitsLwvO3M55NXFH60Tdt9d5Jk+Z+5Js5R+TFnDb0cCaVDueG1UdeUQKUOWqrIIPv1TkmceBvVh0SOVgsxknRAq+9BPFzr/Y61N6ouWb47p9YGLuyOyxodkDsZcYzSIjmTtwfPBtdDbi3CTjW5ubdnsJ9l02yHd4wSpCNo7zakAWzYx/RwQknx4zdNwCUW3dveBqzyGGK5JNvskQMbOS+cM5OgV/0uaIn0uaUIa/o/KR2oyOtO1v17EY4p3aH1VVkdJI8c1gOsGHRRFtNpUdarLxwr3NDuez4CkXcH5rbR5r3pyacF/Ib05JhZquXcR4c2o87w20cj/EfOrnp7VyQ3M1M48PLZe/a7jvW0wxMqvcYRs0gV08BChuQLyEl7AB8QhBqYAAfAcZ1PBdpHwOHcFj4BCuOfqfTM3sSF7KnTeTZtDG/0FBQlA0oA5OUhta4PjnX5HjqGxGE/Hwa4h+8tCFQyDSGHp10H/qtqLYINdlkn4jFF8o+L69OB13r9nyDPIN6R82Zp7VF7d9rGMSRaFXhCy6Rkqq42dZiJtJB8gd5ACUsZM8VFCgH4syhlI6HAGYJbKlU5S4Pp8E5HG890ZhiCkHKnmFpcVLDoQw3YCdCofpIAoaUVhT4jrYYNhrSEuHfu70MFwrFzRhHpmVlQb+SfErScUcpkyx9PRRJno3sC8P3fC9+iVKgu2jzRbn5svRf/ZszW9LyN8cgDaDMISkkxVg8SKqPa5J6PsYxctFNHd+if7ZIK+nwXu3MofmsGSo24qyZEQznWKtPXECKDptd1aUHTct7zzv+Re1R7dRDrnDOqC/Bm03teJjGPNFLNbJ7W1/zXHu4IB51yFpUhjdFiflbZcD7rro1Ll0RLOgBTLTc4kGJy5DUIX7FoIAXJ0gWKB/s2j9ASdkBQsLKBQxHZZFnfKIsCxeJGW/pXbcBnUBLR4V2SmOccUg2qFXnLjI/WRaJCKJSsJDNC5wtm1cHIvlKajiibkVDi1eS92w8KxbSwkGOLfUsFGzgUgNQWuJEsjcb+89oC6kSGoFiCxSwZi7HCXOSMq06r8ISqgOq6C6LcPNItu3xFwPCdFDTT5td/PnIqM3gP7Z4DLC84L02IbC9wwfC+95dZ08nD4ghhq+qAiiE8IGd0X5GG0hpk/WlkUWw9aI97bzedWGOruHvkpxzmEPGaoXEVEJtXfD4YNOGPYGUImTuratIx2HWlcMOiGFW4mo01t8B8WubkuiWHBw6V5WitYkP9r8g/AN63qJbpx3ISQLNHReEUxE7FEa4ZpGgEK111zL6yzR5HRWdp7nEmlTUr7usy4G53VRIDxoLXVbIsl+5kxO4lgVjkuQh27kyqwr064O4Ny0rBhVzuIJ2EGyNEz8mGaoW5oDpYEiukAlLoITMLXBmy2+wGh5/cvve9pbRMpyfJ7jGifZmjUZ20UrLXbD11CrLg7QqK94aFcPmnL9X1oty2lKHfW5l1pumZ58elfceWhkUuuOhrQyWA7aWN0WQ6Y1HeLEe4ViHYpJIDMoygzMjQQ/Sa8GTNJLOtiopxFsQ9BBBFSu7W2cA22JjDHUCYU/qBR1W937DaRHP4pS95TEq3kcNQb30Ot5Wj3NKbCjjWXNx2FL4WEJ1Lvei7t36DvKwp8ZL8raHI9sHZnl2PwZAV8sm91uzMseLGieBoUpHOYOfSp1cRuv/hIpQO4WlSUqF2ksasgF1Q7ae5EzOdLf1vXO+MzQqxjk+lQZE1barkFRjCSVcaQ1iiIaQ8Prqx5LyVi3mv48rCnqtnr1GHxOHcdz4+lGUu9mNM93jOxxAzzdIRXKewAJIvjGPvuuWdgV2QnTgffVbe3nfHbJBH0Rw0fuckLrk3vK4+5BHzFjSBz21NklSguvFPBX1FFI0L0YkRwESd1WR/rQNaTDsh+hHAwXqttDx3FOVtMs4iLG9k3h0+p59IYHZb1aEATg1wLBghY2st8IkMLixVj/IIA/swBeZc3d8q1GC4pM8ruG7OTdZsD+LRPKGDgNifSm5xRF/kLEdBCquYWKjOj3smh2yzM3aXn5RrkHnZY7DfO4mmYtfdf3aIs+JyWmqDxEr61fveDQHORO3VZLqAicu8jmL0waXse5/Dwn9/eJWtxtt3Zr1/1z4PbZ+dD1GqP2FGAuG6/KvE4fAUx0e244N4kjwWcF2SGRjpN/j7KMntYEez/eFpU200OkPEDvirviAqdUqNZzC+ltvc2Oiw1jVc13U7nIayKCQ3bJD6j+UZTfZmeYixITXffYaoWTpixRngbbkD3O04f0nmw0EH0kxRu15jqsshH4iiwd4Y22FnNX1gQsX5o11nC/7yvPjHlkArhiSByUfiw8aNgV4LFbHk9mlyilMZEvBiSHRVrdlmGR/stMPsluYgxPQv5llhxH9u8w/bvvcvKuoAjCyWrj2SXL95ZMetvCvO2F3o8VlRYVsOp+rxff5gzuCMunIY6x1W2n+YbM7AIW1mXR5JsxzVsVybZtsX5otr3YBd7zmPrYXh2J2ccJ6xuUF1ucJ4TjZ7sFKjR52TCqo71e2WvLFo5m5WwBIu6B3yftsXrgVrjHclhs1W39BIcZc/pjUrIYEN6+Tqpv/vdtaW3CkTKuA2NqppUhV/gh8WWT54w94quK3yfpPc7Rvhid8Eqs1fsM562l45VJ86pJU4Q2nrVPy3JauuZ8J/GORopdILI7lJ6wtKs7Xxppdap9hu3h/bKsRm74Ssyu2QAr751NFcLC7rhuRlCjB/2pbsu4i55FRx1lONFtBmJFvBf56cOOiqg+sC/S3oPi17Ty7yu6PATaR7uPOad0fVF9QA81WUk9dP559RZvCOcG74WanCj0fhmOF/UF2nlzK1/pJSBbbe2lfPtL2N4hz3396gWH6KCB1W1xhOquP5oNx9kugdlFQsdKED6LI2SZSOa/NahBm1PC9NlRXRNN45kEpjceqxcgwoPgqNtiCBZ8+5L0hEwC9X5znE+VuTAjEqir8ziZUtfMZ4qcYSBm2uCoZZ6lP8Z5Qtc2m+1WkH0504Xq98TWMFhjc7WMNjjpecQ0AUo7g84eaFiAOuKmA59MCjWUtJPTgLoe5bIjd+g8X800CBbacjBcFddBMSgdxsTVMg2J+Wo5IrZG0E6b62eUleuwXmk0f4mLMjhzPmWn5Tfs18XybV6iXfYYpWGDm+Bk9iaO03T2Nq6ar/9AqS7rUSTbgsaKzR8pFvNA+4oI6HWJg/PzETR+ruxWgacpfW4x2FRF+eZ9kjdJlj3G82xMqwt8MTvyOif6NOxXRtsBsSQ3jeiGBwYHwsHo1mkeMGh55rvlvz6zeA4LtEFMdVuGv8wS19VHAZl2SpaNm+Oclx/hRVGKJ2uuIUkVmck4BHLer1eVIRfZTC137rCrKnNW8zLx3qDbhEg1WVVbWWBCmCK7xU6S7S7Bd3mIvhpwHHSVui2DtpjrSp/Rxpyp4Ug25z6jKpdxS/dCdI22ZE3xupU8iqGA6iCN3tI4k6/xZ96UvyeQ7WtN84cJvEuqumuuRDF0qtEV0NnjXYYq7fjiZEH8uRwDsdJDxNyTR75fELTFV27sX3mcCU21XwfV/pOxtvUC9wH9qN4hqp8JXzNn1f7rHIzxsNyp24Ip9nuDxz50f/uI5PL7rDj6JK7D0j1YXUbypSi/ERLOnAfnrCibbaAotjiqFxKqgwxqWCSm5MV6eaGdwfD3dHY4XekbMdoUvRL/ggl71VCSW1kDGuRYvijxd0KLMW7dV2B5PAdp1UhrDIVOeSiWpF4XsTCZtxd/nucuNn1+eXmfEH2JOYnwWnKfnfn48aip78NjrhmElyjFO8xcZtmPSaOObmLYGNSjvFq54eEnRaoBkzSpDtY1JmgSHZves9DKvk9App4zkEFLwHuUVEQRdw/GBuWq4DAd1gB1WwYn4Uwv2uz5QZ1LSjrLC+jeWSucEyi4iskbovHyym9zI0nKiOwgLAdh+ZmE5Xy7K8qatHqLvZJycvUPwrE24Tgrsk20N3Gc76FnbUB/nAwUcTBFuazU8XxrRqINdwXD9/DgG94Fbg2TbygMQ5dA6GMefnxBpO4Mo2xDfy2QPWhgs5Miv8V3TcnfBprrSOv0gWgvdu5nzL+YNdt8zJozc2uXqCIK+jy/1R0Xx2mqvwBLkHslAvHPs8CtWcpLvmooab+nAQ3LqvCYp/7pkih3DgkfXzCoDqu0Rqyj5Ezq+EOfN/7VPA42q3RNMy34babYh3pP24CW5m+TSndT88/xMiGeOyae6WoNfZp7zWgbowu4tJibNlN56uc9tI+Qf6DK8g3aZcWj5/1rEcVBo6nb6lelUJW2H6GOlWZxOaNmYsoYGxaLEPpI2bbNEfNxGjLmmo/RCH1x9rpM8mqL2zRCMaYCwjmOpM050ColGMxj59y5tQyZB+LMyVXztfMTzN6SdRRlJEZo27M47Iw4uDjRiVQe8Xf0nklZHXCTxOc+iib9e+8rBHZW4sJ8MwJP+yoVjLSrUgKGXdB7CHN4cvUPhoe6rRU7PGfK4knVNv2rJ9X8etsy8VSclJvfMSHsXnNdnVf9ojgIb2A0eRzjslMIMb3BYX4xmSkJMy6x6SQ/6Tq7hPd1YMfO57uIG3bBPcTH29sKBV6+ba83hKE4Tur0/gr/K/Bg4YJoje7ts0A5i2henRTbXRt55WKQRsuQ8Xe8OyrT+xgB7LTW9CZmuHE3WVtwTokgA0/MIGG0BKN5/DmjTenxV0MpbNP4Hv/jJP12nhN5Sb8FxuefEKWYFXcvFBgPlqu6rSiBv+TnpknDNVWkiN99xNYrWA+MsDfBShJorOD+rFk7YU4jGeuYB9KDWo9jgA+LGCWrw23Svl1YBlwvH3QJhO6gSNRt7Wcb+hmjH5H8hmuLViOMiO6K8jECL4uoDnx84OPF+LhX7hHYWMB04OIDFy/Gxb3PazSCvJmYR3TgYXVb467iVaTdyeswPPOv+GVRVcQGz8K5TER14LO18pnDVZ+8uURpG7jgzhdbmi9rQHDgBnVbUQ+aPA996EyFuksne2tmP+8XvCGb9r8XuaGpKC9Y4Vr7+nOkA8D1vZ8cO4NTFyZyXRylEaMl2heNQZ8SZWha2qmfm46/Jz8SUCw50CGYoJekeIQBOnVCctCr6rZaSgVHCpWkMUrvCB7r9lA1UuCGYeg0ed/sJ6nL6Mar+/YYy/IRrhjKGJV9eCH9rTu2jHTcvj7lf4zucP57WXgcfsqz9+P0YZfkm/DxfSjOiiwrfgTjQT++4HwTjug8NT2iGYP/60etlL2KE9O7ZZ4Mm20sJ1V1kiWV4d5i1HeBu5U/joUgni7prAg7C6HZMvbB35qkBaM3fcoi64Kbz6uzLLmrRjJ4b9AB7NGsBzI5ROdkjzSqilE7/BS9R9uvqBy4eofznKqpz0nWkN+/SDPKgb8hk0oENh/hX8kU7mipoe9ZkqL6iqwmLc/5E/YKJWV6/6JFV71gse6RoG9xXdFV0paiLdQRA/9KD/8u+YoyFl6+k8XPGLcY9XX+5Dtrw97yLaZXmqJOHYt6j/N3co/Sb1+LB2pw2M1gdxZvO38fmi0qcXpJ76uq5tBqPq4xKi8IJnSSZGnTqdXhcdxoykrdyB6nqF2VbGeHWJEp2TrQdCV9hb/oKxxt/kGo1d3ZG6b0F4/5+ZJkGaoviooqpEuUVNRGCZ+YPvCjegHg3+OcHG22OLeeE7J83ycVspUZstfFWWar8Qh0k29Uuk7qCyEYTjKh0p8NShV9xbWKoay446QpyY42fWwfGqYT2GQxjIwppRPUwB75o+3Ge7zZFUS9v2EtCAOvcBU/7WxZ5ij7kTxWbWWuNQPvMNWYtnyWS9PjvMFTLTyipWppj3N+nBVfbaeZ3iohMogoz1rrhe7EOWAN1d1mDJdFNjOFuqV9Gv+YBohftC9v2U3Te9IRvCPdPSvKLR0gV9lqM3BUVUWKWxIOJm1BE4d1e9BLVLWbu5shObjQ/9N886zb9GlrTVvEx/HqKlSBbHnbERFiki3Zb8//L2n4tg2OXmumwWEIQiOv+DGRRj7mXY7UZ0ftzRIaAlalyUbeXhOKbvgvvdDQNYzsKCvCHkRPyk5jnKdk3jKXoQhILH3PtJNjc2LJG7RDOXUbu8yhTT/YVPJyf8ZmBWKaaPfrS4ZZLXgY/6sN72j7ZsnAYBUl97LQzqwLN/X0+FY7jqWYVjtvT4JjJ/d67zYbthZ0lJWSa/XVIM4Va7gwrqE1gHlZAENLLsQqssws0RwUSIqCrtMOw+cQPilRBbu+gHSCc/A0BJL0/CivfqDypuUTHVMwcCo+60BcuY1FDPAbxMDr4DWg4wtxGzAXNi1T+L3x2hXZw6D2KJxst25O6G3T8lHJcSA0xHccoAvrwS1A6r3v6up4UDuCBThRO0c27fdV9saS/f1kIzMKcBAb9iAuDChitWe9X168kN0UXiyk6MMCzKOg6VNiG171mKaZl5a4LMTjBhhJqyXjsxPYnwWZCqS1Tftcxb0x2HjVdkqgoOIAGRRirelisD1vAZgBxrJjWp+xH+OMpmoZGjB2k4ePTgUBvT0pZOnyoEabATavpxvepv6KFXT06GF9yCI1o9ker8+AMo1iAY1lmi+r9ZBJZbAXfXWcFXfUK292V0iQEF8OQC4MKSN+Uq4LZfcXYEHlnDwJFwbt/UmxbdPcjIyj4xIRWMWBPZwrE0roAT5UMfg6+FA1goVYUTU/Ns0PdfbnUMNtEo+b92iDk/5YXO1VA4BB11oH5+RXg1ADnMj1c7Ztga43S3jMNHS2aZ6ttz/O6i58DmMZeELJACA4yF0cpBOTwW1APlwY+f71nX4IS/Cmdp6snLpdldUwZn+d3JZpxOxNczCmkPFJbmP9jMkPYQ+Myc+TFWNOidr240XpExEZdaUICO6VexinTbKI114zxlt7VZ1YYm+roOtT0GpvcNUmRL052pFJoZeh+9FgjTNOVwliqgHeham0bUDeFzvGdSANm6zOKFsQMEQKFs6FHCD+fciZriMLyJqOzk9T3tgRuYgcV28+qeObgbZW9hwdRKd+mXUh0VBlPuqMLdjbXBFoMvxxif7Z4BJ12ZaNnYZquVAmqrFo2z+ArgCceRK9dJ0V6RZQelYksunHUH/fm6jhNPzj7ccS3+HctIsS4TXbKI/9k4R9HxEKhr4stxNS0dqmB0LVvbMZUU/4Oyof2yTVJi5ggSMzGIcaUmlsP2dnMag3C/IXRGcr5cXU2zdnHTf5JkM0McJRXZf4a1Oj7p2Um6nExHA2ODR8qKzuwaBWXVGbOMyY1+pgchnhcrLgwgJW50JjrfUISD8US3+pqp6VIARxvtDeE3SimsayD76GZ9Gel/ftXJUH5M7Iy7GwD/PGNC3UXdkL7z1BZ37f/uhSHt2aBiaQKmi4zcfHr2zGwRW7GkWpHMVyXKqcL6ttVl9nNVxqqRNF+Jl59Akv5aox7IFBn64S5c4KODe1gYOUFTUM63uwY2zS0eW+GhY2jmg5XjbOp01X2Hqr4mxL5QvVWYifn7Ai1o1jTwz8dBXy1Q6l+BZ32ZNGj4ctA+tra1gZrujB1IYePEH2thvRcoxuN8dPgeXhkXzs3mZUcKSK/TxwgVfINWicbpR7dAe6q2kllvsXlYDhLiA4Abxh0zsYw0oXEi2D++l2PXn3tuRou2Uta7Do71/iwse+9xXLhm/85a/Ds28pHJbl6+ROk9RKho18uM5iVptgpDhizqoO5+ekxElej9NyUmy/4rwFdAo9sMWjIZwGhQdNrTu071gG144upxdc59SmZ2uKgNCNz3JDZ4FiFRz/hPd3DsNah2g8wZ2exaiGh0k+5ThIKlg8qxANrkOAfHAD3+diAHV0HRwPzalNz9h6a+N93xXAQ+1HYOifScGvR6s/YVUu7LOqKzTuMszOOgccGjYHqntwulUn1Fy/YqecxwCXEwWXuXcQi9W430R/hoZn3RhUg8leVsLlRNcNC2lRi+xa5cZiwHuTHgue8JGhCcu+pUm1cFovNUYE+zGnfo4Vxnp0+7e0foq1RRxc+/7NjYpbHVlTi8xBSlo8EURF3x+12JikdrXSYzXg/UmSFX84SJWIYt/C5eSIsvU2+ZzZrMpvtGfn0NP2ANEnALMi2dilAgShwSQEPaBTegYQ+d6yAWq7swB7aWlt0/6a8gHeXCX0Qb2RLUwKhgePrL0E5NAhqIJ94+suuC8Lai+Y0jYd4GvujcPGh5i5F7CUHAaDQxw2QrrwmAK943NcLZ/t2wbUD2UBFtVPlU0H2HorYFDTsYoEOQNbPsXDEmXvF2XCp3sgcom+Y/TD9lSPh9asvR2gxwostPCUWFE7guWWbXiOnhxLvkXZ7rbJcvpUCc9UVhykrG5kWqamN/+qW1czNCwyK2Nr48CW5nPjPDswfldxb+x/VpTNtn0vwPhGjgwKsfUI5cLHAGqnF3GibIHUnViAv9TEdbEr98tG18UOp5Z8xMMqGakFc+YkAfmeWAnuxVK8BBP46TDTTfv/38ui2ek5iQFUspEzB7FIAfZh+ra6pVPV/6UYD5gPm6anWmtQYh3XWCiZbshzqK8Os4r5Vsp3QNeXVXjcfFjz3QrML4ZfzFYSM+D4JhiDXMV9IF+vhAUVY1jUhpPnx6b5tsL+WfFzUaObiYeMDMPDaxmSgnoxpdCGii/X+TqcYRRLciY8V9Z6cq+PxH1AP6o2D5/xuUwJEmLKAciFH2XET+q5TGX3F2BB5ZzYtL335zJp74cXFkfG0XGJCKziQI/nMkH0AB+qGHwdfKgawUKsqJofm+aHOvt/6Jw9T7R465sDj/7UN4/d8YTZ//L86UONyjzJjpr6npK3y21widKi3JidUVa1IVLpKrqQz64DT+p9cKchLSDvTnP8JDxkH8sNTeqOswznd0ebTUnPjVQcBgFDHN3CubAuiBjg1KGD8/hadb1YgLl01LVpnq+5Z44yqkseLCIXPU1NB/d9Ma57krpreDTqU0UM+reY9KZ8HF+iUkf66mrpnh1jK/g8zgY3qHlHbH1cajWUBZjWag5t+rH3d8fAkXSaz4mfOjFeinu71gDWBXX2SvmWG8S+mJabN5tOtBX2u7i3u1w9jwpwyuXd1W0m4n06LKjo+VIrvDwXT4nZTPGdEuQMDPcUYzqVvV+U7Z5gLOfv+LY+Scim/oJ0+T6p0OYLru8nDlKxi6EexJZDFReuNDWjUosQ98e7+WPZqwV4z3IarDgRxLB3xuSMiJGFTPwC1tIxpa/VqG8QYE+VFOxfh1oNZUGe1s6hTT+GOuvi4U+siLkxMld1MW7mW306hqj9YPbF1OB82nSGq7hfs/UDDY+w2CNNcEqT9YNjqIuI9+mwpqLnSxmr8lysf490iX4Q8bkoCIJqkB+j711XCWJDAN6FIbXNPSkvvc1IFuBWm/l7Eh58aCB2hoCxprXaA/c/Lg35CYzcrMsbPvd41wad6InEg4Ev7/QQTq/s8FifzvICd3wBeYXnYf2Ly9Dvbs88sIqJLzhoHdO5+uLgBqBHZRRcvR4WBIewICeCc2TT/ohgv+EFtBs764gVATpisIGI2T5mJZ7bTduTpWxnFY1teWq3gsiVL0lGdIG1FQ2DQ8zFQbowmaKJJ2Uz68ewAH/q5+lJ2Mn8EPRGHwA7I09GsLUjcNXSBp2Gxuu36i5R3ZT5Jfpng2ySNcDg8K6HgXRzEIBNPCk1px/DIk4B3Tw9CTU3ddrSvFNViH4jIaad57QBK9pmT5KyM9mPm3yTIe0RtKYOvBnjwd02ZOqm1GESzBhmWx4serbIbss4FTa9mGrt0RMgjMS4bChrzM6ET3P9MA5jH/z6JFcRaRSm6DFVhdk59SmGlJkGsQ82fYIBZhdFln0uajKK/vYY/XCUVz80KlVTB0wLKIA7pQPUNAVx69T51TGsxVAW4FmLubNi27HW/oz0e5R+KxrxbQrps9pot0QAGvFgXSeT3rZ1yHqQxrg6bncd3gKs7zrfVkaGWHmP3pS0KQkp7i6Sx/Yw5TzHFK/p+FpTC/at8BXc3Cu6xuzPb6PszKw6s4i7xGIGrPrB1FsNFw6uSYltbHlEhcCGN71ChCybB7jVJBr7V8quo9sD+5vm26ZLYt29SQOd1u9k7t8VdzfM35RnlAKgqQPxPAPiwue6ViCfotD51XG2xXgWYGaLubPphVB1Fexr9LNBwDMx7NN0rOlGsDBvPkl3mhUXmrjPket8uW0VrwvtidGeLIMd3d7ijHxBN6bjPgkSVHQDkJOakzAvnn1I2YUllJSKsFbL5p7Dto7STMjA1aaFU69oEDi8ZGbunlMFesckdvvXYvpxLLJw6ubJRbPRevs7jSLbKvrCFt4m5ePpQ3qf5HfokojaCdkroTx9VB9LmWqC51O0ktOhlLEVkHX7vs+jCq37tAAbWs+ClftejWYdDNr+cONMrkp8luTR75kXwc4szYQgwR24j6u/P7ZL79GmydB1Un0bnE/sNzXzGSqCLCjXcWJIU5PQHRR2LKtb3W1HtARvW86nTVfYenvj7L81qEGb022Cs6O6TtL71o96hjU2qboKxM0gtAs/a5pzfTJ634xsHsoCLGyePptOUPiVMS33Hr0bN0Fvuy/AxNCT9myz3IhmCz+179veuBOaH5vOsPXWwK033cBSfdJXVQUDZ3ryI98EwIVcn1dnHJhGsizPgvNl0wW23ho4lRE+lsXc9BtLmOW0KtsqwM4aiVkVN+tHtDdVDMypTV+Yavt96uuq+VqlJd7R75aPtoJVlA9+sdDOj37BTe3pJVdtZxZgQDPxXRyre3xqHX9PavQeVTRv2M1ZWWyNfKepAz+qzoK7PaWubmh5trPozQJ8Z0F8qy0SU28tzHdduLLeVGNWxmOa2TvbyX1Znulkstv0Yaq1N4Y73+6KsiZdu209CjZOUnUViOU4aBeO0zTj7BKNwnbmDi3Ad2biO/gycX63V2/m6YMz86mrwM+feTKfppn9MJ+5Qwswn5n4T475SENZ0UUjD2yi5wm5gprxJlh33gPagZw9Ogbf//7YNJTFeFY9azZd6KvsjVWPk/TbeX5VF+k3t/2wqSLEuoo6LhxsbPZJBfnajmYBZradzyex0VYNxnSl3lBvYZ5+ivfsLceyR4Z+grfu36O8oRkD+sBC+lPJwgAsxLY8mAu3Qg2AJ5Z5AyPfP5NqhrAAY2pmyKb1tvoynHhK6tSPpE5NaqByMLK3SVl//PoPlNa0CD2QiU9bjZ/keVG3WP76qUInWUn5ofrteV02su1LUV+hevRX7HBaPX/WfWf4qC0A2FGonjycJDW6K0qMQCxj+aMRF/mLJjyA0PRFRhTvijTJ8L/Qpp8+uFMilLlr75L8rknuYGx9mV3n0FVdtlkdqpbzlN0T4IzIL1C5xVWFh+elIcQijBEp/8q7jJAPSTf1sMgysFfku1XlLpGFCsWQT8RySLrhGJH0VxhAmoy3PkwdoefMCqnpyiwkhgg9Ikz8nWhAEBEHYE2bVrvktY5EPYgR5XFW3F0UFYhrKDNPfmcGgDM/2GMGFMNjmhCO6f1cE310ms5azV3gtG5KEEdfZETBBtVAePioJTvq6rrFQZh7l+TNbdLCgmLGlltPHE1UiUu0VfAlAGZGjTL8HZWP13gLDpstt6XilHtPQ0g2o6Er2jGDyRnOaoU2NNSxbVTL7jyMBdd38CbeAMBsUV/tUIpvcdraQeOQNY3AFcxKF6z2sd3kgDpYA+/ZmH0ztsS7TkC7ayq1RfQ5KXGST4l2TortV5wnKuKYaxkb/luTtF8+5RhUDWy57ygcum7bhA1uf6Q9OxIAG/QTtG9D1o34zkCbBMphGvpEX6YloI9VB9X/GMdu2gNhVJJNKGyBjYVGNG2sjcpWGgvt0Ch3dVOpDaJEhcGuF7+XRbNT9qItNSL6gH5UquV0KDMiOX0gy16eZEdNfU/3452SVG+UdPDGxtrMThDWPk2WpcHDPHEIr8DQ+/BG7MADijB2+A1MS+w6hHb0U0058yyxDRr6RJ8STfdQogGN/DQXTC/wCS/T4t4/jgMu5+ObRJZIVBTjX08yIGMfKoDHKbwDYaQek2QeJhv3BoBxrHwmV3i8YsZd065OzA0I7u7kVI6uaJUrmCpJpZG2UEouFXcqUq45tmGD2+w9mVLcgA4UNvOQParW5alH1+XEMnrpwF5Z9WZIiwH2Y8ozYiHRlIPfo/q+AHUpD2FBpUy91jFZKwxoPpUaNGOheZFAOSIGkVbcRBizDXZP9vytDfgVdlVyABaOtQJ2hfSX042OtPa2tMJRNF5At+nE+6SdaWVf+nIzMunaLNw78OqzA24TTrP932kRNfNzABa7Q+AKBbxNBK/C2KM3IDUbqcz9I9Ao5S6BGV2V212C78CN2VBm4WZsI2Gv0XaXKbSaAGJlzr9DNTGv2bNhlXEvQ9ptPEy4gVsixl0pGxsM70P5OGwjbZOqKdEXhO/uwenmAGzRvcGEayvFqEUYI1IuMBXCKAQCm9TEY57q1NlUbLGh4yPA4E2cGK9nhVQzXCEyz3T0AcdBgCchqlgWB0+7VkoBONvTjkcdYhHG2t2owSmAWNioFGyjOSTiIcwDL4uqIhUzDUoRxkJAh7AMWDSnoA0LRFMUgwoZG2IiIGRiDvRn0zfTyTZTR3NIPVUQQyTYVI2aemPAzDho5am5FIVh28QQMsM2MZ3ui3EvPLFsCcnGFpipCEMbxgdWUtJPDIkwUQ/GPjPpxPCEGyH8QCafoYZ6kPqKEBmB+AoNEQ34AUIKYw0nZpFlWtbjATRDYeFAynRxHTpqcCjm5qLpoZQusgQeOgui7zkDqRr+GJliIAKLCiADSEkPEnBRHzdjMIlMCBhQPQYQHiKKGJiioQuME5KQKWAmmEJD2lINbUQQ9QgESIgeTMCQhhQiooWIIAQJqUnBA5rHwU9tMFl4dABx9FznQaExBp/pqEweAEo9GBkYIgwTpaYhDIALoIqSyCEEOcZZxjy4qqOKAGoxHL5GBPoICBciUh8jN13p0VBJgjWPSqyio9MU0WdBLgmxxlyJQa8hDlBrrshA6oFIsBBpmMhEDU1kVDObL7TBk2LbXmubAiRhekhw+nGI4MEMAyIF6KMktY951wUh8qnmIBsPgtNYZwA4aO2NUZI6Uw9CBtBFCOMMpw0Xp3kzxmsC1IEhNUMCK4AUEsNJdYSCsUKmsQJdBCoNDhwzlaBLbNrxCLfXIlFJuJ0mY42yiPWePR0XSTCatUUABXUPE2+qW6ZEVPOyyxBmc3O022UYba4Ltp8yUbTw6lHpqkHEYgLaNbTSYoWWdeUUeFCO9Trr2AiEU48JAocoJIQaa6gEYlyaq4Tu2jAWX8WFC7iaMdmLRwytebpZiULISQna0HCEdhnlUCkm5Uac8+r1sWH4YoWGZGAFixFC9SIQDkQL0A4eZywrYvDVfLz9WOI7nGvMCAnUuOKLNTSGhJUFIeGb2cE0NMtfpFETiIMzj4YFDyYNhwxiI/6yUCzaKC/p3LCXhJQks6puHLwNFg2BdZeTzJS3alytE7kLV/Fnpe+EeZOgrOJAAL6mFcUdSSy0MO9SI7fuREYfAs5JukWJNm5ypgt2SprJsMZhSVU0FLPclikxz73NEBs2M5kEaj8qM4N5kmtR7uI2PPxdSyXR1HWMg1RW1ZDRfvdmbGSBXQjUBzMfguBuAzXzYxghF2VL+AqrBSENFY1D1tfXEFd5PddMZkOb8xJcdyf5RnWTGIgf8ECjiQRwxwbGHujvZ+tCETw6AB1D2zFFdDnRXzR3FR4tNl/u1lN4QTHTdsR6ThVMFbBVHoJf2+v46p0yC2be2zLQwftkFpdaR3X9j0UVTXYAW9+CNQojBWwxaSitz5FgngTrLiznx9B1ybx+29QOoop5JZ9zThY1onQd4VNheE0HhyKIKiymPU0M1wVgdoTUInNNkYeE+IuFhyx40XlZrgdSmdyIRRryWlU3ksAGi4bscPIWM+WtmlXPwhwWqmhRqXtqPyk6JK400uBymCCPydE1bDFFGs6Ir45sBMdc11tx2IhMVE21L2EBUxfdKAod5kKPx5lMWnQOczTkwXCfKH0P1JNm5JKADZulSeVoN9kaR3Z7tqXNnCFzljFIEwbUHP5D8GAswZTXSxdLAKJbLlDz5irZ7jI0JRpTs48AaZ5zvkIwCwnooI2liuQe9BmTpnH35iD6KCDVA4IrQPRh07ppKKRAuMAVv6llzf5BBrIZimaP4EyWRfcBl+g7Rj8sNlQCoFECePjg0HkY64Ikeouy3W2T5fQ+DFdgpJm6puVwlQjiUlXdjEY2Vc14kHtM2ai9AARAqccnA0MkYxNJaogFIJv5os+UfNJMEgHMMAweWkmUIS2miSoCuiXIcsPmxFTQhIUxjIABVVAjMZOBRQLQgMvxGZM5+hSjWs7oYKzmsRtLHJ7ocMHEkCgapDTYTK06tcHA2cg6M5YYqoNBp+IQmMAhtPlckO0ekxFXQx4B1GJIfI3gJUmFV0WtWPfkhty42kuVMpB6IBIsRBomW6+GJjKqmS9V0gaHi4pT0mCYHhKcfhwieDDDgEgB+ihJHXKt25hxRQGpHg5cIcKd7sUzquiSQmvtGbuK6iFb1Ycoakh7raGyXZMz20VtkmaLTAMgnHpsEDhEvSGPt4ZMIKqZswt0ber4TYAwdV/HQdY0WIonoKTnN9MDPurrS3AFjW9QV093fUnMrG5xlwluQ3OXaTZK9inqLcnYQTuOr+OXGQnYNQBQD2ZmXxFs12UlvUQQg/xMkEopNJtVIqZlSKBxtclANp3XuNicCbGMY2142uDmgvT4PqnQ5guu75mnCmTSmKqoB2eoCZGNeZZBQzUTYhU7xTJCoRcnbqZXI9Q0hCuYBwrW09HPQTPp2wBIqZyjWJT8xE6oNTn5Wo7j5SrPSVi+odn1Hn04xKD6GRCDnpoglRqvf8XEpPEYTHOSAHjpRGuOauHVQ9JVgyileKhFQzRtCzPbsVDbRgk1V3IbrKfYOOL1nSufay39Wzdq4RQg1OPiAcF7JtPrPbo7JTyeOQWTfennZno/SE0EHtA8Bg5eRxKziQajhC5eqKjsvXUeHk8xehREQNMeWIAP2k+LuGZ2KnCvOmm1uQJSPR64AkQc6fEpDZUUWGfW2nyrSiUDgdkORalu/KmzhEnAvOJlMAZASN36AlWAFxX+rTHtogUinZl7ps6bNZAS1sIzbqGHHH3tC2sj8Qk3Qy4UHbhuBVLWgpc26ek57fKmRr5ULhOpDzq5VAM7DFInnYH0W0pGpYY17i0lrMO4NM6uQIotFFNWZNnnom5f2mgPh6Y096rM+ypw9dg0tcJDxjS4FSn6Fdn+fVYE8OXHG+CxSWCFsK2r0e+WKEAiq5631K0ntu1BQg680hnBbuGfrrw5z3GNk0yzA9dV0BkcmnqwMSM9vKm1Z3To57X8wIdFb+RHQc3EVNa1H7gKhQ2JbZ1Gli0CVDdOqs8jDtPzpjfSU6cyzXXg6kFraoHPOnAPvGqIqcML2ZjSa69Ryaezj0A4u4HprCIvSi1lC5loYkkLEw2MY19yzOMrvTeazZkMpJk4ERZkA+bhYB0TSLhm3oiNbwMb7+EoIHUMDVWAZSSzCVlSIFwgMIxeDKPXXPA2KR9PH9L7JL9Dl2Sappd+gc2DsZLG1jfVhZ9kKkyJ5s14QWpObx3HJWX7w5qGPLTlILlKMajGI5ydXPJjzTf8Y8sA0Ux1NCM1VAUJCL87raOnqRXolIB/gTqYsOBj0DdnGFZ+Gmj1MNWVIDKqnrHW0FHTwMzXN+GWTZeBLWq5DtZ0TTgaVZe+QMz04oZ/F1xLVx7WapBcFQMNrSnHIwXoJbyFPhNHcq+z2zIkW8mVTVgSzMmObDvgfX/1lPney2HfrTbf7oOh1cNUV1LeYhLe3DZd0YGRz7zb4N+Mvzkri62OdDpwjYdTXQtOCyC8dK+9b6tGvSzprgsHwjHA1mOb6kQmGoN4ZpKdb5nn6412ogZaPTZ1JYhqHLSeaBrEC5iDpw8ulNNAqweorgTf7LGmnAbxYpR7g3ZZ0blF+46o6QbAmgYnV1HTbIK1IRuAGbJWtLPh8whkkn47z4m9mH6zXleNddRjNVUFn8+E6+iJamxo7pdGFe1rDnlNVdwHqznyjUrUZQ6A36O8oefNve+N/gTfeAPA1EMCoMEX3jgwPZUglOBGLW8U6DTE+fVlh4KeOCc4R+VY9utLqlG3Sf/h15cEJEW7ukmy98UGZdVQ8D5pj++rqWb/5dnVLknp4el/u3r+7GGb5dVvz+/revfXly+rFnX1YovTsqiK2/pFWmxfJpvi5etffvnvL1+9erntcLxMORf1r0Jvx5a6DapQSt+E2qAzXFb1m6ROviYVmYWTzVYCu9omZf3x6z9QWrdH7w+CdPw60nlosM9j1CUIkOeOQtODngGc/t07vmhTra/tBe3TCzD/wUTDMzIsqsTbESJmuhX1SM2rNMmSkgjKDpX142BBbcjIi6zZ5tNvkefUta8eK8KA9G8eC/vdHtt51dXrU0Rw3eKL7HG+rbeZOMbhmz2W42LzeFJVJ1lCj09YXHyJw1jzNGs2iGg5TIaV7IThSqUuVLwgfflB5J0U1IRzkTjFEIA9/qEyj3T6ao/pGteZwDj9JwcOvCd2CoCI/e4yK3VZiFPRfnLjFZlHXDC8R3XyH+jxR6eyWUx8iRvGN2hcSGWkXKEbXoD4zGd7XO/wljD75roYDihYjFKhPd5LlG9QeVR9wZt2aWTRimX2WLsafy9yYejsd1dsX8pk10d4QUi5YlfcRB5+ADMlFbriPS5o3I2oYsQyB+1S4qIka5mgXcavjtrlOrkDFEz71UHHNK0FcF0cpZmgZbgSJx3dfM1wdQ/o5qlAxvfrS2H5Fy2Ml5KJIRh8osFiZ84kD9oXnx2smhGTdKBpY9voas9j4ci2jatV8wZXuyx57APwWEx8yWpmm3ygwaNhE90j8ZhkZc21TnAb8smj6D85qBhKBHEg48fVsMa7gnQe/wtt+u6HqgMRn49SsMAxD+d0fRBxTF8dTJ8+MZiIi/3ugI0SBBEzsU+9yGEUyjywKhC64wLkhitYD9ePeduCeF2RkM6GxZVV16oThx6fNBl9dRFm67HQHu+nHP+zQVeooA4cHqtQZI/zLEvuzrekPzSQRB46UOyw+agFi7H9sP9NkcL81BqfP42B02mZq7psr8hUrRc2wjomYPRdyoxo5pH5uGvQ0HtZnPgSd4zAqiEUuWzD6Pn+Rdbc4Vzch7ElLhiviyYFtnXj59VIwQUqt7iq8JQ+MUQCRGwe3G9GsdbVLq4bfHofncU1fV0NB5kSptpzjy7C3oJz9NXXyjVnJULDNXTB5OBKHFxeycPpA9ruBPch89kJV798dxevBIRcmT3W9tKPgG345n5Q1F3dgM6JupK5JXhfmrvIskBtTTD4aGiw2lOwR2Lp+P7wBWKSsWg/Vjj14X/M3xI1eNFGowuHYVyZg7xmWfHj9zb9yHVB870LoisXL7FvUHvRCJsTBkefauH8mC9x8fFsQHzs9+V2c3vUN0OGgFCtA2dGsNQ9qsrzaCDaoohh+Lak5vnQbL+i8uPt5y5nIoeKL/qJ9+xSBoxQRmQzZHiyox7FfEzZiQHEmlNJ/Ikz0JhsccnfH2//i8q272fuvwbY98Pp90K0HpoVsbDfHYzW3Xj/muvS9NnFAD7a7criu+xnmL47jLNEZC3bfMylZY4vcXDT7jYKjHzJ4lwqMudxVtz1T5N48KW29kw82TV3TUMR+aliCxyilcgQ6AM9Yq/Y73ufpQvdm3w2ylpffyZN3TUqqenp87KRct3oZcZhvztgS2rJbTF8c4jO7B40/E9Etg91IhyVSIXOeD8UarRj2bq4m3niMZTRtahm5fmufQXnT4UOsWZJ1Y9GiDNjvu99Hrl30Twmz1B/nhkbGxXRcAUOwTCSj0DlFlh4dphHyDzmRlt73pVeVv18yf5sh+G9NnGc7PfVbSDjHFQEbGKW3r383mDF/qUrcbDqK/qCmujOmL46uNW6BAWcR637tI97CEMdovK2iWywSaXumK+SrIaxdiUODtnNFueDJuJ9sVyJ05E1fGzEFTj0cEjpJRKSK1j6yOgNypB0E2b86H70NGYngU6fxsJ9HSG/S8i6DfsbhKJ9egloV94VdzgHXexyqRvmIb+gErkE4DBbSdbUiXyVhv2+7O6uvcwosw/z2Y16Mqrpq0OvmiwDOjV+dbJddkkuxkQMH91Xxc5zDq+LQ5mDFwWX9T3lI8GJMn1ejR00JVMMsYMUWSItzCBlzXmsoIOk2mGKYZFFk1G6eJZSZD3z2elIuEbk23ecp8AFC6HQoY/SHbUTx/tpvSC8Eq244aszptcgptcumP6Od3TLn2RyeLVQ5GAD3xc5gtQtV+AgP8kDhI35vIRNs69dbCsDodduekny2cSqas6jvWXd5qrX2giVqn/PGghemYpcccKhe2KZw9ryo3iH6hqV5xVwu0EudcB8XyKkww2UO4UnoBKnIGaxzEFvDzdrPyfCJosveWpXHpQr+UwXPfpVoHNegAvEULQaHcetyoG3UFlUPndR9fXncvzGM38iKNCvX0v0HQMmNF/y1ARxT8w9nKuH8fWAxTNmAa46DzfHjeONsQELT9DSLvK9q7UC1v+pyAFnH7jT1z2RvYswhIMuKGpzI0ogl5sDdwJRui8/TxzvIUFPTBPlyW3CLoZcgyEq/ALOtmihwZU151HgfXN0JIqIjqHI5VysJCNrcxC0eRvAQCkFjEMsAq7w1wyd5xv8HW+aJMsEvQ8CLHqp5L7NyKqQe7nUzfOuRCwV7vP8cWAitCX2mnxqCBTv+1LMUEdtfMIQhwXGwFut7TTc5WzDxEDrSoRwN7K6SMWrZgtbWEyxl3mlQA9DuPe+DVyE6QNCeI1B3YgSyMO7Ey2j2lzGydW3Rugg/eAgH0ne3CYpzXNSkhWthlzXKhj7Vn6vxQQG3ReXuIbf8W19kojxNOx3h/70dSCjQSxziWD+Z4NL9LG+R+VogwmxzBCEcwuTuQHj58od5LcheosIfkoNjaPNRsAmyrIR2mV2h8eQxNmdvjs4Xfo64syy3x2iyvKsE0/mvSYuvgwod5G/h+GSnAI/DOFOjdOHHS5bZ9ib5LGCKSPCuLfSBq20GCDZUkM5WDdJdZUQYwvBLAMUu5zGszWl41ip1KnXNBDx6K5ESLZN5VK3qMexohw7CxS7yOX4gK4omEyBi/7qK508phl6h/K7+l7UYBCEbwsXqMSFNI8qGI9WWgOjRSNpYgjCKY7vHu9O84Ts/ySlyBW54FSn9BDLnE5wMJXkJBtqn9zTLP/SYY4Can+RoOfVaSXRtv3k4kwcU76KbCYUOdlk1PGcfycSSyqTvcq9zMRKIBc3ZpF++1uTtK4b0Y/JFTkfeLT1j74nOEu+4kxCr4byawkeBAzhMA8412CXSx12A8WPbux9iKd0+ACUO+2S8O1j6+84K8qhf8eI7E2lnZIa0OHAIkm/tam06fsa0kVNsdBxv61+oEXaeNu/5aJus3WFkKnF22YLzzsM4dpC8mBqQYSwb2Goc1UjIb0qX+KKsX15pCwyOeESVO5gG+ENGnrWoxDMIwjAkY/QpseAxaUaKHbSQnQdPm4ej5u6Fh1Xcqkz5i+4us9wVWvQiyAOlOl0b4aI+F+QfansKYQhHA5PyO6wrYpT8ZYYV+Lij5VQOeP4mG0ANNNXZ+/wCT2xhvzCXYHDmrxDKU4yoHd8iR/G8XjyGm+Bw0stpF+L/QGmsT0Rzj1i/jSvUVlBjAYBOFkBVBFzWBDEPlpAJ4+AZXs6QKed6TVGnRwKmlEocrJvUFUf1XWJvzY1Oim2X3He7veBcRiBncZCdGL3hvHRbpdhce8EAtjj/4Lw3b34+En/zYE6wL7Xfaf7BW9EJP0nB3oB43nrPJ5xjdCrFw2YR1s6xaIE2mcEZdSwssi3BYcje9HPwBc6rABxE86uKP+PZkbwd1Q+UlaTvJ5Cmbsl/ynHUvyBWOa6YlbXSYlvb9UXzQQA5wDRj7cfS3yHc0WgKFvsstesUG8wAK4xudQD83uUVE2JKF0V2DkIjxaOtnJgm1TogZf+ocXNAjjgb/JNhtrjctm/LBW64r1AJU3GALslFSCebVAa6JsYIbxHUXTOTbLCaUfCgrnYhji/wO1JrOwP5IqcbKaLsj0i76tLFpNYvJpAt9GUC4p0G7B4hLqpq84T6yavqK5r6dBj8HROKHM6gyKcnRISSWFMQpF7T1WIoXJ37JC6FMt+nsDg/oy+Ikvwrsjl+1BQudO6D2L1wzbMQhsc024HVfzKQzjG5HSn0MQyg2Jy2MLVaL1ITx0GvHP49B45jJfoQRmk6BmgeFzUxNZWYgWKHZTHQ01zH2XvcP5NUB9ciYupubmDjMDpsyOuq/pRjM1kv7s4+XEiOfbbTy7O6o6vVbG+UPkhaFaPqwuTV52Ry6UOmGFD2MsIpsHQV/hf4rnC+NUzSLi6Lq5QhtIaxm+Cde//R+hwVCp0PGy5TPI7cXnkChwMpHkC3md2+z6dcOA1Ohnju1KfhkPwNmmy+jPZOL+XLGKpcDXGZa89A++O9Z4Cd8tSWXMew7Jv7hjnifiemVDkcl64RXKExPT1KZ7wXKGCvi6aSxY0V+By7vABCbFI/SenaMAyySssBdNyBftUAe/RBieUw4Hr5WLZahQA27EwLcBi8lAF+urz6APab0FTt19WMzv9WVwcLc3h8r/ou7TOjvhuQcwtxJNL9cFe6wqUdAaTj6Rrq6/VpRTbEcRSQeWAUMEcnBB6XAdXwdNzFRy2yD/7FjmW22bPp8/9oVmXUSnGQTSDMOBMWotlJvO5b1p1UOeHjRnIZZMhVWyZBbhTCDN8NswVOJx1dFk8Fanz5FIXj2p/nQ5GDRS7HBdXNVHeraJl3zCXL1Sq4Xxau1Beb4cgvFpIHimDdLfm1K0IUD4tDRMALrcaML9ZapWBbnpGAKdTxDKRd8bM5/WoZCbSMlAXM5h8lLC2+lq3NKRuUb5FD5+TrJFiOLgiZ9PmXUEKZY3NFu3TXDqvepe86EocP6+Gx3vV18UT0mDCKF6gCZ2/I0iHY/2+oD5AE8YoFbrHcMPR2z4GEWz3rCe2LlwRzZh3Dmc1KsfLRsJ6LJc67GbwBl3fN9uvufRShVBkj7NPAMhjGz/uac/7U+9V16LURx7sWDKyjhewx1D5RpTzrABTB2CVrX7nUemVGwYivQ/HFnjga00nJdKx1MVouShR5wiUc8RwRWvj80ghrDw2n0hWI4b12y2q0Dq/oLrz6oyo3GbK4ybylVT8E5+Q9WOMd1AGIPRn2n0cm8VkXLb/uoOuAwOHMnCbaOEWp+11Bsa6jcDKMGp/prbFt372hkfS+fckL60B1vFMsTPB4LefmDLXsN0h3kMRucsV/8RCpZuswOeANJh9XgdyQjePRMF9sOP/5d9iC79CEn933N2/B1yyXMHKpWMOuYgmEU/HQb9ODp1zdZhnPTtLUlRfFWUt4eRLHDEOIVlvseikBYodTNp8gx46tU0/CAwgl65GF/Rz3j6vFMHWJHj8DUuw8r7lfL/z8jkpcZKDmbeizJcGv/88OiGdyaYJfHMh/DWEZV5tiJFp8WfLdfZUc/gcVRW+y9FmDHkVzQig3CkO8slkvzqv2qzKAmNPX/fjLZgs5f+5FU4PhSIHPTVDfu/W5vrY1B9vWxStrQjl05VBVrP6sawTts6xmDxWNH31fdsmOgwXWVMmmYxn+L6/WxSHcKNlbMVZDMR4VuFTdBj3lZROMqjcHjvNzkI+SW9nsN9dGHh47knk4Om7x+LHJJBXHpQLMD+xo1mc8CgyF0HYFpeyGG6v8IUv9lI1j2sr/mK1bteWCvPpw64oB4ES8Iplq5X4/lCJwMWV/QlvBC2gQ7ZWIzbOWrsuLpmBQ6Jxx8L2GMCbphnmIZ/a2hTzgnN71QO4/eHsOjza/KOpavm1Q6nQwRHYOuxUiOXSZYIpl1uL29NeyDzmChzcszj/pnzhXiqc89LDura3LTln2eN25/XRNroKdLNqV7EPCtUKgR306kGvHvTqH0CvTk9Vh+jQ8Q1nd32prjqPbhza+73B4gEYV+Jwiaka35r+VArOdrHMvZ8SykB8UA5FscxFV+Y16l4iEDUmU+B07Q/IXuiTupBmzs4ryQfEfHbRjYc0iKY0iMp5aEVdxDd9dcUk2wrsdzdO+4xKmUO4AgdJuKeZk7JCcBAyn1ej85m3AkOU/ojGQ+tr6q7/AAhM3uGRsEOXqsU/PUu8e+wx4opOkixtsjbsq0vPIl5+k4pXIyZnRdlsL4oq0Kc7ovEQE03decTkutjhVEQxfnRnbRVbu5+JyqehTqvvxdFmU6JKPLiYPjuMbUXX4JWi65FIYJ9i1rJYBDlr8fgKmqLyPJLWtiiiGD/uTdIoCaD9NVfgsFR27y4IS+Xw0elyTaeJxZs1/VcHDwJGPwQ0/ScHr0FS1bRhyWHAfHfHpppJqNwde5vhCsTblRz03/70XxKu+Hx13qLq7veyaHagzhtLnnKgyIdxHRO11PB5HwqPijlo1nEFB+VnyTOHrEozmYCtCohgArZ4fNWhovI8OnF9Guzn5m71IdA8Gcz9nyPbkxx+IHZweKbKAYuHDKqrziOB75L8rgHc3ex3h82T/ArCtesLCG3ORIFZuk8OtlaTCfzWfZnXfFZ7S9f91nL3YkOx7fLCy9G9U5EDzt2uLL6jTV/3RL4mCEM4rB1FbW5ECbR/3Rh/Xfi5X0zZ04owvD981NT3pNE+I8clSltahqwSOsweK4cbunlWk7iuuNOtlJ+2/+R2CE6pcr6hNLnFolUHlbtj721DUyMAmH1bH+nEXhffkCCG7HdHbEdpiqpKhZMrdTpn/I6Jjax6/QIqX42091eAQ8S62yC4y6+i3jyC2jYG5WLgChzxyQFFzOf9HS7HUUb9yyf9aZmIUy51MXi7BzUUqIFix3khVmbdSHiFIvf+wmjlUgfV0b1iAiOWCp3xdufrSr2kAnLnuJOmLFGePp5IzynDEC4tdPUuiYEoYmZL3Pt8nTz0yxN0+KWGcolcBbO/MJ9d+br5Whc1WbvzNCMdg9hbhPBs4fTB1MII4d7CNa0/PjylGwsMGdiidmwwpGuLvUrQjE2E8GxBMxYRwrMFUleWPRjCUz8RPY+p5Z5kZwiBJLMAj9E2SEwL8Bhtg2S2AHdwVXVVhI3y9NWRP2Cu8+E0+O0UocjJzUAIdUxKcjGUTShyHTFVDZekOxvpTjhU7oNdhdUF2yW6JV1AGyhTlVjmgvVHUm4uCpzX1RdUIsKNoqtQAeIST4vSb0UzXUtSemL0kAEtyimeFCDu9obK8QyVO7gdb29xhoH3iLkCjz3ETrGH2Dk7WulOhkhEJ3wnhEUgo0gP6RJ8Wm6AqwnjVzdMstE8fXXEBIzZb4Tvk+ob2uipqYJx6/PJ9++v5R53X90wnT7scNmF/Ba5mC4QBPDF/58oAagslvtx8BtcorR+g75i0bOvAnI5hh2rHaXtmve2yIAjWRVUSEsQB6mhvFo6TvJv8uYQBPDGLwsrCOCH//xEjZqWeWHtX35VYh7LvbCff03EEAGx0H1daG2S/mQJXiF4CAdJa4iVW+J/tWLa3o9KUuhBBh1ceGsyk+ohw1u8RJWUbc4E66Idd/Q6tIaeMERIC9CI1FBOwRWjlacZkAbM5Ti8TO+TCin9xiCAy04QwyfaXIG7jxK6cSOWuWOlW0Qi0rumZu7tqPyK1pVcgp4yJN1RHz/+XKFJ7DbqEm0TnEvbTQWIfRtvE3qXtXcvfCjq8eEFvh0NmIPeS1O0q6/vMelxQj63J5pvk3zz8bu0CdCDrubQbHBLfKrIfu0trurw9/IAlD6P5tmhmeeIbfTTinLKfHd0ioBHOI7Lk7fI74m5fse37Z4tInMBKH2Yyw7NPMw1tC1iYb87aO0Kbb7g+h5kMqnQDS/wHBDz+Q/AuHF4NYA/F7tg3puADLcAj92qody5HzqpFMscVmbAQ+zuGT6vhh60afoTIJcQAOA+drIZ3kH7M6jcxdpK8Y4m4pDtWKHIAycQzyWWOdjiKKc7DdncZr67YgM6yBW4BDhWlfS81PjRhZsmsrcGJ5TGWgL4ibXqqDEiBGl5huhr6s4YrEUbVARYTUXL25MXEbOKxMvbQd+paBN1gOfncqkHZvB0XC51oaSqv759VffTt4/gsbzH4fuwB+qWV3DQChDfNkAyKEAcTAbj0Wzokewcr+0Myc6A+xJCkctCNVRVmj0AgMstjBTlUyo5OVGhVOzQd6I/vwBvbrHfHQJFm3yTIbrKCCGizHdn/XpCM+BAGrYrcHIdzvD+VR8TyCKQJE0GWJdRQcynQL/CiMbXqIDrzmlUhBsAbZ85JN0XZ+a5LoYjM5B12OKf2L5lfdlxvF0yRh9nlxWWeTg1bhx/NwRBl/bfXLGAUXVCkZu3DPJBsN+X34E+OQmi523dddUQqRmw+Dzdray6bk1+XSbpNzIw6HhXLHPASgM2IeuKK3A8g0XwYbFY5m4WgWilwj+A9IS7WFhMAVK0pKNlbBOIGBi+e7htYNl09no/mYzqX5IsQ3Uc64XF5WO3GOrPxEervMEYa52Ic07S1QAtKKHIE+cFvd1ISK7BPYHsM2bmEiWV6DYavi1v7x1ttjgHIxr5ktVom0tUN2VOH/NEoYmKOVRemyRt/XUrm9jLVVzlFe9soBOts6LsZguSO6bQBW877aj1b8ryLBR641VH2GkB3ecNTggkl7pwanJ727nZBGadvi+hqNSUZsQXvoSuAHFpg145uy46u0REzpc9zUDOvW1civbnCVHpMTYvPDavDYwJxbrXg4uk7LdfciYGtsT1ZATCyJe4bNgmGkPBQFD53hyPEU/K5zgVHEZFWBiV9NEv6ekLGGIfa/hB3wEXcYMUnoTOQ+NZ4JhH5dH/C2dQiVv8F31Ng3za7oBXNobvblFb/2xwCQVrDd8dPZ7J1wz1qgLGrYZy6fd18nD6gCQycAVO4SInRHbuilJ6/koo8lB99H21ssggta+CcT6TjJgG+Lxq4x6QSNjhq0soRFhCyrUorAgv18I4Y6iuRV+rlVqXLA4IYEn9eHjNNUwr7M03ljYlvYbfX1qLFVEAYfWLKrDENI/cic3Lu325/HBly5nr4nJbBDZbTK8/phl6h/I7KccHW+CI7wKVuJACIIUix/P5traYCostcHLyRU52Hs9yinVv+jzHNU4yUMDFsp9YztsJIAXvirswEWcQeUi3tvY8gs00Ce4/5OJ9eZ3g3CTuvvGnzJwUczQGbfNmBTEpjGEmZ3CbX6JP/S+oY75oyd3EaU4dFUJ/xo+rYaFgveanzxbUY6Spd+g7yqTrFMx3J298WYNxCHyJPUb6aguIkCtwWLh38CO4O59HcOOeDpCRfCoFX/740el8EZUlKiVcXME+Pe2Et+7E7fPwzR7L27reQcl92O9OodfAVeLp62pU0pjGMnA9G9D4rGTqujMZW3BefK98+LF2ANCORL0f2WMs75ToKjyal0ma5XMcrkew1pOh9T8weH6XE1Kf3CflnbiHF4p++jOGozSL8TLTiMbL0FfWnYfFu7ZFHNNXV0yyyLDf3e2jyyJTJnkeylycQecb8dmw4dtq2JDYWDHYcETjwYaaun8sNrzKGiFdYPdlLye3Xo9t7it/FcpRidNI8SUiNp98VkYUa+fs/0CP3fPKHKbpqxMmCYlLfSArm3NGNtdAwz3x8fU92qLPSYmpeymMiTlUHhxsqD8P+7aNCkc73aclzeg/EMP1VnHQbqu9leSxyYLrrXVvJTmpHJ1TV1UGupLZ7w7Y6Cmx7IRjPjs4LosyRaQb5N+jLKN+PGEPBQI4uL+KSryZ0X9ycsQVFzilecgB/y9btM/d7dt6mx0XG2n9Zb+7HIflNRGc4Ub3B1T/KMpv4ukYDOMULUUE+rGVxuFdPjnuG4ZxbuX0Ib0n9iNq84vrG1OBrkZ19p0KDVAaxuYTCKisulYlqnts0u+RSTnFm0d+Nyrs7wpSCGeCGopc/QlEX26TusZiDni5dDlnl1JAm68Zru7F1Yn5vE/FuqY7FMpRFzSb/2n7hJwwL0KRA3fTJ+mmNKKgAaGCcWzlQ7N9g1KierMKwM+V+vS/jdAy9J+H8W7lDcqLLc4TIgPaljg479YuG1FrgACrWbZa1dB/imD492C+9r+y+trdI5Edf09kt5iSlYCw83VSfYsTni1j9DGErLDMw1Fs09IECmVOl7KaXH48hvlsj+t9kt7jHMmsyhU4Bs2CKydf4rABxXlrYwAohSKHXjZpitAG7qdQ5iD1ZSkuKv0nl61QcUeDAS4Q2VXL11OEQne8YAySVLhKnRJPlwQqkaezozrKcCIYbP0nF+u6yE8fdpQ/5Od6hTIHX670lKvrM66uoZea1XX3MQdklytwmDX0UBNFLOkV9ruLrn+LNxuUi6p++OpgmTY50Ru9WhdsUr5oNdLPPZMeJv4cKg/5N9SfRwFwjape5VMCuQWnKqODpMK1bfCeaITP3xrUoE37HstRXRPZC79gBqL0YHZLPPMwPdO4iEgocttBEcOGuupkBpcKXQRUvA/XfXGxbuWYouGbg3dJytHvmp0/3NZ4j7dIXtWnrw6Y0AYn/ayItBHL1ijO0YQ4THQXW6VKTLb6YjKc6auDMJTFVhCF9ouDWVkIRmXhduVglz2KKMaPDiaz8Kb4idM74sepYDm2H1w2t1//gVJh6zh+dOhHsRHmtPuyT8f8FeH569avJJxsj5/dcEH7/+mzw2akXStT8JlUscyph5v3Sd4kWfYodZIpWY0SZIcapgVZTB5qUF99Jvex/Oae82t7/RGYvBpzBW7RFXJwhZN2L0rRC9V+cbuSlEsDmr66mFtVJV//nL66Og+uKnG+ps9O43uDbpMmq4lWI5tyemm+kgYLgaxGbk+S7S7Bd3lgsEKPxSdYQVl1ra61n3mVfaI76t59fY22RFWGhnkLyDx42ohhray9DiP6fbFBWZfwgN8BMt9dLixUdVezRAJ5hCInQ72zM7r7m2JHgeKnqF7iXRqZx0ieK+bJ3/hWnsaMvrRXkNsHKPbB/VqP+3UI7j/pcf9JjXtPS8IH9KN6h+oalUR2Rh922MoA4/RYIGwRzbROgK3LDzPr4JbdHLldVV/QKREpT/mXoqQvk6nu1gHFq5Gzs6JstvFETELnIV0WOOYRLL04hQhR3EQtLYGks4fho4u5uMOpiGf8uIRg7ut4umxff+7t/MDzaQ6XzwG1AcFMvB5J9VE/v4q7xTIXzlTh5EuWN8tpNnahR+0XpyBAlIhz139zwdKnjDl+PGrqezEEBij2wn2JUrzDUjwbDPETa4z3KKmaEnXZoEP9EwwqL++Etv5afRNzpAG8pOf+0jUh7OSteHIJtfv5f4NJk1WwwSZi82dIDYoDT/7kPHm+3RUlfaXkFocmOOBQeXCjof5aWfGsyDZQAkD2u1twEpQVmP3ueikGwseXLB8C3E32JaJu+o0cSwUUOyiHb1jIktJ9cbAVk29iqHT7xeGEsr20+DEXHZnsd3tsRJudYZRt6C/B3SoUuXPaSZHf4rumBML0FCAO3PJQl4k8u8xnB8uzRTDcT+NNT77I5eCkarL6PL+Vzk6m785Xj0gfNJePmNLVLAJXj3ka5wbihMjnwoeu9jzq/zTa/cOroilTJKVWYj679kpeUtjvLoKT12SnK6PjClxH+jap7qGhdt9dHWznUpb56bMrrqu6VNzwH0pcMR4XRQbh6767WK15Cl+AYwtWoxZOH+gi/AbtsiLCUywiNp8QMyOKebREb5PKiVHGz0sanLFMsLiL3zQrkLkplz7tYLbQ5FM00/h1meTVFrf3+CCaqWDCWjG34WpEdhtu+V6GWObk8u12T5LTd/jsGgEBh4v4x4q0NUHPNF+y70gNytv4O3ovpffhCpxkUYrgHL6tbN2K4tPgUHmvWAefRkteogZqorRlLcEVujoB5V763cgkP79jMjrlpU+g3OVkpleFPSsIhzJC4fLGQMeoSn8MULzkvljDWYRpJGuf+ew0/1RnS04Q9rs7N3WuE5mgUPm+TLePt7cVEpax4ZtjUCAQCugUOpnU6f0V/pcgH8xnhxkgotpmNOXpPn7d99J8Umx37UmszkJRArme2P4d747K9F4K5pJLHTBnKMnFhNTjx9WYA8dJ+u08J7OefosXLqVA6mEiWGNadzjJRfdyPLBHHj7vK+jq6YUsEAG6TdoEumWkmxUARp/zYis0azVqP2P0Q96lTl9/4lPeEzJRd0X5GIebRGxel89MKA5ctDou6nV5HCYSkHmFXxowHFhodSzU7yD7qQt9253F5fWyux7BbIcJnUX0SmEpvfLC9lqB7fVPzU8nZVFVVyjLonCUiM1nYTOi+Hm5am9hlXkT48W0CY9XKKW68kwbt6jez8EFSccBOye7Epd4+35dFmLuh68Ol7Tw5g7Vfy/EHIrsd4d+4Vr0v/afHORj1cn5Z7ue251qXRdHqfjMDVeyKs1Ac/DH0g4TLk8NoUMwj5ZoW5USunXfXI4RSzIk2nvJv8OVuPvHZf3FlziMlOYTEAbafVpWN7TP1StTe8qlTh7t/pie/hbkWir8WbTZMbrDOfDyIfvdaXZ+nD7sknwjvfvFlTjsB4uzIsuKH8KecPzqgAn9+ILzjYRq+uywnqci53VfXBzDj6Is9J9cAlC2UkbR4ZuDA7eqTrKkEo9Gxq/LrzdHVVWkuA2Hlr0koyB2mv6Gfa+Y9FbjEjHUlBwgAjwLDqx1G6U6gZq76cI5gYXOapWSkENTTsk89iq4w9f0oXJoz2bVYRaXY2d/fQnygz3LDG3fMAd8ugfKZGj5JbIOpgex4AYZayAHjAgjzDzQubDZ7vHsbaKPcUYjHobpsZltsYpqyl3mmscZSFMBWcxpF1CHMaZzx06KfIPpfD47rz40Wfbb89skq8QYBdPog5mHGFBtmMHN0W6XYbqF609RsF5f6OuJbDRADyc0FuykayBwrkbUEbhJ283AxaMn1tL6RB4Se0TryBVCVRVjsGBezMG1s2r+4HsaxiIsrv2zyegud+KQqZaKORRebytqD9hXzRJjJ8O4oUezNCMMzQ5Hp8mdfkMCgSuOYScYm62HjDh0v+FFUOvORZltgmlvBqf1JlRVQ2VuOm46YfRr3Gr8LJvLS/QjKTcXBc7rqn+L8eZThTZfcH3fO+N0Z+zGyvKpulTFgi+MDQXOAI8rAp+YO7zGXYqJDPEUztU9br+7bHGlOjH2uALSQD4SscXUOCLuNTKQefxmFhq8q/T+eYJz+gweDzK6b/sv4+9q+HDVPUfUHvBUUz16l2SbtASpdklKiUsgznBZ1ZTTviYV6kCePxuOmYYD8z6BwT+zk6zLiTYAEMMd36Kqvi6+ofy3569/efX6+bP2SUealji7ff7sYZvl1V/TdhqTPC/qdui/Pb+v691fX76s2harF1uclkVV3NYv0mL7MtkULwmuP7189eol2mxfitV7tFZYfvnvA5aq2nCPEDFHnMMRF80S+fyZ2Nxfz/MNevjt+f969r95hvv1P5DEKQMHXaLbZypm+/WlWPFXgGFpx357jim9W1n/HRF2aIOyuutgFAq1Q3j+jPIkvdc08uVLLXo2DqJrJv+elOl9Uv6XbfLwX1l8ddkY0Q1hED39Ooxf6fUWx34N5x8R+kTzik/HIDEGmadZs0Hn+RUmQ012IeM8r4aL1aSgRmmNNiHoplvaEcbZH+pGwNSe54Lonj97nzy8Q/ldff/b87/84j4VdVlocb7+y198GCbKsN+jOulTrMXhPIqQOxaPhDPeTEuBM/68fImIsi2Pqi6AKQQTGwIVYYwdui9lsmtzw+Sqvtnjoqfq3Bz4j/K4oEZroBYZn8hjVh5HHO1wqJ8jiv7gIqeCtO0UrmGLhj0G1xsNycN4HvAHMB0go4FTvX/+5RdnpHz8iy37WU8RsbTr9sbzYXqIje4+PZ+TrDErUTvLc7g9GX2S6YO5Gf4XGvdwf4TpnrKxMY2oh00r/fXZ+f+8kYh1Q29T07ev/+1ZK4V/ffaKEMm1O+xjENE79GefDlG0NMVcHwwnCkacnv3p2f/WVuB7QYcRqC9HhHON6HW0TnrrDnvB75nujyDvRvX+ymeiegKeNBk9gjUsH87oP+X4nw26QsVJm1BJh9zVQjzLkrvzLen6kCkn8kbzsg4yPCNuizws2MXNrE7gu0SSl6jqPLR/AKEMWPXGmqPm/cVjlRuIPYvxNyCPaASeV/QRlYusucO5gp/t3I7XRZOqZULGYc3KYjjtH4GNrdzBge5lnZPOCvW01Q5CbM0I3BH+H5QJgiftrERoOBwLWb+uk4fTB7TdBbkFCZJ+HewSeoLLoI36Gd76C3FMdZLSMZc/Hj95C1GPRZb9EaRh7yt7bJ08Pv0SwT0bxSSlPvCP+duCJrm8CxKCI3ql7PcGVTUxCz4XdRAyP0sZcnclJT1eR20WrQ4PfeSixnRe3eh9mm8iYfLelzgpiKO8+oG0HomfRU3Q0bqriK7WStTDh2b7FZUfb6ngVCEcP/MecwytHM7gfn7uYjP7uXHYVDOIy853XKCa3d7SbgN3tNuVxfewJYTPVKjWjHbOqvYNIi9kzjz8R2Le7m3hrqGmdQjiFuUtpnRwnaTxfQOtt8+ZH/snwOMiVQXnxMJ7VpTbpHY5UlPjukqyOnY/jzZbnJ8U2y0TMhEYbRZlH3h0e4szTLg8jHThu8D+0VkOhU4x+O4y+0zMc200tV0OY6F3SVVrFiGHlfCGQyX07JV7z/Rrj0vHRkxex65V/a64w3ms/QHB1/I10fhKlK5UHxB6jI+opWaI/I16vmMOTvTgiTak2uyMee0coUipOAfeMwLtKtPyHA9YxJNj91mhp9FJ7ne2PclTh0TojUdc6ICx2yEFdorDFa5+jnFZ379JJl+Hn3iOaHjZtOuBdLXfbY8iIVBtVf7/9r61N24cWfSvDObjxcXOmdmzwOJizgVsx54YSGKP7ST37JeG0k23eaKWevWI7fn1l6RepFSk+JTU3f4yE7fIYlWxWCwWi1V6EcfDFxhm6Awh6OOjfQBod7Ljt//lqnvm4HSplrYLgJaoZitg4DHECpJEk1rCIsZjZhFr1nZ0WtkEPLqlz46StYU7vtfdBZMLLkTaiaG1FvjVJ7DfvAD7F97fpnkRxVDEjt1dwVOaIHgHtVu90YtHaA4eJH2nT7UKTkHnj9qoNkFq7Honr60F53ui3Mt19HP6AVFmXecBYtsenjKE9OH/3RQ+WT8ow+sebKtLruadxpfIybswY4ybxzsyqW6uHHUWCkv/nQO/y52Cpglnk8yo6L59y9APPO7usDgvHkIU6Xmcbqn1cQryO3t8id55SguU1vtZ/R26vjFwU7z17WIN64J3f9vsCZ/SwjfILs2U62PJRUaaqN5Cqx6YLO5ZtCuyHs2JKU4rt3WuuhPQwTWpdATXS1Sa7p898WoLJ7lB/IJzTFoTluMfeFNGcfzqIjij5orNK69h0n4fx3vS2jdM7/fQjeDUVVvcptpfHGQDw9dB6E2FC2ZJ8x6AmOjo2Yt1chfRx+n35c6TaeIFXgPsgej9uEesI36+QPpKD+Fxa77/XnpfIlGXG5fsMIX5NaTyJa8OCn8UGDiJBhzwOv8DPxYXUeZ0Tm1guO/sd+jfJc7QTfGEslshO7BtjhsGrzMS1IqVHPTNlVVJJ6fAa2o0nG02vSGd0L/O36XPSZxGbm6EGobb1HxO4mr5NuCcKPvYXCvcPA7gWQVD10AuX/Y4Y6vkXfQqg6gzrQ1AFkzDALpL9/sov4+IDYN8zKoIyeKirtff5aaOEEYjRc+2GUK81WdDlwDoAb34CmW8Q+syyxwvIlogF6/rGFVqw03f8fBuUYZTx2XaQmSbPwPrtLCu2QXOZUK7OMkrfYrs53EiUbIsQ2gUN9Aunmg61faIjtZ4F8U0eSb5V86yYP76T3KepY/myTZpgbqXONjr/DJ3YiGXAMtNSIipQz2ayQ+yxAgwYsg/ucodMenW3/8so9o14KDJq9MUg3f2I8KkL445mA6OdBBHq90LJ97o/ZA+V7TWUZtu00Csf/z4yk7gV2nW4HeOWBE4e7Dn0fo7S3xHc906hnHTwx2Fd13xkBwu2mOpk0nBti8yM3hX7nxMTAUvevEFr4FxX6C9OxyWtzhLYwrHySbBG9RgVoN0vvNHmxoiRv7tbbKWaePz8vW8LIpUltZCVy/Q1l9x/hTjvHAHWCusGJHFR3YfwSVk5YEmhwoGCq+dXFUCAO/74028CTtAfZa6YJeVgca43xM4UWxNiNbVETdGe430gHc+LoB42PW1kifIjSfuMilQljvLYq2iBagosADVanzSMcmh6AGjau06bWzEQkB5cVYUGf5WFugi3X3DCTvWBRVWgn9TeSivKw+5UPEV4e1TuOUrnsW8g/+KNwGhvw/Lm3ZX8q1zWsB+FY6vCxU4CEcnYUBTfKkBsKqfDq6aJ351C6ccnobPEZ3w+83hOWN7RR2aiX8HieyeQnqlzu6FpQ12v7r76Zfy8h4UYvwDZa906Zs7IcXeLi7I5gzzOcH9WAANPMTeTo8W2GD5Q5Thx0fZFRIfTWzxaI9FVN483mR4ixPrkMwOgAu951GOapvT2UXYwvqIorzMEJ0NJfPMXzy2Q5zt+IAy37ZTOwz9hziUxV3EeZlsYlRl/Acc4q7KpQJ/i7Jrorx8OGgFgJQNPuHdP6WVz5eYMm43UDi5xey6V+rQ0jSTbzN2x15D00dKO0isscNPIUpsNHbKJrVqw0D3O7zPORXENSHOMXqoLVY7gObd/m+Gstd5mlcASwyMre/jc7Lr7dOEf4Fj5ZAbQPH1YqCZJBYkw9a2m6B2cKhxZXAHqP/Q7q0Yi1tEpzwdhNuG7jvQ7zwtyPT6hnr5Quckij/g5Lta4VuUGIs2W9BWsqOfQrsvXrtIR7uLLxyNXocYHwbqNegnNvYtArXxHLEIcz+X7bCRqeNiaDq6nM9ogPI9/ksmucbhuPlDeo9itC76gC3SyjcgbsTLX19JD9kB7C5KtiO3kBby4TGI3K+XfYGhun6eQgZO3ebL5bxkR91jVMbFF3JQ/WiXbEL/TVNzED5+u7Am9RwnUVcqgbD0G/vBblMjs8tHdUh0ltsVlo2jQ7iksgBwj1JaPSgZs3j/bhPH/gk9u+iX6/whi5Ic94NXNbbpdpWuOCBOF0Pqde+Mks3z5Y9og6O63rm5JSP2DpDnix/gFNQOLRM/rm08vEs9JVUOPiTVvwp0TBAQ7piwhFwT/OOqUxCmIP4i78dnblLeHAh+HQhvh/4R3A7v0P92Jj62M7GT88X49ra+idqdSDWVhmyLqKC2p6tJ0+f7XRkjeeCXXTqgPQp+s1onY4STsJl6CJs3cz6A3aG8IAqXaUW+gKNTREUH9Fb6bNtqqjjA0SsVh+rhm2/gDYclO5wj9Hr7ceLw5UuRRfwZMoQXkI/tOwV9N2ry/8Pc4r9I4zR7j17AksCuwOv9vSom7TnOzpftcJ3Xfmtti9PUw1EFl9HIslMQ0pmdHHUkny0Sve5OqHh6q7wOGUYqjxGbIiuZ5kVogKRkOCZi3j54cjpp0fetD0/l7lvCpfq3AVSniJv/1HdkpzJ7fd0KSCUvp6C+O+JtlWfV10lztnx3TATQgGHGjRusaxqDXbmoghzha9k7pYjHmS0FSeyVzvOWtqdbLdb8iijIssvktTA/m6nsntoNzMzyy7Pb4sJc6P0mx9yEsjwHj3jN0G33kDeJDi7RMOMrJ5dF+jklNDf7hF5dVTah/OGZfpRnEwkhP32Fc4joFyRRsPMU1gZMvy+xDFC9xLZgw9gbAvP3xL4OlNVjZ8FT6XgR4ij9pyD3pyFauorYdXvwaQ1dRWtU3KdZwQ1iQzuD00TnvMduRVAYPdWuQH8w8KyZmmkP0fZU159hnJcpZ79EGY4SMB/UKXA8QOZ3OO36NNnkHcPMQiYAs0+ppQPdOqOW+VuFYNk9DimJyFme421CVmATYBggNedbLqCe64Ul9XW7M5r30N9dwv2/nZ9K2d4SJzM77qYsbh4ZSEZkCIOGl4dT2GBDxLHQ/97GZRYNZMg5iEV+9+wnhOWgw2MCmJunsARclW7z/5Zpq48RCwnstPKKFwGn14L9sayx9uXuonk3yE9ctQTn0h1VSR2nU393XGrzpmtzyuNdyARCNJ4N01QHnNTiD5KnxZ+fznu1xmncYb52vIW4w4awLl/2aVbUS9Qm6Nt6UdZx5vfozTy1dqPMuoU6zvwpzLpc5t3nioflePE80c7Rl3dPz1NtHif4Jrfyq23+h0wjX0nOf0J/5iGcYCDL0MRwOyi7oQXMX0MwNG2fp+rb5t4g10NsfUt9MnqzT7+10gQZKfvo85Q7tVWuafu+KdsTU7YTLxWbyhYyBe/zOHcY2r+rs3v8mr6h9Y8StwOVCf53iTAD+YjH4r6NH/3kbfngz5nT2yoAjJNPo4HnMy0f9RuiKoO8r0duYCo861zKSe7LJfSWTe8os+kBYsNUjTkGTb/6H/R3J350YIYWjiM6/eJLFlYSXalfUAYtL9dSPk80UVGcbi2WrfYm2BYhPIVdcOZwCXVqDj1DX55GRefxm9DbRxCNCy1hC2xeRPG6jBknqkQuASzIqzQrd7es7uzxL56HdI/X5jJXd3N77lnLrb3Ee7i09nNBd317ttlkKPdf60H5Yt+rZe8zyZppFU5vtyVs7TLhPIXFy6g1Xz51t65K54EvYzrdRr4PUY1VXVcforygWp/GoroZBFU9BhOztlcs1Cbartqyck0OWHiYMHo2qCgFuI9q/jpeQdRQJFJnCa3KV2ZxThwVoF/NrbAT0fka6mj86G+2MZzMnvBHlpZ7y42h7gv7pn09hXWv7ug5JOpTbTY4qThQCVuoJMgoP3pt6yma2ouY2/gcDy65VseGU9CLB6GFLKT6cAUVumGUZ1v3yQFpxbQJTqifiBl9KplHP0TJtrS67+h6uh0NoToPdi4elvXSDyhaA9YPJC/ZJZdZw7kqRpHuaOyIU5D22X6fpT/QpoZ1oXhTq7eRpIVvkB6rTHhV7ida2kVblzfljM/K4omqyCoZzR1aE4adgn6f3fV3ueMy/HqpTH3NBc54BFsbdp6h31DBe0i/Iz/Lh4E7W69RnvsDSv78gckEO5Xg0F6RteV8/EuPEQonGLGbewpPHUc23734bPqlLsJSX+5Z5F/u9Xe7V6hrjFhjMwDgEivAehLTsSj7mFiS5QNWXePFI6gqysBz/aBGOi/KLEPJ+vXCrjgzBLgCeBcVujEY/7RelQ/RS72tAZdvxvHLkmxJ9sqMHPILshbi62QdE1SDhaQIg12+TDPYAx2srZk1EYXCoNNQWuuGaSisB5uUMjKQwWI1H0xQY0TxY7o3RPEVQqF5Kh85NIPlI4fmdg3fT/0vJifBBdGlno2uR4LMwHkUR0nAwMCKWVRB3RFqNlxag4BDBRuCHD0IEWgTuMbgHXqOss1tSnbs/CvKEFkrbq7Eiye0/p6W3Rsn3x6ewQDecpQ1Vo3EZ23qdHx8xDF2rq3cHmP2XmhkPlV6LqO1INmyvCDzLxpeVtNOoNDefiaCouTN0h7Q51g9O/+ONjLWOWN68ePHb96AXb7scVZFSqdJl3bTI9z/RpEf2nm5fIeJciveISaH9iLJgTlbsw3ufRpvPM3VELhHQeCAn0fJd29nwx5cb0uMh3t94RtkXbLWN9jrb5GnDanW0MwoqO+D/KyJklivGf6LrTT2BC1ai2U3goD3Jm6yAe5QzuUudNRGe/rI3j9zhoA9Yk0O0q1N5B/125L2zZFv//BthH3d7DZHX/EBjxtPa5D08EUW4b4suCdCnp1371CM+pkGTiM8hz8u3KFdhBN5nQOtjN0RfXRcn9Y/pUVbzsPpun+9Rvvi4QkTTCPyM7sifB8lm5sfJkaufr6B+vz8OSeHhveYyMFp1ApsnY/mSb/bnk7XIayr+eh1N7cnL/ZLVFuu/sCP7IhxanLV0G0+s11Pp8n9nKPNV1w8WcpXr7szKj6LK80ryacgvY35xYmAVeFeGRyXG9tmHtxv6wK7Ja/zBlVWSyFyzOvUACNHyb3HA84dIXZP04J4syxbiP7Cmu5RQs8BvjCswPlD7yPKc660l3PO8GZGmC3p6MqeQDO2C/sUVGNLrNcooxmtwNt58474SNZB65uwhB2hL4PbgYLf/U5BzSSUhL6rbo5h1WYemmXiaKF5F+z+0f+9Y5O3zvkRwXXegPJiNX0gIp502f6kBqPW1ky0/tew5dnKZBOjd1ER+Qpaphr6giUBCrUi5JXTTK81WBAeDy+An6tyKKenUQ9kRquiYvGIDOuqXBacmTZ3VEs3h3n/8gl5vGaPcq847hQszSD0YrysDNI8eNCTzzOn37sa3t2zcvQ8rkx8RfpF15/wvrrhPv5lOeMe8JBF6+842Xq8v2XximFtMHYJi3zdEjfmjCdwU+xfzeo4FY9OS6/VS6Sqp4crwKGDPbgnRVskvkZxjIoTMmagV386AlF1W7XL9LBTEobZO7QcFGGNpwp86GB+YZRb+syQSMdMd6BOwmuTR+oORXnnrbLwB0gMXOdS72ebHU4kIY6G1ckMjoRFmSW0GCw6jXzMHl5Ne9oVZ1eiPi44qsV0lWaVIPlxrNTiiJijVcOxbANUN3DP6LlxL5uO21VwET0+UmeVH3AelQvEW06L+HgcXr3Pekgr807iV5vegTBB0sD7p5RF2l5E2emcc9x18m2U1QdDJ1d/dc1hF1LE93WxMHkJcA8lmn2XmfkWP9xDRrJ0UUbrtYWsx+Fjjz44HTh4hHoKShCKIVOlVdNzdKKXgvy02/t5WkNDwP5d4swxHyL1Y9L29Qr3AfM6f4heLl8QR6kNGALkgkznNs36Fbxs1Q4tcZelsbsi91Wq+Dpn4QsGV9hQ7IJlPkV7FXAy1YUHlFvs3EMQYRPM26imtzq5fle6gctnXWb0JXj9MOyEnOd90s2X1hCCdz/2IgNHRKpPQVYuXtcxqhSc0/RQMLcow6k8xE/PNKEXzAyaUySJbjLqyWwJ6LmvKRoJLnAUW14Kib0X/26RcZz89IFWOD3+ZciRC9jQGtM7AHDYN5+6KTCWKK/011OQ2SohQp3Yfeya0L81blNo5TKhjQ20sPb8n4ieImR+QD9QbFGKI92uWNf//dN1/pm91/o/P13RMa1yGKdZoXNB3b/m0YJOC2t4fVK41ynr+ptFWdeAalovm8sWfc4G/mZx7f/D5qLtEWUZykLA9uomJkK9HUZW6q8H1h1cEI5uh/dFsYcT4/QUsyn7PufwO2CzCsX622qTAPEUlKt1RnY/ieE9HFFMz0lGMbF8sqfjF4Yg3sjJCs2ZHm23CeHeBUFj61a3KLzL/mwdn05lnop6N49OBSOIODdWzF0a29wfCL2dVOc10b1xCL1HLKA3aVuItN3H5dY7UC/3lBa5pvXTLtH5xeuTClLwIYiEhLoormeJIYCtDhz9iVwRQB5OGkJiMcfamKYBctpC/PCEduhLlGEK6hQkmBFsInpaxc0NtaoOTFB8+n4RY6jhJKl+Q3L8EhRkCx1z6Fhty3ls7NYFoNC731FnloWAX6XZGhEcyf/P4ph6zJwOOO/TfBh57/wm50O6TW/xmmarXkb41vtiF5+nG24PdgtmTZOCrIPmMfAnVDyn2XffU32b4V2UvbKV2JRQswkIhqA4BiczkJcvhMpki1h6a1f8YGAGaOoHydXQ37QuEct/2JyS5eUBRdjmkUYhCwTWuuRDSgEYMEX/5oTo5B2ZMi79uCfwHitxe6nN7kstLyXGHqAwpYnkL1nJLv/7IysG1j6IzD3YGwzip3L3rlo1TsFOHXYsesoXdh3EdyhJdziJiDAHiyvtDXlXdmvet/n+MWJ3CaewnyzdpRbunLYmKpPI0EOUfz+h0F+ebIv0GUJvR4/4XZnwhT1GCpy0OHyM1k84QfTvVQvEKRMCB9HKcabG6D88+NBYtKpb6ggGYHWFE2YrOE1dAyQAOr+ax5jcl+s1QhtPb9gvsywdxGvY5vbc0kv3W0TO99KUqyaQ/CTcsNKQp6AaNY5W5iv3LMaR2ty0ClFKk8uXPZUJ4KLe2Zit4lMtFCETHlHb/dNTEKbG6J/QS0GU76ru7rQlENna3ySCMrAxk2uUbNWkjCKLSLzr/D3ekJXhZPSXCVFm9W4X4C5bqKd9ChpHIFhePs5PwKoitMc98Yn3A2b4OJ0/S1SiDasxclYURKhP5UUXR7j5CUDo7GRFUrKINUO9d7xM2mU9GdYddt4Fr7BNzFDVy0VT88nlCS3fcBJlr1YW6KgusXkh8ZFsXrCB4AoYbXBUC4U538XeAVISc4J/CkriNsNp5phw5SpLd94N6ofUO8g7tI9fzeBqWemDItWuEM/Xa98g78tv/4PW6lRoNtqT3s36uZn1eaNwT1bDQ4Ydnw4TIH58Dcz2WFuWNxV7u23FKNl8jJIyiuPXAJYWj+kp6E6wbp24P/7D/Cq1vqIb39RF2FoFkrn6M97wvU0zmdNL7/YvJ7NgSKymtZjnoy9SbQBXx577PHY5+BCy36HHqIwLovmYBHKXfz4zC0W7fYS3ySksSGjNWEagwtulHTCtPXL6a/fwR+/akf2AdkSjnUbEeJCz2OHYwh/TDWLJ37z7wj9EeVFBz9D4Ite0siuLpXo0OYKyxavsxdvcPt6cKIzbqfSRL0MXuNVpHYa/Am4iM2Z3sH7zCOvvhrC0dfcn9Jx/QFQdEjHmPMonoMJByr1WGZacHLSuip7zeIBcdUhsCkeIN2KOxq67hvB7srerB2PEtt8sjtZf04xW2Ar6Au8qzcrdqa3GYGtw9tRWbDpdk+Tt8Xr5+RRvM1Y/uI1rOQGpdU/xT73q9jIq9narHpjao8H3dXNdatjS/2nxJoJmDPdzNqfJwiPHpN515pXz17OyeHILSeGA3aE13mMuNm2pBXs/oigvM9SUlTx+NTHqLbBJmBc2Hd8dnYrQT31C5YKuBewdkdkkPxED6k3GppWx690+zWgVikd8GikGggjYVRpvzBPMaYa1xyyyxcdjGB9wnGPoKom7Q9THvuHDiqxcZ9/x3ulIEn1HLv2rx4A3iZvjjiyJK4ziDf3L/yvARoIu0uQRb8ssgoItrTyyly9FFvFT6PjaPS53SfvKzQPEO5SXcXGdPA5uDuyK/lVPDwh2Tu+U2v4hXp3fvybrU38oqDMVHZtW568VlG46+NSmD0QwbE6AaZmtkW3SIxG9CpYaPTG9rCUf4Q3MlZ2/DvHVJRXo6ryhsqQaL0UYWn+zpxXo6m74siHeR7k6EPk/LRMRXDs9IKtg3BeZL3VfATxPU1moi5aSJ3MU2HFw+UItoHdoH6cnU7ClPmFYFZp8VEavz2f1+8hY4NdC6WTK3dTXinSzSTekE+hmAVcjCZYxTJqP/CGLknyH2aNGd65CEJ387mRtVO6N0ecmFjy9L79VR1vfgA1CWWzmjYHXcsLbou4jhISuBPwDfeQSHFnGZhpFdxruU2++oUl8QzYJGOjip/+qKfe++rXffhrvAVn6AxOuhHxdep3XerGRX4eoMfeNvlpLvrxe8zkiQCEkwufLhid/UqXty0PVCFrl+/LmqvJstt08PubI6VkCi0Z0AXAeFeune/yXkzFyS1Z0lWh2CRGaF+luz+6UzYwN41Mnu2r+F96fEXCuwWcxipIu+bbHvfw8Wn+/TsjsrL+fXEyWe3TLbVUL3er0WnU88LqGU4RTEMl/jFgy3+ztDYZrenuMnk0PhEu4jr4gPN2m2eubAJyoANQK823+T3T+6xNSLQanMP2tifCrFwvlNxcoobV7lub5PYrjt+mdYHoN4gyTsi7XdRw8V7mVtLL2JOWqg7FqPEn0d68JVHnAwpnPF5I2SQa7rdfvOfkr3mxR8a80GYVsnhoSFyOp9m18lGAGe92ZaXvbVDT1lPZe9URVl44eDPu3EA/p2Tq2E3IOgL9AKQqaZsU/KsXHiLLIUkZ7uZVqiTKCMuWn5fCk54oHYlUupvL1mt7iaGcK8OLrDaKrWGF5g1SaxroQZfV9Nf174EK1c8276NdKXkLoWBMEeCAWOJyjLU6EaotWl0FP6fPlyz4iaDrdUH1Kr9I4Tp+dYKDnrzjZuAG5Xo9njzZeIMXryKozrylPi30NC325YnqR5xdxlI+Ggavh6vtb2pVdbYR8dWGbTbEPbzXcJcF+/LhAn/GdFRzY0Fk/BOHFbd/SZOe7r7uHqVFWDVFfjtlMeANCd6LroazmWBjLjpmuc8qjb4ZB1zPETJKtiF2ZNg50m6lsYGiv2Xosq7kUBjPjJNfVbX3y+BuKU9c15Gzyd2JTzCg/3qHO6oAGQ+NO7B5ydhsv6AQTWw91qHPKo291AR9oJmvozaVVtLUymaRzo2pOBrOzk6ZlpRSBBn8rHKrOIS0iVzvY1Cxytn8XYBst3969JSfcLymtnDqr20+DGa6LjBB6luTPNjFDfN8Qk0Czm12kO/by6Vj5X9MHOvssH0dXAHUSAGlV4SCTYOeu7XqGEI7zON2einD4mkvKs9s0twhP7HqGmEuWMo6CJyrXahM9iNlsqbRIe9Z1dUKB8dd/zHBtbN2hHxg9v0fx/rGME0s30UFMpUCwtXHadHdC5WuU1xwPPbHHPptzBW372/urefK2XdB8Vd1rHatHrJVk/jfKWWE9D6A+pYaQZLJ+lufpGrOZbaIgUpp6q0ozcIdylhFh1WS+7Qn/ZbL5iR4/aAL3ukGN0T2KH//W/fixjAu8j/GaoPBfP//6c3/J3CRVPsOfzthbJuprzNfRZsgOQsZGigOAuYgP2EDE7X8NhiTLGGVVCY+LNMmLLCLsHq55nKzxPor7/Og11FQPlNIWZP/LO7RHCQ15UNGtMy6fz3g4fjtMbwbG+PH7L5xQacga/ovFrzLcDkjQeLSHUiZ+PQ4RE2g6CPka3FDyPpN8dQ8sFW6a+72FWR5+nET0lPfPKvzEhkEEcsCSCQRT/z5eMr7eBfwChPUhyraof8jvBEMqCKqJP0EhNRaQuQV0xNM9lXCmcXwYmzPFVBQy9sPBb8GMjMPYdVuH+GqItOlMBZSSCscBBs3PYfZI3Vn0IC01IVq7IGk+m7zcF1GBWDRyQo6aF/QCfBCQw2109Xdhj2t+m0R2BHwFPHpfwmxgEH/CCJFIjtZmVSE3myg1QWMeheg//va3Xwcz10FqQgF5SO1vhy4AYJzjwqdeIbTua3hxwmC+RCcUCQG52QSjDddoU9qMHfybHuA5aqJNph9qDKESWM80BE8gVcrAasmQ6pChmSRr5JRuoCCOVq5M5ngGsZIH3E8tVec4pjnn4McGVjI1sn0Z6b3DlQatsUTmzy8NLA17UqxgIha6edVIg6i0345m82ooMtm8ZpOrJpTpMJx6DbYCDt2PB+/ca0k5DAcfZu89Vh/RBkf0Tb5KfIRG/PSJH4w2qhoB0XXX/BZEGOSkhhGIhhqdoXjc5pOJ+mlHLRog/nYzGMoDLCAsItL7FMYTbDDDrsIk0qMzYt12MeIEPv0aTCY0i6ciThCHliNOXWzhPFZ08/7Uo14aO0w1L4sF47f98dB1CvxwWjb9M2uT9l3l2Z6wnKZnqtHH487Apq8wj92PkyiXwWt1CJfAstWSPIFwqV/nS8ZUv/GdXczG4ndM1MURi5nRlM8hZoosDhOJmfDkfroNTUiuIBzkhA+HvrHJc0hIxlve5saTcFD7m1TE4AZHs88Zy9wS9zpR6ka2OytlcgLSZywJc0ngSOqb2aSwPmkelNqDvBeDb0ej7Ew8FUvUc62Ejai4+X1S88vXhF4pG/G6VaUSmlS6mn/coX+XOEM034H8xn9JuotDGERH+H40OoynykSPze1PbwJjbx5vMrzFyQQBsgZq8PACZE2UTY/1s4sCUQL4B8peH2hRMuky5xsJ61v4sHCJkJM6v1jwuM0tE+dlsokRzVZ0VhQZ/lYWqCpXuuq+jNk7XEtggvmvU97LSSlTIzloHNJKkvE4qIzKadXBoeu9HNGtZfVgroxtF8zhmeh2ci5M54LEzKuAje6Wb1KiHnQh8tHeDkkSiy/rwq+HNCRZR3Td16dIy2ivGy9Grg5mW5tRqKZXVmbRMMtQVYIHX5E5f5mXNgARkKQd4ZUNRJnOsHyHRcnewai0BQjb9KrNPB5iGertfo/W+BGv2af2bHs4wgbjD+Ela3kkAigh7xBEEUb9htUcXOnQxWcsGJcHPUEIlRVFQasGlk3DMFkS7EXINYWKilid8WEAC1Wu+tSGFpyT1M3Owja7wlZRMLfMc8V0xlOHLsWK6HAGnyRxX4/EWuBIMrAQZkysCAiXXmCRZE6ByTwFEdOd8DmlDK5BNq2gfYkyHCVFq1sv0t03nLCGs0cEKHCDREvZ/JjiCFSE6mCxpBADlfwdzNl88YI6/b7rKqNzH9U1xPPPMmI1ND4nWC6jQiNeFsQPR6ke5QxatOjxaC9N/g5XJ+qI5NFqv0NSeb1jdn6P2tPIuFdy0BCY9ak9kXK6lNjxzUKK5aT+RwWRBiK6GL9j34euS15I6ZhUijVwnEh6DSUpiBxbyXCH+NzSLNvsD1rtLs8OmEPhuhgEi9G2fSK+RHHZCqmawjByMa3kMnJ10KwbhpRhK3kKI8oVtQby3Acwt1jPfpCf8OXTTEfywzl3v0ufkziNNvMlM20wEJ3p7Y9Hkc60JUdnrCXlM13dR7t9jGD8bSdxcVrCaHom1BAi82eThQeMMkIfLU4lrd7nWoqRyUQQ66bFXsCF+/Uo6ix29OgMxmO3ALFavpN3HiGa0JFrJj9zu26v0qzcsfT9vutByLeldkwBEvfrwRdy6Ggx0SHzCsFDusfrqaWADToUg/rn45CDipjDEYQV++8fWVrupVLANRlMXv3zJLsJG3CIQiDRkTEmoPBoDdThtQQVAuBtPmMh5WUGpaM/l9NqHNZ8AcaHDG2XqQspQpNbL4bzOqn9wtCaTYg+oeecpdE4iPpTDbYCDt2PB19/qiVFZ6zZ60+1FRL5Y/xBVXUddRGpXEUHaFGDZJnY1vMXYOwJ21g5Bxsf4AkJ3VRuQWfBox3mE77LlwJlSRSflcUThVg9jLtD6zTbHEYpUBUFAl7qhgevAZXkHYSX4SbbBClKrPI2sTEFIPUvh16LuCJDZyCR4TPP/kGonGmFZkoloi81s2uLJuP155ycFN5jgk32uoJTdS80szmPOYiP2OBocpsLZOmMO3tyc1DWgLViriNOQMT0dcpc8sUwnHfjY56qJUtUi+UQg4AuqslkpyPkkARm+WEd84jNhGEdZoIzd1jHH/ixuIjI0f62zNZPUY42X3HxJKHBfhpH4g8bLARg3Y/hNMlUqe9bWrRkApyK2UVEsHVggmynNJCugTAH8ZnA6DGSAE/SZmz3NB2XJWuf+aWwUGtoSaI2mY1kLWfCjM5rNn1KC7R8O5tiOcSg+vWwZagjZPl29h16JtJ+mxIAeaOcDsI/CSAuoAN+P3jfJUTVQXgyITnzugmOmOPLEZfJ1JCtrAjTMt+t7f0T3tPSkIveyRokxRS77Y+HLUAtHcvfxhpUmccIxtt21gJLzsDhIH4Ik+LYZGI9CZG2W6LtMO8lLUVj7/eOfmSLerum/7nH9NlE4GsUkwV+UPawgLKASO/LwdvAIj0HYf2K8jSFvTuvOExmopjLwtzH7aLMElr4HAV4dhrooM2h3DszCV8OXrGI9ByEYmnjqqc3V4xk8/CMFiPB63F/zkNzyhC5iLKCK+McsuT4iJj0Meqddfofj6c0+IA2nTEXUAp8IEIHsUstQcym3KuspGv27WogW8sP7lmCYE0Y6mMlV3NH/Fw8ofX3tOzn2Bz8LNdgg5aCKht+nebJGUiWGrWQWTRH+BlGICUUaqm7ftcZj33rMiMUb2+jV3ancZ1gCtfT1Ybq2kscuHd+6388bGfAgB6tMbmZWIx8NJ4iNUW+5jmY4wAkSolaWLeUnYD4FUoTX1W/72zyScXgB5mJD+l2xf2bTqTc09BrJ3gc+t8mEUhuVBk2ofwWKp6FkTueKJ3heiguQtQO4uQ5n1RNed40FafZj5r+5Sdcctm+6ByHyByMqJw9PuKY/IJWEz3IbwcUAXW/HrrzvCNFa+uZ+bb/bB33koEME5wsNCtNi3pv64knSfmhnQfGefOJLXJ80L7zeTqJkU9TUuNdlL1evqyfomSL7siKuCAWPUrWrwrxqhuIotX8qK9lGAqiu7L6JZBMQHSFkYeKDj3PpHQCliEa7I83mZhBJgTOzycM6ye0KWP0EOXfG68P/5s8hpVvJEyo8GGaO5IhEVKUwnp45KwLJHUAZTrD8v1mk70/S1SizeUuwvFZUUTrJ+aavsIK28e8QksQgQMx79WXAlscfOEXmC6dgem8LkzUZqsWtSz5mbqOlL0MLaKoFIf+qiJirc6HJTQSsiUKHybZKjnkZfIWSMrkrAouZVrD8fgtQbY4PSUlxW1iT2JzNBWD2XQb13PeogD35bd8neGqNvfENWn4sYc5/sWvB++gHtJk4l6asagi/hEV6CPKaY6K1VWW7qaTEnHwXlye+Ong5aNHkJaRzU3GUgTkIX0Tj4WIRzcVswnH9W6fZgVB7ZGcB4O6muRSIuAgQOx9OQofkUiTgXcIJ9tZ/UOXL/OLioBDL9X98YmKSNPBiQoZKE6rAFeQBvd5DVZfQUQfQIj/GOZ8bjz3XoSNo0tnzBq/2QTtPFp/v07ui3T9Peg5KYiYSZAXUJK2OXh7SUbZQRy6ZHK3/AdMyxO6CZ8zucjc3K+aPqKkpE+w6tgk+qfikiQpgUAg/udJhE3EeYAL/ynQVUpL8ATS1SNJZ0TWZTbHQBrHX9KCGIu1TNHp6YAyV1K7k5LfC2rJPaSrfr/RTbbuC9dHa74ZeBX64wvadPBR4yrQabNsKJjCx6DkvNGYc8gX/eEsyZ8VdhnXpD+rzc+TaC4nGfO1MUrYtSDZ6lCctejtRbpjx0xNBcZ1mVp38UP36962vx+RxpKy2mi4icUIrtxrW7o4YPlkQ0nypJeMChtPLD8NbvOdDON0a6iOuC5TqyN+aOEEyP9+ROpIymqj4SYWI/rvYQX63iyyBv0prH6cxq9gLkme1BHMnmXIT4PbvCEkFIMvtAqEpkISOk2tksTBB0En3ZcjUksKhhsNOLlAtX9JpaRrAU7lRNrJUqg8aSgJl5YiS22PGUNUmG/1Dv3A6Pk9ivePZZzQzIm6HilJ/8k9UzI8AH8/0OiINJrejBiNPaccCh/GbnfqVtI5n02i/F7YQMxYoDAJnZchWVZqbVZdpi/Mx6O1DkhVHcC9s6U4Hd4ds7kMTXuzfEn6FK90UZEeKGsiKtINusJZXryLiuhblA+PfbTXPSra0NU9Xv/8U/UzN5n17zQObRf918+bbymZ6+hb3HUZKJwe4OjlIirQlj0KHoLnv4KD8A1GhiL/otcdwDDtF2iI9uMI+A/pOorxX2jTzDgwENAGGhJoNjZ4lGxLFlg9HLP9BA7VftUhD90XGbsxytMyW4Ojgc2kRA5ajmBxi7IdznMi481V3ACDYRNo9GGrkZHF9C6DUcXP0IhiizE60ziGaGM/g/SwLxpQm2tVEHbzUTZC812TV60ZImVX20LFsbaR5rCK8dQDjY7QZnsaDNB+geC3H8cIoK8fQUXYfgHRbz6OKcCCqEqiUn6QrQ2S4d53UBmKTUYG7HzSg7G6T9Aw3dcxiW6MmqE4N19AWW4+joB/h/P6efAAfvcJGqD7Ojbl8s1PvfNpb3u3eF2UGTTf7ReQRc3HEfDiO/zBGOJnaCCxhd58K2jqNVDM/qputPoYsaTn46RGSfkYsT6QjhE/g6QKLTRlj1akwRnawYoUbKWSSKHhGAooxj9Q9vqAdxCvxc/goEILvbnla43Ippdvo5hhvpnp4G3a8CscF/CGOdpFC7VBLz1MFYpj0EK1CJpW2qug7jiyGMBWKjz4lqa43O/RGj/iNTv8cHn6ZVjJ2qvwg/toYwp3v6kDrYdbsbI5uDMre1hhp42XCUa6c/oQQSc1/qNitth3vXG+RBmOkq5KwEW6+4aTSDIvOp0UeCn7jeD7ZxmxXz4nGNoIxM8QDmILO+7os2Rk663+b76O+h3lCOlhYiyXvaWVkwYaOPCNdbDh21vhpY2TCT62UlOXJdEVnbq5wTqqe4yZM22msqEp034CzZj265g3C6PsNsPg6Yr7Bnqyus8jg3CX6YNBuG/QIOxzvtI6WrG2Mr8f/1ExkJ4PkDWWDaKEX7fQgf9HlpZ72SD1R8VIdYuRkbqY0ME43SdoFPp1FPrlC7EPkyg+K4sn6gqudlapI0zdHMJC3WMEu7o6zQCN+ndovPqT5kHoM0200CaQlJ6ExGaqo5DYcgSLP/AjsaizzQgWcDMIC7ilJhaKkdWj6U2jRIa5b9Lp1DptsZafUnD75r5JB6k+jwxyh54JwbcpTopcPmFgK2hgsOGYXV1Xs4cs6fYTaDu3XzVHkMyY+Fk1kta89apHD4brfYfG6zUZnUOhyDAwe8J3eN6EJqP87Nf0A3jabwLztd9qzBM4rDM3dAkO24C+wWEz08FlJpusoRYaemaatCoUMPuSlrAcSBoboqOBhx4C47cNfA2Z4Y0D/xW8deAb6A9Fh1APV7UYGbJqNHqpCFEmpUiLkq6OxpCG7huIffdZQ+vSxf0RFU8ptBn3G8g0L99mdIpiqanHfYOnJdY04j5n8kG4b9Ag3OcxIwYliJx0VIpu2AQ0agatxs5nBAZih8dv4C1u7zt4ThObjF65peA1Rf07fMWWaly7dOn/hyq6/QTfdTZfdVBv3TIwBe1nKSHa/lEoSfxwUKgVODbU0ACFkbHHBx13lVSbgFSD9L6DzhCxyaj/EEz7CjgSwXawRxFsqo+IevjRQcdPzEKK5+EJWfgMnoiFFqN3ubt9hLeQ7637BN/lNl9HL1tZtsEHtNvH8BY3aAFfufYaabg1PqCiQJmYHwZ0ckANZS4PqK2OM2cEDaCNwrVjNHw/jyXg0xQbqNxwvaajkx/lZYa+Irx9gqS59x2eeKGJ3oDvMFnWOczpYRPFsFyrkZF7iSAHw/a+Q2P2mozp/tdkrdjr+K+g5ucbjHrx+gnlAM9dvwnsreu30hpZztXed/mYulyVJpYaDC1tCUYHyRobxG+oVCjYbCyeQ1uZNhfuCgyGTVQRQNojN5ej8oEHLVR3rLrD3iHabCMP1eo3gA/PYpsxJmdpnhPgsXzUYROQyYNWo0qzS7oFqMvuI6wou+8a4/AJtcCx+Aay8fg2htG3IzGx6uY6Ubkr2lT7briBL4/SHLRQRYPWjdD4xUxzoaCIyRk2Ud1MrM72+xijzUNat8cGWIxE5sDN9LDh++gjJF+DgxZ6aNTNxzFoogE1Qko0I0tWXTtjudQMIdcPJRcqluqHUAzSWQ15MmgiiwgXW2mcI9rkEODhof0qOzG0DTRCj+VDCV9lAci6Q/WelMOnke679EDQNdGTauD9p0y4gaYKGQdam2A0hobG2IMBucdN6icpq+5BC9endSyrOvTfYnWP2fqvawgK8pczYk/Fq5kKis4rmF9E8nVZw78gGucL3DoYU6CXUh1HlI+fzNkxsCt4xZmv7mu2Dzmj11FOquy9EqN07A2SEhK0mYBQle+OvLPyIcq2NGjNmJV1RzkDpASrCFwmC8m+qVyPYoMQS5B/X1aRDL0fsyOteiS2qiDCxPFNHJEc9BAfuLX9JE/XzEkUnkCt2rdXQ0LhhgoBF19/VbINP+0S+oHPulhv9Wstc9LrE5GK6H4T/+T2Tnesn+yBnT2JPdbJCRUbep+lCUlvTxXtwVexMcobj2twUHsrCZec7AUAQRkh39bkjb3M4xLYcI5jWmKmhaxgQq9pOBZoyZAD0U1alg60nOpB25BLoPemWgAgeyhtzobmAa7STBk2CmGq9B8Rs57SB8IWJkvzClR4hwrYLVA7OdrQu1iGuuq9q2jMiI9cK0tG8irXgmzhoeyqBQwQDrf0grjYD3zcW3XvffJOfuOAHCcfTi0zJAPCfzHkt68+FfM+aON/xvsO80qbyV62m5Op8qcrjBudbnKi+kkDGFGyhABgT5Atw49h2SM3eXS6eZrzWdkjPElWLBSwnf/FAt3rVDuKKq2AD6kQrn2M1g3YM+TSkTIJbhCcWSarCOzpWR4WyrTmRs9IuPqdQsoVtKkPvoVkjIkg9Tv5t1VmYguQxAQ8j6o7hJQTIFuLAESVfsXaim28bTePNxne4kRhxg6a+nfQGciUA8liahk5vUI7xfwBqW6qiVOksJmebGnamBWf60bKDa3uoyQOM/TwxMqT7qhhSRLuwKDHkuZ4YHDNkvGTobRLsCOi7QR4YYsRQ46QFe2xuDtpSDkxbBviXCTJCMUzwuOpqE/auDwMmgaThmlZ0EtZxZvIUl7I+4Q09IHBIRZ5NvMhkselBWweTGLmYYkkmdg4c0Y6BmOTOkEYD00z65fFvbsiwZkkOxt4K28BRnGrO84XPYZoQBUTOylgS5I1+ZZW9eCmIqyENjGrFroChiHIytA1eetwdxeDQGvh+oL7GoQZiuAzeetRoiTUAGQsgyWKJIO6p2ZtEBMcqDUSNPIcNUmxGITF45u4Tu9gS/TQ2ClmrbTiqQBCziAowSZjhSpx5qGy2EJMp5dNHW75YxGQfHOQp1TBLq3uoyxQ0q5nPcrxUcJUpBz1xdIBP/gxdRmrAjIhUzSga0AOv8B1RHe874RyO68SADPLrqTDarNUDWcSpujAFdK+qYDDqdzszXjNbX7yvTzQdU6TUXg06hBuqPAVOocd9rMkV0a7LAOy/YzfR7t9jDrA8jnvtfSE+oSz3eZ2XokviYYkS1oqbg2cX0UNclaz7vJ01C7kK4zOYSP/ZmZ4UtuHvMrAcaDV+Aybh4wPEoWzrtyvfshlSb7H6e01C0awkLK8oxhORW5J8orPGi6hl28zgrKQm7xDGcw5Puw97Oh3ZmuQymmt2jgjOs9sspXIg1WtWK6dR9QnXrdNMnrl845hIznK9s87+inzWc/uR3+vmHSffKs7hFBbqvwgAhTP75AllGq8dZv86fZcLFJVRFBueXodQ0iTTpUIBs2o2oM561iifo23g2A7OXm2DweF8hSsF1x9wpZQlTD0WoSY9XDkQdUyVl1MrDz4Fu6gOL05B99CtTkEKMpaG55YU/Ndky9Va+d5XQQ72vofch70m/glfFC7pOvnyZro8FccZYeN/B9lw5PaVIxZ3RLD5ynK0eYrLp64EYaEj3XxRo7Qt18Th3WV1ruxZ4Swbjv4cjbAHTwRAvaUrnitYkOeWPOZn3dt/oi9/OqFudjT1ioaUYhckwAKka+z1PUDSyiZkwhURVKaQsr2IQwjRRUoBkSnrJMftoyui/FOfqVjHtY0Bafka6LXwi/R/ZJcrJu03JY9eWz/7+DKqRQbekIc7DnYX5W1v2wPQk1Zm9EjX7/hIZ35hCpmSoUnaRlC1YHV2Vh3ddE1V/KlCxlq5nc1T0eyUEZuZIsDW4bZ3IDyeLXuVlW9c/AVaixvaVv/Kzx0Oqh+Fb+RZ5aq5qNnQC/PKmX1C2t9P1KT0AODVCtD3jjE4pidFQoXgbStf0/B1GyAqz6ugJqUgObQ7auQF1kZzUpwxipjqmENw6M0a1za7DditcjVdYILHMWK04Oqg++TA1wTs956RupcujOjMSmGQ43zRdo3ELlKSPCxS69QqUVy064852pQqnPIOVVzxVYOlxKttnR1fVAZHBkMzyxRbV1guxC71jRkj5EbnMw+eZ5ms6ngulKYp8NG/u3SQRXaqqe0wKyN4MZAHnVYdMGWIa/rB7VqaxGWVYi1ycKdsmSMeBdlr5cv66co2aI7wtquvilgdY12UjFFLLhaMwQupioaYHwN2MrqAku8OjKB/aFNvdj6wMgeFnddibVXAeLH+igIAerCVvQo6r1KIUA7vk61WnM2gdVZV1cY1hKK1oqbZuuAa2U12vsqUl+nuKwvtozF4Wv0khPrHpS/CHatxGq4SiaJbeWEQRV6GT2qyrsy1sgYEkxqhDF0hYbv5JWshUjMoPrueGw43DqE3SktH3zfxtgqa4ravHfhq/yurrJ0p+KHqnkIhsAFjWvvkrI+sTMrHlIDRnCND50NQmniUctF0TqkzQKWYGYg1JWVbWJsTdihaB2SHWDt5GqjUpZEtmVHV9Z51YKWMQNo650QAMawhDUHRlGW2qIaBVzwWbmvjPYJoUVGCl4zQLrVq/2xSXEPMdbF/3XEvCwSCx6v6J9gRnegmcq875d8ro17WTHnQe9hIecWgqI8s115Nr5i64p6YS/SJC+yCNPjHrE22hXSlLV5SFfDSq/Afu0L9vjCtKiBI8qupAJuJcRjVW09sJ0vSKfBSa65miij2nezsoSr5asrKEL53yHXHCGGlzqg+vF989hNVmvYjbHdMzo1u9p2auS1n+nNQDZXsFl39oUaz8DW6AYxvDwBJa6r3VNRu9qNsV3VLTW72nZq5LWres1AtlCRW3f+e2W8Jc4ON6jh5QqsZ965R6Rlyl2ZzD1wHuNc11SDDL1X1DOxoDaZByXXta0nSX9FPhX/Y01gsUnJHB4/xsvXe5wm4YMR08WemqTL6T0c9lmJ2yhr7aBOLLn60+fM5PH8jr2G/o/6/kn//ZcKCGU8mWWUtd9+/4W68nZR/QP5s7pV+5huUJyzX3//5a4kvXeo+usdyvG2A/E7gZmgNR2zA9q0uU4eaVGlPcoYATxGTZPmc+toKKJNVERnWYFp/njyeU3WEjlC/fwTCxGkN1zf0OY6uSmLfVkQktHuWyzc+/7+i3r8338Z4Px7nbDQBwkETUxIQDfJeYnjTYv3VRTnva1PBuKCcP8PRH6v5pIszQJtX1tIn9JEE1DNvndoj5INWXIPaLePCbD8JrmPfiAb3D7n6APaRuvXW1p6m0U8yoCMT4TI9t/f4WibRbu8htH1J38SGd7sXv7v/wfQSE0CquAJAA== + + + 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 49ae7dd7a8..18a610b862 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -1,9 +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; public sealed class MigrationsConfiguration : DbMigrationsConfiguration { @@ -12,22 +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) + { + using (var scope = new DbContextScope(context, hooksEnabled: false)) + { + Seed(context); + scope.Commit(); + } } protected override void Seed(SmartObjectContext context) { - // This method will be called after migrating to the latest version. - - // You can use the DbSet.AddOrUpdate() helper extension method - // to avoid creating duplicate seed data. E.g. - // - // context.People.AddOrUpdate( - // p => p.FullName, - // new Person { FullName = "Andrew Peters" }, - // new Person { FullName = "Brice Lambson" }, - // new Person { FullName = "Rowan Miller" } - // ); - // + context.MigrateLocaleResources(MigrateLocaleResources); + MigrateSettings(context); + } + + public void MigrateSettings(SmartObjectContext context) + { + } - } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + + } + } } diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs new file mode 100644 index 0000000000..3faed3ad16 --- /dev/null +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs @@ -0,0 +1,355 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data.Entity.Infrastructure; +using System.Data.Entity.Validation; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using SmartStore.Core; +using SmartStore.Core.Data.Hooks; +using SmartStore.Utilities; +using EfState = System.Data.Entity.EntityState; + +namespace SmartStore.Data +{ + public abstract partial class ObjectContextBase + { + private SaveChangesOperation _currentSaveOperation; + + private readonly static ConcurrentDictionary _hookableEntities = new ConcurrentDictionary(); + + private enum SaveStage + { + PreSave, + PostSave + } + + private IEnumerable GetChangedEntries() + { + return ChangeTracker.Entries().Where(x => x.State > EfState.Unchanged); + } + + public IDbHookHandler DbHookHandler + { + get; + set; + } + + public override int SaveChanges() + { + var op = _currentSaveOperation; + + if (op != null) + { + if (op.Stage == SaveStage.PreSave) + { + // This was called from within a PRE action hook. We must get out:... + // 1.) to prevent cyclic calls + // 2.) we want new entities in the state tracker (added by pre hooks) to be committed atomically in the core SaveChanges() call later on. + return 0; + } + else if (op.Stage == SaveStage.PostSave) + { + // This was called from within a POST action hook. Core SaveChanges() has already been called, + // but new entities could have been added to the state tracker by hooks. + // Therefore we need to commit them and get outta here, otherwise: cyclic nightmare! + // DetectChanges() here is important, 'cause we turned it off for the save process. + base.ChangeTracker.DetectChanges(); + return SaveChangesCore(); + } + } + + _currentSaveOperation = new SaveChangesOperation(this, this.DbHookHandler); + + Action endExecute = () => + { + _currentSaveOperation?.Dispose(); + _currentSaveOperation = null; + }; + + using (new ActionDisposable(endExecute)) + { + return _currentSaveOperation.Execute(); + } + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken) + { + var op = _currentSaveOperation; + + if (op != null) + { + if (op.Stage == SaveStage.PreSave) + { + // This was called from within a PRE action hook. We must get out:... + // 1.) to prevent cyclic calls + // 2.) we want new entities in the state tracker (added by pre hooks) to be committed atomically in the core SaveChanges() call later on. + return Task.FromResult(0); + } + else if (op.Stage == SaveStage.PostSave) + { + // This was called from within a POST action hook. Core SaveChanges() has already been called, + // but new entities could have been added to the state tracker by hooks. + // Therefore we need to commit them and get outta here, otherwise: cyclic nightmare! + // DetectChanges() here is important, 'cause we turned it off for the save process. + base.ChangeTracker.DetectChanges(); + return SaveChangesCoreAsync(cancellationToken); + } + } + + _currentSaveOperation = new SaveChangesOperation(this, this.DbHookHandler); + + var result = _currentSaveOperation.ExecuteAsync(cancellationToken); + + result.ContinueWith(t => + { + _currentSaveOperation?.Dispose(); + _currentSaveOperation = null; + }); + + return result; + } + + /// + /// Just calls DbContext.SaveChanges() without any sugar + /// + /// The number of affected records + protected internal int SaveChangesCore() + { + return base.SaveChanges(); + } + + /// + /// Just calls DbContext.SaveChangesAsync() without any sugar + /// + /// The number of affected records + protected internal Task SaveChangesCoreAsync(CancellationToken cancellationToken) + { + return base.SaveChangesAsync(cancellationToken); + } + + private void IgnoreMergedData(IEnumerable entries, bool ignore) + { + foreach (var entry in entries) + { + entry.MergedDataIgnore = ignore; + } + } + + internal bool IsInSaveOperation + { + get { return _currentSaveOperation != null; } + } + + private bool IsHookableEntry(IHookedEntity entry) + { + var entity = entry.Entity; + if (entity == null) + { + return false; + } + + return IsHookableEntityType(entry.EntityType); + } + + private static bool IsHookableEntityType(Type entityType) + { + var isHookable = _hookableEntities.GetOrAdd(entityType, t => + { + var attr = t.GetAttribute(true); + if (attr != null) + { + return attr.IsHookable; + } + + // Entities are hookable by default + return true; + }); + + return isHookable; + } + + class SaveChangesOperation : IDisposable + { + private SaveStage _stage; + private IEnumerable _changedEntries; + private ObjectContextBase _ctx; + private IDbHookHandler _hookHandler; + + public SaveChangesOperation(ObjectContextBase ctx, IDbHookHandler hookHandler) + { + _ctx = ctx; + _hookHandler = hookHandler; + } + + public IEnumerable ChangedEntries + { + get { return _changedEntries; } + } + + public SaveStage Stage + { + get { return _stage; } + } + + private IDisposable ExecuteCore() + { + var autoDetectChanges = _ctx.Configuration.AutoDetectChangesEnabled; + IEnumerable mergeableEntities = null; + + // 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 + PreExecute(out IEnumerable changedHookEntries); + + return new ActionDisposable(endExecute); + + void endExecute() + { + try + { + // post + PostExecute(changedHookEntries); + } + finally + { + _ctx.Configuration.AutoDetectChangesEnabled = autoDetectChanges; + _ctx.IgnoreMergedData(mergeableEntities, false); + } + } + } + + public int Execute() + { + using (ExecuteCore()) + { + return _ctx.SaveChangesCore(); + } + } + + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + using (ExecuteCore()) + { + return await _ctx.SaveChangesCoreAsync(cancellationToken); + } + } + + private IEnumerable PreExecute(out IEnumerable changedHookEntries) + { + bool enableHooks = false; + bool importantHooksOnly = false; + bool anyStateChanged = false; + IEnumerable processedHooks = null; + + changedHookEntries = null; + + enableHooks = _changedEntries.Any(); // hooking is meaningless without hookable entries + if (enableHooks) + { + // despite the fact that hooking can be disabled, we MUST determine if any "important" pre hook exists. + // If yes, but hooking is disabled, we'll trigger only the important ones. + importantHooksOnly = !_ctx.HooksEnabled && _hookHandler.HasImportantSaveHooks(); + + // we'll enable hooking for this unit of work only when it's generally enabled, + // OR we have "important" hooks in the pipeline. + enableHooks = importantHooksOnly || _ctx.HooksEnabled; + } + + if (enableHooks) + { + changedHookEntries = _changedEntries + .Select(x => new HookedEntity(x)) + .Where(x => IsHookableEntityType(x.EntityType)) + .ToArray(); + + // Regardless of validation (possible fixing validation errors too) + processedHooks = _hookHandler.TriggerPreSaveHooks(changedHookEntries, importantHooksOnly, out anyStateChanged); + + if (processedHooks.Any() && changedHookEntries.Any(x => x.State == SmartStore.Core.Data.EntityState.Modified)) + { + // Because at least one pre action hook has been processed, + // we must assume that entity properties has been changed. + // We need to call DetectChanges() again. + _ctx.ChangeTracker.DetectChanges(); + } + } + + if (anyStateChanged) + { + // because the state of at least one entity has been changed during pre hooking + // we have to further reduce the set of hookable entities (for the POST hooks) + changedHookEntries = changedHookEntries.Where(x => x.InitialState > SmartStore.Core.Data.EntityState.Unchanged); + } + + return processedHooks ?? Enumerable.Empty(); + } + + private IEnumerable PostExecute(IEnumerable changedHookEntries) + { + if (changedHookEntries == null || !changedHookEntries.Any()) + return Enumerable.Empty(); + + // the existence of hook entries actually implies that hooking is enabled. + + _stage = SaveStage.PostSave; + + var importantHooksOnly = !_ctx.HooksEnabled && _hookHandler.HasImportantSaveHooks(); + + return _hookHandler.TriggerPostSaveHooks(changedHookEntries, importantHooksOnly); + } + + private string FormatValidationExceptionMessage(IEnumerable results) + { + var sb = new StringBuilder(); + sb.Append("Entity validation failed" + Environment.NewLine); + + foreach (var res in results) + { + var baseEntity = res.Entry.Entity as BaseEntity; + sb.AppendFormat("Entity Name: {0} - Id: {0} - State: {1}", + res.Entry.Entity.GetType().Name, + baseEntity != null ? baseEntity.Id.ToString() : "N/A", + res.Entry.State.ToString()); + sb.AppendLine(); + + foreach (var validationError in res.ValidationErrors) + { + sb.AppendFormat("\tProperty: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); + sb.AppendLine(); + } + } + + return sb.ToString(); + } + + public void Dispose() + { + _ctx = null; + _hookHandler = null; + _changedEntries = null; + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 579994d654..ea5675eabb 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -3,31 +3,28 @@ using System.Data; using System.Data.Common; using System.Data.Entity; +using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; -using System.Data.Entity.Validation; -using System.Data.SqlClient; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.Smo; +using SmartStore.ComponentModel; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Events; +using SmartStore.Data.Setup; +using SmartStore.Utilities; +using EfState = System.Data.Entity.EntityState; namespace SmartStore.Data { - /// - /// Object context - /// [DbConfigurationType(typeof(SmartDbConfiguration))] - public abstract class ObjectContextBase : DbContext, IDbContext + 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"); - #region Ctor + // Instance of the internal ObjectStateManager.TransactionManager + // required for detecting if EF performs change detection + private object _transactionManager; /// /// Parameterless constructor for tooling support, e.g. EF Migrations. @@ -37,92 +34,17 @@ protected ObjectContextBase() { } - protected ObjectContextBase(string nameOrConnectionString, string alias = null) + protected ObjectContextBase(string nameOrConnectionString, string alias = null) : base(nameOrConnectionString) { this.HooksEnabled = true; this.AutoCommitEnabled = true; this.Alias = null; - this.EventPublisher = NullEventPublisher.Instance; - } - - #endregion - - #region Properties - - public IEventPublisher EventPublisher - { - get; - set; - } - - #endregion - - #region Hooks - - private readonly IList _hookedEntries = new List(); - - private void PerformPreSaveActions(out IList modifiedEntries, out HookedEntityEntry[] modifiedHookEntries) - { - modifiedHookEntries = null; + this.DbHookHandler = NullDbHookHandler.Instance; - modifiedEntries = this.ChangeTracker.Entries() - .Where(x => x.State != System.Data.Entity.EntityState.Unchanged && x.State != System.Data.Entity.EntityState.Detached) - .Except(_hookedEntries) - .ToList(); - - // prevents stack overflow - _hookedEntries.AddRange(modifiedEntries); - - var hooksEnabled = this.HooksEnabled && modifiedEntries.Any(); - if (hooksEnabled) - { - modifiedHookEntries = modifiedEntries - .Select(x => new HookedEntityEntry - { - Entity = x.Entity, - PreSaveState = (SmartStore.Core.Data.EntityState)((int)x.State) - }) - .ToArray(); - - // Regardless of validation (possible fixing validation errors too) - this.EventPublisher.Publish(new PreActionHookEvent { ModifiedEntries = modifiedHookEntries, RequiresValidation = false }); - } - - if (this.Configuration.ValidateOnSaveEnabled) - { - var results = from entry in this.ChangeTracker.Entries() - where this.ShouldValidateEntity(entry) - let validationResult = entry.GetValidationResult() - where !validationResult.IsValid - select validationResult; - - if (results.Any()) - { - - var fail = new DbEntityValidationException(FormatValidationExceptionMessage(results), results); - //Debug.WriteLine(fail.Message, fail); - throw fail; - } - } - - if (hooksEnabled) + if (_commandTimeoutInSeconds >= 0 && DataSettings.Current.IsSqlServer) { - this.EventPublisher.Publish(new PreActionHookEvent { ModifiedEntries = modifiedHookEntries, RequiresValidation = true }); - } - - modifiedEntries.Each(x => _hookedEntries.Remove(x)); - - IgnoreMergedData(modifiedEntries, true); - } - - private void PerformPostSaveActions(IList modifiedEntries, HookedEntityEntry[] modifiedHookEntries) - { - IgnoreMergedData(modifiedEntries, false); - - if (this.HooksEnabled && modifiedHookEntries != null && modifiedHookEntries.Any()) - { - this.EventPublisher.Publish(new PostActionHookEvent { ModifiedEntries = modifiedHookEntries }); + Database.CommandTimeout = _commandTimeoutInSeconds; } } @@ -132,11 +54,9 @@ public bool HooksEnabled set; } - #endregion - - #region IDbContext members + #region IDbContext members - public virtual string CreateDatabaseScript() + public virtual string CreateDatabaseScript() { return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript(); } @@ -248,16 +168,9 @@ 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.ArgumentNotEmpty(sql, "sql"); + Guard.NotEmpty(sql, "sql"); int? previousTimeout = null; if (timeout.HasValue) @@ -274,117 +187,80 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { - //Set previous timeout back + // Set previous timeout back ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimeout; } return result; } - /// Executes sql by using SQL-Server Management Objects which supports GO statements. - public int ExecuteSqlThroughSmo(string sql) + /// + /// Checks whether the underlying ORM mapper is currently in the process of detecting changes. + /// + /// + public virtual bool IsDetectingChanges() { - Guard.ArgumentNotEmpty(sql, "sql"); - - int result = 0; - - try + if (_transactionManager == null && DataSettings.DatabaseIsInstalled()) { - bool isSqlServer = DataSettings.Current.IsSqlServer; - - if (!isSqlServer) + var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager; + if (stateManager != null) { - result = ExecuteSqlCommand(sql); + // Get the internal TransactionManager property instance from ObjectStateManager + _transactionManager = FastProperty.GetProperty(stateManager.GetType(), "TransactionManager")?.GetValue(stateManager); } - else - { - using (var sqlConnection = new SqlConnection(GetConnectionString())) - { - var serverConnection = new ServerConnection(sqlConnection); - var server = new Server(serverConnection); + } - result = server.ConnectionContext.ExecuteNonQuery(sql); - } + if (_transactionManager != null) + { + // Get the "IsDetectChanges" property of the internal TransactionManager + var prop = FastProperty.GetProperty(_transactionManager.GetType(), "IsDetectChanges"); + if (prop != null) + { + return (bool)prop.GetValue(_transactionManager); } } - catch (Exception) - { - // remove the GO statements - sql = Regex.Replace(sql, @"\r{0,1}\n[Gg][Oo]\r{0,1}\n", "\n"); - result = ExecuteSqlCommand(sql); - } - return result; + return false; + } + + public void DetectChanges() + { + base.ChangeTracker.DetectChanges(); } public bool HasChanges { get { - return this.ChangeTracker.Entries() - .Where(x => x.State != System.Data.Entity.EntityState.Unchanged && x.State != System.Data.Entity.EntityState.Detached) - .Any(); + return GetChangedEntries().Any(); } } - public IDictionary GetModifiedProperties(BaseEntity entity) + public virtual bool IsModified(BaseEntity entity) { - var props = new Dictionary(); - - var entry = this.Entry(entity); - - // be aware of the entity state. you cannot get modified properties for detached entities. - if (entry.State != System.Data.Entity.EntityState.Detached) - { - var modifiedProperties = from p in entry.CurrentValues.PropertyNames - let prop = entry.Property(p) - where prop.IsModified - select prop; - - foreach (var prop in modifiedProperties) - { - props.Add(prop.Name, prop.OriginalValue); - } - } + Guard.NotNull(entity, nameof(entity)); - return props; + var entry = this.Entry((object)entity); + return entry.HasChanges(this); } - public override int SaveChanges() - { - IList modifiedEntries; - HookedEntityEntry[] modifiedHookEntries; - PerformPreSaveActions(out modifiedEntries, out modifiedHookEntries); - - // SAVE NOW!!! - bool validateOnSaveEnabled = this.Configuration.ValidateOnSaveEnabled; - this.Configuration.ValidateOnSaveEnabled = false; - int result = base.SaveChanges(); - this.Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled; - - PerformPostSaveActions(modifiedEntries, modifiedHookEntries); - - return result; - } - - public override Task SaveChangesAsync() + public bool TryGetModifiedProperty(BaseEntity entity, string propertyName, out object originalValue) { - IList modifiedEntries; - HookedEntityEntry[] modifiedHookEntries; - PerformPreSaveActions(out modifiedEntries, out modifiedHookEntries); + Guard.NotNull(entity, nameof(entity)); - // SAVE NOW!!! - bool validateOnSaveEnabled = this.Configuration.ValidateOnSaveEnabled; - this.Configuration.ValidateOnSaveEnabled = false; - var result = base.SaveChangesAsync(); - - result.ContinueWith((t) => + if (entity.IsTransientRecord()) { - this.Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled; - PerformPostSaveActions(modifiedEntries, modifiedHookEntries); - }); + originalValue = null; + return false; + } - return result; + var entry = this.Entry((object)entity); + return entry.TryGetModifiedProperty(this, propertyName, out originalValue); + } + + public IDictionary GetModifiedProperties(BaseEntity entity) + { + return this.Entry((object)entity).GetModifiedProperties(this); } // required for UoW implementation @@ -455,6 +331,14 @@ public void UseTransaction(DbTransaction transaction) this.Database.UseTransaction(transaction); } + private IEnumerable GetMergeableEntitiesFromChangeTracker() + { + return base.ChangeTracker.Entries() + .Where(x => x.State > EfState.Detached) + .Select(x => x.Entity) + .OfType(); + } + #endregion #region Utils @@ -463,7 +347,7 @@ public void UseTransaction(DbTransaction transaction) /// Resolves the connection string from the Settings.txt file /// /// The connection string - /// This helper is called from parameterless DbContext constructors which are required for EF tooling support. + /// This helper is called from parameterless DbContext constructors which are required for EF tooling support or during installation. public static string GetConnectionString() { if (DataSettings.Current.IsValid()) @@ -476,7 +360,7 @@ public static string GetConnectionString() protected internal bool IsSqlServer2012OrHigher() { - if (!s_isSqlServer2012OrHigher.HasValue) + if (!_isSqlServer2012OrHigher.HasValue) { try { @@ -485,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 @@ -522,84 +406,55 @@ public bool IsAttached(TEntity entity) where TEntity : BaseEntity public void DetachEntity(TEntity entity) where TEntity : BaseEntity { - this.Entry(entity).State = System.Data.Entity.EntityState.Detached; + this.Entry(entity).State = EfState.Detached; } public int DetachEntities(bool unchangedEntitiesOnly = true) where TEntity : class { - Func predicate = x => - { - if (x.Entity is TEntity) - { - if (x.State == System.Data.Entity.EntityState.Detached) - return false; + return DetachEntities(o => o is TEntity, unchangedEntitiesOnly); + } - if (unchangedEntitiesOnly) - return x.State == System.Data.Entity.EntityState.Unchanged; + public int DetachEntities(Func predicate, bool unchangedEntitiesOnly = true) + { + Guard.NotNull(predicate, nameof(predicate)); - return true; + Func predicate2 = x => + { + if (x.State > EfState.Detached && predicate(x.Entity)) + { + return unchangedEntitiesOnly + ? x.State == EfState.Unchanged + : true; } return false; }; - - var attachedEntities = this.ChangeTracker.Entries().Where(predicate).ToList(); - attachedEntities.Each(entry => entry.State = System.Data.Entity.EntityState.Detached); + + var attachedEntities = this.ChangeTracker.Entries().Where(predicate2).ToList(); + attachedEntities.Each(entry => entry.State = EfState.Detached); return attachedEntities.Count; } - public void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity + public void ChangeState(TEntity entity, EfState requestedState) where TEntity : BaseEntity { - Console.WriteLine("ChangeState ORIGINAL"); - this.Entry(entity).State = newState; - } + //Console.WriteLine("ChangeState ORIGINAL"); + var entry = this.Entry(entity); - public void ReloadEntity(TEntity entity) where TEntity : BaseEntity - { - this.Entry(entity).Reload(); + if (entry.State != requestedState) + { + // Only change state when requested state differs, + // because EF internally sets all properties to modified + // if necessary, even when requested state equals current state. + entry.State = requestedState; + } } - private string FormatValidationExceptionMessage(IEnumerable results) - { - var sb = new StringBuilder(); - sb.Append("Entity validation failed" + Environment.NewLine); - - foreach (var res in results) - { - var baseEntity = res.Entry.Entity as BaseEntity; - sb.AppendFormat("Entity Name: {0} - Id: {0} - State: {1}", - res.Entry.Entity.GetType().Name, - baseEntity != null ? baseEntity.Id.ToString() : "N/A", - res.Entry.State.ToString()); - sb.AppendLine(); - - foreach (var validationError in res.ValidationErrors) - { - sb.AppendFormat("\tProperty: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage); - sb.AppendLine(); - } - } - - return sb.ToString(); - } - - private void IgnoreMergedData(IList entries, bool ignore) + public void ReloadEntity(TEntity entity) where TEntity : BaseEntity { - try - { - foreach (var entry in entries) - { - var entityWithPossibleMergedData = entry.Entity as IMergedData; - if (entityWithPossibleMergedData != null) - { - entityWithPossibleMergedData.MergedDataIgnore = ignore; - } - } - } - catch { } + this.Entry((object)entity).ReloadEntity(); } - #endregion + #endregion #region Nested classes @@ -609,7 +464,7 @@ private class DbContextTransactionWrapper : ITransaction public DbContextTransactionWrapper(DbContextTransaction tx) { - Guard.ArgumentNotNull(() => tx); + Guard.NotNull(tx, nameof(tx)); _tx = tx; } 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/ActivityLogTypeMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/ActivityLogTypeMigrator.cs index b397af928a..103513101e 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/ActivityLogTypeMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/ActivityLogTypeMigrator.cs @@ -13,7 +13,7 @@ internal class ActivityLogTypeMigrator public ActivityLogTypeMigrator(SmartObjectContext ctx) { - Guard.ArgumentNotNull(() => ctx); + Guard.NotNull(ctx, nameof(ctx)); _ctx = ctx; _activityLogTypeRecords = _ctx.Set(); @@ -44,9 +44,9 @@ private Language GetDefaultAdminLanguage(DbSet settingRecords, DbSet systemKeyword); - Guard.ArgumentNotEmpty(() => enName); - Guard.ArgumentNotEmpty(() => deName); + Guard.NotEmpty(systemKeyword, nameof(systemKeyword)); + Guard.NotEmpty(enName, nameof(enName)); + Guard.NotEmpty(deName, nameof(deName)); var record = _activityLogTypeRecords.FirstOrDefault(x => x.SystemKeyword == systemKeyword); diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/BuilderDbContextExtensions.cs b/src/Libraries/SmartStore.Data/Setup/Builder/BuilderDbContextExtensions.cs index 60c743711e..91ad3c1149 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/BuilderDbContextExtensions.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/BuilderDbContextExtensions.cs @@ -1,20 +1,16 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace SmartStore.Data.Setup { public static class BuilderDbContextExtensions { - #region Resource building public static void MigrateLocaleResources(this SmartObjectContext ctx, Action fn, bool updateTouchedResources = false) { - Guard.ArgumentNotNull(() => ctx); - Guard.ArgumentNotNull(() => fn); + Guard.NotNull(ctx, nameof(ctx)); + Guard.NotNull(fn, nameof(fn)); var builder = new LocaleResourcesBuilder(); fn(builder); @@ -26,13 +22,12 @@ public static void MigrateLocaleResources(this SmartObjectContext ctx, Action fn) { - Guard.ArgumentNotNull(() => ctx); - Guard.ArgumentNotNull(() => fn); + Guard.NotNull(ctx, nameof(ctx)); + Guard.NotNull(fn, nameof(fn)); var builder = new SettingsBuilder(); fn(builder); @@ -43,7 +38,6 @@ public static void MigrateSettings(this SmartObjectContext ctx, ActionThe key(s) of the resources to delete public void DeleteFor(string lang, params string[] keys) { - Guard.ArgumentNotEmpty(() => lang); + Guard.NotEmpty(lang, nameof(lang)); lang = lang.NullEmpty(); keys.Each(x => _entries.Add(new LocaleResourceEntry { Key = x, Lang = lang, Important = true })); @@ -57,7 +56,7 @@ public void DeleteFor(string lang, params string[] keys) /// IResourceAddBuilder public IResourceAddBuilder Update(string key) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); Action fn = (string v, string l, bool isHint) => { @@ -80,7 +79,7 @@ public IResourceAddBuilder Update(string key) /// IResourceAddBuilder public IResourceAddBuilder AddOrUpdate(string key) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); Action fn = (string v, string l, bool isHint) => { @@ -104,7 +103,7 @@ public IResourceAddBuilder AddOrUpdate(string key) /// German value of the resource public void AddOrUpdate(string key, string value, string deValue) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); _entries.Add(new LocaleResourceEntry { Key = key, Value = value }); _entries.Add(new LocaleResourceEntry { Key = key, Value = deValue, Lang = "de" }); @@ -120,7 +119,7 @@ public void AddOrUpdate(string key, string value, string deValue) /// German hint resource public void AddOrUpdate(string key, string value, string deValue, string hint, string deHint) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); AddOrUpdate(key, value, deValue); AddOrUpdate(key + ".Hint", hint, deHint); @@ -133,7 +132,7 @@ internal void Reset() internal IEnumerable Build() { - return _entries.OrderByDescending(x => x.Important).ThenBy(x => x.Lang); + return _entries.OrderByDescending(x => x.Important).ThenBy(x => x.Lang).ToList(); } #region Nested builder for AddOrUpdate @@ -154,7 +153,7 @@ public IResourceAddBuilder Value(string value) public IResourceAddBuilder Value(string lang, string value) { - Guard.ArgumentNotEmpty(() => value); + Guard.NotEmpty(value, nameof(value)); _fn(value, lang.NullEmpty(), false); return this; } @@ -167,7 +166,7 @@ public IResourceAddBuilder Hint(string value) public IResourceAddBuilder Hint(string lang, string value) { - Guard.ArgumentNotEmpty(() => value); + Guard.NotEmpty(value, nameof(value)); _fn(value, lang.NullEmpty(), true); return this; } diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs index 92d059bec8..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; @@ -19,7 +18,7 @@ internal class LocaleResourcesMigrator public LocaleResourcesMigrator(SmartObjectContext ctx) { - Guard.ArgumentNotNull(() => ctx); + Guard.NotNull(ctx, nameof(ctx)); _ctx = ctx; _languages = _ctx.Set(); @@ -28,12 +27,12 @@ public LocaleResourcesMigrator(SmartObjectContext ctx) public void Migrate(IEnumerable entries, bool updateTouchedResources = false) { - Guard.ArgumentNotNull(() => entries); + Guard.NotNull(entries, nameof(entries)); 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()); @@ -50,10 +49,10 @@ public void Migrate(IEnumerable entries, bool updateTouched foreach (var lang in langMap) { - foreach (var entry in entries.Where(x => x.Lang == null || langMap[x.Lang.ToLower()].Id == lang.Value.Id)) + 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) { @@ -98,9 +97,6 @@ public void Migrate(IEnumerable entries, bool updateTouched // remove deleted resources _resources.RemoveRange(toDelete); - // update modified resources - toUpdate.Each(x => _ctx.Entry(x).State = System.Data.Entity.EntityState.Modified); - // save now int affectedRows = _ctx.SaveChanges(); } diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs index fe509e1cdf..8825ed79f8 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs @@ -14,7 +14,7 @@ internal class PermissionMigrator public PermissionMigrator(SmartObjectContext ctx) { - Guard.ArgumentNotNull(() => ctx); + Guard.NotNull(ctx, nameof(ctx)); _ctx = ctx; _permissionRecords = _ctx.Set(); @@ -23,8 +23,8 @@ public PermissionMigrator(SmartObjectContext ctx) public void AddPermission(PermissionRecord permission, string[] rolesToMap) { - Guard.ArgumentNotNull(() => permission); - Guard.ArgumentNotNull(() => rolesToMap); + Guard.NotNull(permission, nameof(permission)); + Guard.NotNull(rolesToMap, nameof(rolesToMap)); if (permission.SystemName.IsEmpty()) return; diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/SettingsBuilder.cs b/src/Libraries/SmartStore.Data/Setup/Builder/SettingsBuilder.cs index 25934cc7a4..2a2d087e89 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/SettingsBuilder.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/SettingsBuilder.cs @@ -5,7 +5,6 @@ namespace SmartStore.Data.Setup { - internal class SettingEntry { public string Key { get; set; } @@ -32,7 +31,7 @@ public void Delete(params string[] keys) /// The group/prefix (actually the settings class name) public void DeleteGroup(string group) { - Guard.ArgumentNotEmpty(() => group); + Guard.NotEmpty(group, nameof(group)); _entries.Add(new SettingEntry { Key = group.Trim(), KeyIsGroup = true }); } @@ -41,7 +40,7 @@ public void DeleteGroup(string group) ///
public void Add(string key, TValue value) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); var valueStr = value.Convert(); _entries.Add(new SettingEntry { Key = key.Trim(), Value = valueStr }); @@ -57,5 +56,4 @@ internal IEnumerable Build() return _entries.Where(x => x.Key.HasValue()); } } - } diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs index 41869fc806..b3d707666e 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs @@ -17,7 +17,7 @@ internal class SettingsMigrator public SettingsMigrator(SmartObjectContext ctx) { - Guard.ArgumentNotNull(() => ctx); + Guard.NotNull(ctx, nameof(ctx)); _ctx = ctx; _settings = _ctx.Set(); @@ -25,7 +25,7 @@ public SettingsMigrator(SmartObjectContext ctx) public void Migrate(IEnumerable entries) { - Guard.ArgumentNotNull(() => entries); + Guard.NotNull(entries, nameof(entries)); if (!entries.Any()) return; @@ -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/DbMigrationContext.cs b/src/Libraries/SmartStore.Data/Setup/DbMigrationContext.cs index b3889e8a38..02159f16b1 100644 --- a/src/Libraries/SmartStore.Data/Setup/DbMigrationContext.cs +++ b/src/Libraries/SmartStore.Data/Setup/DbMigrationContext.cs @@ -7,7 +7,6 @@ namespace SmartStore.Data.Setup { - public class DbMigrationContext { private static DbMigrationContext s_dbMigrationContext; @@ -41,5 +40,4 @@ public bool SuppressInitialCreate() where TContext : DbContext return false; } } - } diff --git a/src/Libraries/SmartStore.Data/Setup/DbSeedingMigrator.cs b/src/Libraries/SmartStore.Data/Setup/DbSeedingMigrator.cs index 3dff6a6ab7..ab071e861c 100644 --- a/src/Libraries/SmartStore.Data/Setup/DbSeedingMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/DbSeedingMigrator.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; using System.Data.Entity; -using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; using System.Data.Entity.Migrations.Infrastructure; using System.Linq; -using System.Text.RegularExpressions; using SmartStore.Core.Infrastructure; using SmartStore.Core.Logging; +using SmartStore.Data.Migrations; namespace SmartStore.Data.Setup { - /// /// Provides advanced migrations by providing a seeding platform for each migration. /// This allows for initial seed data after each new database version (for example when @@ -21,6 +19,16 @@ namespace SmartStore.Data.Setup public class DbSeedingMigrator : DbMigrator where TContext : DbContext { private ILogger _logger; + private static bool _isMigrating; + private static Exception _lastSeedException; + + /// + /// Initializes a new instance of the DbMigrator class with the default (core db) configuration. + /// + public DbSeedingMigrator() + : this(new MigrationsConfiguration()) + { + } /// /// Initializes a new instance of the DbMigrator class. @@ -39,7 +47,7 @@ public ILogger Logger { try { - _logger = EngineContext.Current.Resolve(); + _logger = EngineContext.Current.Resolve().GetLogger(this.GetType()); } catch { @@ -51,26 +59,42 @@ public ILogger Logger } } + public static bool IsMigrating + { + get + { + return _isMigrating; + } + } + /// /// Migrates the database to the latest version /// /// The number of applied migrations public int RunPendingMigrations(TContext context) { + if (_lastSeedException != null) + { + // This can happen when a previous migration attempt failed with a rollback. + throw _lastSeedException; + } + var pendingMigrations = GetPendingMigrations().ToList(); if (!pendingMigrations.Any()) return 0; - + var coreSeeders = new List(); var externalSeeders = new List(); var isCoreMigration = context is SmartObjectContext; - var initialMigration = this.GetDatabaseMigrations().LastOrDefault() ?? "[Initial]"; - var lastSuccessfulMigration = initialMigration; + var databaseMigrations = this.GetDatabaseMigrations().ToArray(); + var initialMigration = databaseMigrations.LastOrDefault() ?? "[Initial]"; + var lastSuccessfulMigration = databaseMigrations.FirstOrDefault(); IDataSeeder coreSeeder = null; IDataSeeder externalSeeder = null; int result = 0; + _isMigrating = true; // Apply migrations foreach (var migrationId in pendingMigrations) @@ -106,6 +130,7 @@ public int RunPendingMigrations(TContext context) { if (context is SmartObjectContext) { + _isMigrating = false; throw; } @@ -117,6 +142,7 @@ public int RunPendingMigrations(TContext context) catch (Exception ex) { result = 0; + _isMigrating = false; throw new DbMigrationException(lastSuccessfulMigration, migrationId, ex.InnerException ?? ex, false); } @@ -148,7 +174,9 @@ public int RunPendingMigrations(TContext context) // Apply external data seeders RunSeeders(externalSeeders, context); - Logger.Information("Database migration successful: {0} >> {1}".FormatInvariant(initialMigration, lastSuccessfulMigration)); + _isMigrating = false; + + Logger.Info("Database migration successful: {0} >> {1}".FormatInvariant(initialMigration, lastSuccessfulMigration)); return result; } @@ -168,17 +196,19 @@ private void RunSeeders(IEnumerable seederEntries, T ctx) where if (seeder.RollbackOnFailure) { Update(seederEntry.PreviousMigrationId); - throw new DbMigrationException(seederEntry.PreviousMigrationId, seederEntry.MigrationId, ex.InnerException ?? ex, true); + _isMigrating = false; + _lastSeedException = new DbMigrationException(seederEntry.PreviousMigrationId, seederEntry.MigrationId, ex.InnerException ?? ex, true); + throw _lastSeedException; } - Logger.Warning("Seed error in migration '{0}'. The error was ignored because no rollback was requested.".FormatInvariant(seederEntry.MigrationId), ex); + Logger.WarnFormat(ex, "Seed error in migration '{0}'. The error was ignored because no rollback was requested.", seederEntry.MigrationId); } } } private void LogError(string initialMigration, string targetMigration, Exception exception) { - Logger.Error("Database migration error: {0} >> {1}".FormatInvariant(initialMigration, targetMigration), exception); + Logger.ErrorFormat(exception, "Database migration error: {0} >> {1}", initialMigration, targetMigration); } private class SeederEntry diff --git a/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs b/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs index 44f470e318..c0c842e39c 100644 --- a/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs +++ b/src/Libraries/SmartStore.Data/Setup/IDbMigrationExtensions.cs @@ -18,7 +18,7 @@ public static class IDbMigrationExtensions public static void SqlFileOrResource(this IDbMigration migration, string fileName, Assembly assembly = null, string location = null) { - Guard.ArgumentNotEmpty(() => fileName); + Guard.NotEmpty(fileName, nameof(fileName)); var tokenizer = new SqlFileTokenizer(fileName, assembly ?? Assembly.GetExecutingAssembly(), location); foreach (var cmd in tokenizer.Tokenize()) diff --git a/src/Libraries/SmartStore.Data/Setup/InstallDatabaseInitializer.cs b/src/Libraries/SmartStore.Data/Setup/InstallDatabaseInitializer.cs index f79ab383b4..3a8d7f115a 100644 --- a/src/Libraries/SmartStore.Data/Setup/InstallDatabaseInitializer.cs +++ b/src/Libraries/SmartStore.Data/Setup/InstallDatabaseInitializer.cs @@ -1,30 +1,17 @@ using System; using System.Collections.Generic; using System.Data.Entity; -using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; -using System.Data.Entity.Migrations.Infrastructure; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using SmartStore.Core.Data; using SmartStore.Data.Migrations; -using SmartStore.Utilities; namespace SmartStore.Data.Setup { - /// /// An implementation of that will use Code First Migrations /// to setup and seed the database. /// public class InstallDatabaseInitializer : MigrateDatabaseInitializer { - #region Ctor - public InstallDatabaseInitializer() : base() { @@ -35,10 +22,6 @@ public InstallDatabaseInitializer(string connectionString) { } - #endregion - - #region Interface members - /// /// Initializes the database. /// @@ -57,9 +40,5 @@ public override void InitializeDatabase(SmartObjectContext context) // seed install data this.Seed(context); } - - #endregion - } - } diff --git a/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs b/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs index b3181690c2..7042551b5c 100644 --- a/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs +++ b/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs @@ -3,19 +3,14 @@ using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; -using System.Data.Entity.Migrations.Infrastructure; -using System.Diagnostics; -using System.IO; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; using SmartStore.Core.Data; +using SmartStore.Data.Caching; +using SmartStore.Data.Migrations; using SmartStore.Utilities; namespace SmartStore.Data.Setup { - /// /// An implementation of that will use Code First Migrations /// to update the database to the latest version. @@ -24,7 +19,6 @@ public class MigrateDatabaseInitializer : IDatabaseInitialize where TContext : DbContext, new() where TConfig : DbMigrationsConfiguration, new() { - #region Ctor public MigrateDatabaseInitializer() @@ -66,15 +60,15 @@ public string ConnectionString /// Initializes the database. /// /// The context. - /// public virtual void InitializeDatabase(TContext context) { if (!context.Database.Exists()) { - throw Error.InvalidOperation("Database migration failed becuase the target database does not exist. Ensure the database was initialized and seeded with the 'InstallDatabaseInitializer'."); + throw Error.InvalidOperation("Database migration failed because the target database does not exist. Ensure the database was initialized and seeded with the 'InstallDatabaseInitializer'."); } var config = CreateConfiguration(); + var migrator = new DbSeedingMigrator(config); var tablesExist = CheckTables(context); @@ -94,16 +88,31 @@ public virtual void InitializeDatabase(TContext context) } } - // run all pending migrations - var appliedCount = migrator.RunPendingMigrations(context); - - if (appliedCount > 0) + using (new DbContextScope(context as IDbContext, hooksEnabled: false)) { - Seed(context); - } + // run all pending migrations + var appliedCount = migrator.RunPendingMigrations(context); + + if (appliedCount > 0) + { + Seed(context); + } + else + { + // DB is up-to-date and no migration ran. + 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 @@ -131,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; } @@ -151,8 +169,6 @@ protected bool CheckTables(TContext context) } #endregion - - } } diff --git a/src/Libraries/SmartStore.Data/Setup/MigratorUtils.cs b/src/Libraries/SmartStore.Data/Setup/MigratorUtils.cs index 334f4fd96b..a89c9fcb13 100644 --- a/src/Libraries/SmartStore.Data/Setup/MigratorUtils.cs +++ b/src/Libraries/SmartStore.Data/Setup/MigratorUtils.cs @@ -106,7 +106,7 @@ private static TType CreateTypeInstance(string typeName) where TType : cl public static void ExecutePendingResourceMigrations(string resPath, SmartObjectContext dbContext) { - Guard.ArgumentNotNull(() => dbContext); + Guard.NotNull(dbContext, nameof(dbContext)); string headPath = Path.Combine(resPath, "head.txt"); if (!File.Exists(headPath)) diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index c5c2745302..25ec8fc33c 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -3,11 +3,12 @@ using System.Globalization; 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; @@ -29,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; @@ -46,24 +46,31 @@ protected InvariantSeedData() public void Initialize(SmartObjectContext context) { - this._ctx = context; + _ctx = context; - this._sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); - this._sampleDownloadsPath = CommonHelper.MapPath("~/content/samples/"); + _sampleImagesPath = CommonHelper.MapPath("~/App_Data/Samples/"); + _sampleDownloadsPath = CommonHelper.MapPath("~/App_Data/Samples/"); } #region Mandatory data creators public IList Pictures() { - var entities = new List - { + 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"), + CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_allstar_navy.jpg"), "image/jpeg", "all-star-navy"), + 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/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; } @@ -195,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() @@ -3849,254 +3856,6 @@ public IList EmailAccounts() return entities; } - public IList MessageTemplates() - { - var eaGeneral = this.EmailAccounts().FirstOrDefault(x => x.Email != null); - - string cssString = @""; - string templateHeader = cssString + "
"; - string templateFooter = "
"; - - var entities = new List() - { - new MessageTemplate - { - Name = "Blog.BlogComment", - Subject = "%Store.Name%. New blog comment.", - Body = templateHeader + "

%Store.Name%



A new blog comment has been created for blog post \"%BlogComment.BlogPostTitle%\".

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Customer.BackInStock", - Subject = "%Store.Name%. Back in stock notification", - Body = templateHeader + "

%Store.Name%



Hello %Customer.FullName%,
Product \"%BackInStockSubscription.ProductName%\" is in stock.

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Customer.EmailValidationMessage", - Subject = "%Store.Name%. Email validation", - Body = templateHeader + "

%Store.Name%



To activate your account click here.

%Store.Name%" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Customer.NewPM", - Subject = "%Store.Name%. You have received a new private message", - Body = templateHeader + "

%Store.Name%



You have received a new private message.

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Customer.PasswordRecovery", - Subject = "%Store.Name%. Password recovery", - Body = templateHeader + "

%Store.Name%



To change your password click here.

%Store.Name%" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Customer.WelcomeMessage", - Subject = "Welcome to %Store.Name%", - Body = templateHeader + "We welcome you to %Store.Name%.

You can now take part in the various services we have to offer you. Some of these services include:

Permanent Cart - Any products added to your online cart remain there until you remove them, or check them out.
Address Book - We can now deliver your products to another address other than yours! This is perfect to send birthday gifts direct to the birthday-person themselves.
Order History - View your history of purchases that you have made with us.
Products Reviews - Share your opinions on products with our other customers.

For help with any of our online services, please email the store-owner: %Store.Email%.

Note: This email address was provided on our registration page. If you own the email and did not register on our site, please send an email to %Store.Email%." + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Forums.NewForumPost", - Subject = "%Store.Name%. New Post Notification.", - Body = templateHeader + "

%Store.Name%

A new post has been created in the topic \"%Forums.TopicName%\" at \"%Forums.ForumName%\" forum.

Click here for more info.

Post author: %Forums.PostAuthor%
Post body: %Forums.PostBody%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Forums.NewForumTopic", - Subject = "%Store.Name%. New Topic Notification.", - Body = templateHeader + "

%Store.Name%



A new topic \"%Forums.TopicName%\" has been created at \"%Forums.ForumName%\" forum.

Click here for more info.

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "GiftCard.Notification", - Subject = "%GiftCard.SenderName% has sent you a gift card for %Store.Name%", - Body = templateHeader + "

You have received a gift card for %Store.Name%

Dear %GiftCard.RecipientName%,

%GiftCard.SenderName% (%GiftCard.SenderEmail%) has sent you a %GiftCard.Amount% gift cart for %Store.Name%

You gift card code is %GiftCard.CouponCode%

%GiftCard.Message%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "NewCustomer.Notification", - Subject = "%Store.Name%. New customer registration", - Body = templateHeader + "

%Store.Name%



A new customer registered with your store. Below are the customer's details:
Full name: %Customer.FullName%
Email: %Customer.Email%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "NewReturnRequest.StoreOwnerNotification", - Subject = "%Store.Name%. New return request.", - Body = templateHeader + "

%Store.Name%



%Customer.FullName% has just submitted a new return request. Details are below:
Request ID: %ReturnRequest.ID%
Product: %ReturnRequest.Product.Quantity% x Product: %ReturnRequest.Product.Name%
Reason for return: %ReturnRequest.Reason%
Requested action: %ReturnRequest.RequestedAction%
Customer comments:
%ReturnRequest.CustomerComment%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "News.NewsComment", - Subject = "%Store.Name%. New news comment.", - Body = templateHeader + "

%Store.Name%



A new news comment has been created for news \"%NewsComment.NewsTitle%\".

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "NewsLetterSubscription.ActivationMessage", - Subject = "%Store.Name%. Subscription activation message.", - Body = templateHeader + "

Click here to confirm your subscription to our list.

If you received this email by mistake, simply delete it.

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "NewsLetterSubscription.DeactivationMessage", - Subject = "%Store.Name%. Subscription deactivation message.", - Body = templateHeader + "

Click here to unsubscribe from our newsletter list.

If you received this email by mistake, simply delete it.

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "NewVATSubmitted.StoreOwnerNotification", - Subject = "%Store.Name%. New VAT number is submitted.", - Body = templateHeader + "

%Store.Name%



%Customer.FullName% (%Customer.Email%) has just submitted a new VAT number. Details are below:
VAT number: %Customer.VatNumber%
VAT number status: %Customer.VatNumberStatus%
Received name: %VatValidationResult.Name%
Received address: %VatValidationResult.Address%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "OrderCancelled.CustomerNotification", - Subject = "%Store.Name%. Your order cancelled", - Body = templateHeader + "

%Store.Name%



Hello %Order.CustomerFullName%,
Your order has been cancelled. Below is the summary of the order.

Order Number: %Order.OrderNumber%
Order Details: %Order.OrderURLForCustomer%
Date Ordered: %Order.CreatedOn%



Billing Address
%Order.BillingFirstName% %Order.BillingLastName%
%Order.BillingAddress1%
%Order.BillingCity% %Order.BillingZipPostalCode%
%Order.BillingStateProvince% %Order.BillingCountry%



Shipping Address
%Order.ShippingFirstName% %Order.ShippingLastName%
%Order.ShippingAddress1%
%Order.ShippingCity% %Order.ShippingZipPostalCode%
%Order.ShippingStateProvince% %Order.ShippingCountry%

Shipping Method: %Order.ShippingMethod%
Payment Method: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "OrderCompleted.CustomerNotification", - Subject = "%Store.Name%. Your order completed", - Body = templateHeader + "

%Store.Name%



Hello %Order.CustomerFullName%,
Your order has been completed. Below is the summary of the order.

Order Number: %Order.OrderNumber%
Order Details: %Order.OrderURLForCustomer%
Date Ordered: %Order.CreatedOn%



Billing Address
%Order.BillingFirstName% %Order.BillingLastName%
%Order.BillingAddress1%
%Order.BillingCity% %Order.BillingZipPostalCode%
%Order.BillingStateProvince% %Order.BillingCountry%



Shipping Address
%Order.ShippingFirstName% %Order.ShippingLastName%
%Order.ShippingAddress1%
%Order.ShippingCity% %Order.ShippingZipPostalCode%
%Order.ShippingStateProvince% %Order.ShippingCountry%

Shipping Method: %Order.ShippingMethod%
Payment Method: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "ShipmentDelivered.CustomerNotification", - Subject = "Your order from %Store.Name% has been delivered.", - Body = templateHeader + "

%Store.Name%



Hello %Order.CustomerFullName%,
Good news! You order has been delivered.
Order Number: %Order.OrderNumber%
Order Details: %Order.OrderURLForCustomer%
Date Ordered: %Order.CreatedOn%



Billing Address
%Order.BillingFirstName% %Order.BillingLastName%
%Order.BillingAddress1%
%Order.BillingCity% %Order.BillingZipPostalCode%
%Order.BillingStateProvince% %Order.BillingCountry%



Shipping Address
%Order.ShippingFirstName% %Order.ShippingLastName%
%Order.ShippingAddress1%
%Order.ShippingCity% %Order.ShippingZipPostalCode%
%Order.ShippingStateProvince% %Order.ShippingCountry%

Shipping Method: %Order.ShippingMethod%
Payment Method: %Order.PaymentMethod%

Delivered Products:

%Shipment.Product(s)%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - - new MessageTemplate - { - Name = "OrderPlaced.CustomerNotification", - Subject = "Order receipt from %Store.Name%.", - Body = templateHeader + "

%Store.Name%



Hello %Order.CustomerFullName%,
Thanks for buying from %Store.Name%. Below is the summary of the order.

Order Number: %Order.OrderNumber%
Order Details: %Order.OrderURLForCustomer%
Date Ordered: %Order.CreatedOn%



Billing Address
%Order.BillingFirstName% %Order.BillingLastName%
%Order.BillingAddress1%
%Order.BillingCity% %Order.BillingZipPostalCode%
%Order.BillingStateProvince% %Order.BillingCountry%



Shipping Address
%Order.ShippingFirstName% %Order.ShippingLastName%
%Order.ShippingAddress1%
%Order.ShippingCity% %Order.ShippingZipPostalCode%
%Order.ShippingStateProvince% %Order.ShippingCountry%

Shipping Method: %Order.ShippingMethod%
Payment Method: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "OrderPlaced.StoreOwnerNotification", - Subject = "%Store.Name%. Purchase Receipt for Order #%Order.OrderNumber%", - Body = templateHeader + "

%Store.Name%



%Order.CustomerFullName% (%Order.CustomerEmail%) has just placed an order from your store. Below is the summary of the order.

Order Number: %Order.OrderNumber%
Date Ordered: %Order.CreatedOn%



Billing Address
%Order.BillingFirstName% %Order.BillingLastName%
%Order.BillingAddress1%
%Order.BillingCity% %Order.BillingZipPostalCode%
%Order.BillingStateProvince% %Order.BillingCountry%



Shipping Address
%Order.ShippingFirstName% %Order.ShippingLastName%
%Order.ShippingAddress1%
%Order.ShippingCity% %Order.ShippingZipPostalCode%
%Order.ShippingStateProvince% %Order.ShippingCountry%

Shipping Method: %Order.ShippingMethod%
Payment Method: %Order.PaymentMethod%

%Order.Product(s)%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "ShipmentSent.CustomerNotification", - Subject = "Your order from %Store.Name% has been shipped.", - Body = templateHeader + "

%Store.Name%



Hello %Order.CustomerFullName%!,
Good news! You order has been shipped.
Order Number: %Order.OrderNumber%
Order Details: %Order.OrderURLForCustomer%
Date Ordered: %Order.CreatedOn%



Billing Address
%Order.BillingFirstName% %Order.BillingLastName%
%Order.BillingAddress1%
%Order.BillingCity% %Order.BillingZipPostalCode%
%Order.BillingStateProvince% %Order.BillingCountry%



Shipping Address
%Order.ShippingFirstName% %Order.ShippingLastName%
%Order.ShippingAddress1%
%Order.ShippingCity% %Order.ShippingZipPostalCode%
%Order.ShippingStateProvince% %Order.ShippingCountry%

Shipping Method: %Order.ShippingMethod%
Payment Method: %Order.PaymentMethod%

Shipped Products:

%Shipment.Product(s)%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Product.ProductReview", - Subject = "%Store.Name%. New product review.", - Body = templateHeader + "

%Store.Name%



A new product review has been written for product \"%ProductReview.ProductName%\".

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "QuantityBelow.StoreOwnerNotification", - Subject = "%Store.Name%. Quantity below notification. %Product.Name%", - Body = templateHeader + "

%Store.Name%



%Product.Name% (ID: %Product.ID%, SKU: %Product.Sku%) low quantity.

Quantity: %Product.StockQuantity%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "ReturnRequestStatusChanged.CustomerNotification", - Subject = "%Store.Name%. Return request status was changed.", - Body = templateHeader + "

%Store.Name%



Hello %Customer.FullName%,
Your return request #%ReturnRequest.ID% status has been changed: %ReturnRequest.Status%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Service.EmailAFriend", - Subject = "%Store.Name%. Referred Item", - Body = templateHeader + "

%Store.Name%



%EmailAFriend.Email% was shopping on %Store.Name% and wanted to share the following item with you.

%Product.Name%
%Product.ShortDescription%

For more info click here


%EmailAFriend.PersonalMessage%

%Store.Name%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Wishlist.EmailAFriend", - Subject = "%Store.Name%. Wishlist", - Body = templateHeader + "

%Store.Name%



%Wishlist.Email% was shopping on %Store.Name% and wanted to share a wishlist with you.


For more info click here


%Wishlist.PersonalMessage%

%Store.Name%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Customer.NewOrderNote", - Subject = "%Store.Name%. New order note has been added", - Body = templateHeader + "

%Store.Name%



Hello %Customer.FullName%,
New order note has been added to your account:
\"%Order.NewNoteText%\".
%Order.OrderURLForCustomer%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "RecurringPaymentCancelled.StoreOwnerNotification", - Subject = "%Store.Name%. Recurring payment cancelled", - Body = templateHeader + "

%Store.Name%



%Customer.FullName% (%Customer.Email%) has just cancelled a recurring payment ID=%RecurringPayment.ID%.

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = "Product.AskQuestion", - Subject = "%Store.Name% - Question concerning '%Product.Name%' from %ProductQuestion.SenderName%", - Body = templateHeader + "

%ProductQuestion.Message%

SKU: %Product.Sku%
Email: %ProductQuestion.SenderEmail%
Name: %ProductQuestion.SenderName%
Phone: %ProductQuestion.SenderPhone%

" + templateFooter, - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - }; - this.Alter(entities); - return entities; - } - public IList Topics() { var entities = new List() @@ -4114,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
" }, @@ -4130,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 @@ -4138,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 @@ -4146,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 @@ -4154,16 +3921,19 @@ public IList Topics() SystemName = "LoginRegistrationInfo", IncludeInSitemap = false, IsPasswordProtected = false, - Title = "About login / registration", - Body = "

Put your login / registration information here. You can edit this in the admin site.

" + 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 { SystemName = "PrivacyInfo", IncludeInSitemap = false, IsPasswordProtected = false, + ShortTitle = "Privacy", Title = "Privacy policy", - Body = "

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

" + Body = "

" }, new Topic { @@ -4174,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", @@ -4206,8 +3975,10 @@ public IList Topics() public IList Settings() { - var seName = GetSeName("slider-bg"); - var imgContentSliderBg = _ctx.Set().Where(x => x.SeoFilename == seName).FirstOrDefault(); + 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 { @@ -4217,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", @@ -4281,27 +4049,23 @@ public IList Settings() "Payments.Prepayment" } }, - new TaxSettings() - { - }, - new BlogSettings() + new TaxSettings { }, - new NewsSettings() + new BlogSettings { }, - new ForumSettings() + new NewsSettings { }, - new EmailAccountSettings() + new ForumSettings { - DefaultEmailAccountId = _ctx.Set().First().Id }, - new ContentSliderSettings() + new EmailAccountSettings { - BackgroundPictureId = imgContentSliderBg.Id, + DefaultEmailAccountId = defaultEmailAccountId }, - new ThemeSettings() + new ThemeSettings { } }; @@ -4719,15 +4483,9 @@ public IList ProductTemplates() { new ProductTemplate { - Name = "Simple product", - ViewPath = "ProductTemplate.Simple", + Name = "Default Product Template", + ViewPath = "Product", DisplayOrder = 10 - }, - new ProductTemplate - { - Name = "Grouped product", - ViewPath = "ProductTemplate.Grouped", - DisplayOrder = 100 } }; this.Alter(entities); @@ -4795,7 +4553,7 @@ public IList ScheduleTasks() new ScheduleTask { Name = "Clear cache", - CronExpression = "0 */4 * * *", // Every 04 hours + CronExpression = "0 */12 * * *", // Every 12 hours Type = "SmartStore.Services.Caching.ClearCacheTask, SmartStore.Services", Enabled = false, StopOnError = false, @@ -4803,9 +4561,9 @@ public IList ScheduleTasks() new ScheduleTask { Name = "Update currency exchange rates", - CronExpression = "0/15 * * * *", // Every 15 minutes + CronExpression = "0 */6 * * *", // Every 6 hours Type = "SmartStore.Services.Directory.UpdateExchangeRateTask, SmartStore.Services", - Enabled = true, + Enabled = false, StopOnError = false, }, new ScheduleTask @@ -4843,93 +4601,9 @@ public IList ScheduleTasks() public IList SpecificationAttributes() { - // var entities = new List - - #region predefined older attributes - - // var sa1 = new SpecificationAttribute - //{ - // Name = "Screensize", - // DisplayOrder = 1, - //}; - //sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "10.0''", - // DisplayOrder = 3, - //}); - //sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "14.1''", - // DisplayOrder = 4, - //}); - //sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "15.4''", - // DisplayOrder = 5, - //}); - //sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "16.0''", - // DisplayOrder = 6, - //}); - //var sa2 = new SpecificationAttribute - //{ - // Name = "CPU Type", - // DisplayOrder = 2, - //}; - //sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "AMD", - // DisplayOrder = 1, - //}); - //sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "Intel", - // DisplayOrder = 2, - //}); - //var sa3 = new SpecificationAttribute - //{ - // Name = "Memory", - // DisplayOrder = 3, - //}; - //sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "1 GB", - // DisplayOrder = 1, - //}); - //sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "3 GB", - // DisplayOrder = 2, - //}); - //var sa4 = new SpecificationAttribute - //{ - // Name = "Hardrive", - // DisplayOrder = 5, - //}; - //sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "320 GB", - // DisplayOrder = 7, - //}); - //sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "250 GB", - // DisplayOrder = 4, - //}); - //sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() - //{ - // Name = "160 GB", - // DisplayOrder = 3, - //}); - - #endregion predefined older attributes - - #region new attributes - - #region sa1 CPU-Manufacturer + #region sa1 CPU-Manufacturer - var sa1 = new SpecificationAttribute + var sa1 = new SpecificationAttribute { Name = "CPU-Manufacturer", DisplayOrder = 1, @@ -4966,7 +4640,7 @@ public IList SpecificationAttributes() var sa2 = new SpecificationAttribute { - Name = "color", + Name = "Color", DisplayOrder = 2, }; sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5011,7 +4685,7 @@ public IList SpecificationAttributes() var sa3 = new SpecificationAttribute { - Name = "harddisk capacity", + Name = "Harddisk capacity", DisplayOrder = 3, }; sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5136,7 +4810,7 @@ public IList SpecificationAttributes() var sa6 = new SpecificationAttribute { - Name = "ports", + Name = "Ports", DisplayOrder = 6, }; sa6.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5206,7 +4880,7 @@ public IList SpecificationAttributes() var sa8 = new SpecificationAttribute { - Name = "material", + Name = "Material", DisplayOrder = 8, }; sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5226,17 +4900,82 @@ public IList SpecificationAttributes() }); sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() { - Name = "aluminum", + Name = "aluminium", DisplayOrder = 4, }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "leather", + DisplayOrder = 5, + }); + + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "nylon", + DisplayOrder = 6, + }); + + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "silicone", + DisplayOrder = 7, + }); + + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "ceramic", + DisplayOrder = 8, + }); + + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "cotton", + DisplayOrder = 9, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "100% organic cotton", + DisplayOrder = 10, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "polyamide", + DisplayOrder = 11, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "rubber", + DisplayOrder = 12, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "wood", + DisplayOrder = 13, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "glass", + DisplayOrder = 14, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "spandex", + DisplayOrder = 15, + }); + sa8.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "polyester", + DisplayOrder = 16, + }); + #endregion sa8 material #region sa9 movement var sa9 = new SpecificationAttribute { - Name = "movement", + Name = "Movement", DisplayOrder = 9, }; sa9.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5261,7 +5000,7 @@ public IList SpecificationAttributes() var sa10 = new SpecificationAttribute { - Name = "clasp", + Name = "Clasp", DisplayOrder = 10, }; sa10.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5286,7 +5025,7 @@ public IList SpecificationAttributes() var sa11 = new SpecificationAttribute { - Name = "window material", + Name = "Window material", DisplayOrder = 11, }; sa11.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5306,7 +5045,7 @@ public IList SpecificationAttributes() var sa12 = new SpecificationAttribute { - Name = "language", + Name = "Language", DisplayOrder = 12, }; sa12.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5336,7 +5075,7 @@ public IList SpecificationAttributes() var sa13 = new SpecificationAttribute { - Name = "edition", + Name = "Edition", DisplayOrder = 13, }; sa13.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5356,7 +5095,7 @@ public IList SpecificationAttributes() var sa14 = new SpecificationAttribute { - Name = "category", + Name = "Category", DisplayOrder = 14, }; sa14.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5441,7 +5180,7 @@ public IList SpecificationAttributes() var sa16 = new SpecificationAttribute { - Name = "type of mass-storage", + Name = "Type of mass-storage", DisplayOrder = 16, }; sa16.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5471,12 +5210,12 @@ public IList SpecificationAttributes() }; sa17.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() { - Name = "3,5", + Name = "3.5", DisplayOrder = 1, }); sa17.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() { - Name = "2,5", + Name = "2.5", DisplayOrder = 2, }); @@ -5511,7 +5250,7 @@ public IList SpecificationAttributes() var sa19 = new SpecificationAttribute { - Name = "music genre", + Name = "Music genre", DisplayOrder = 19, }; sa19.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5556,7 +5295,7 @@ public IList SpecificationAttributes() var sa20 = new SpecificationAttribute { - Name = "manufacturer", + Name = "Manufacturer", DisplayOrder = 20, }; sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() @@ -5634,3029 +5373,7597 @@ public IList SpecificationAttributes() Name = "Festina", DisplayOrder = 15, }); + sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Seiko", + DisplayOrder = 16, + }); + sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Tissot", + DisplayOrder = 17, + }); + sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Breitling", + DisplayOrder = 18, + }); + sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Adidas", + DisplayOrder = 19, + }); + sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Nike", + DisplayOrder = 20, + }); + sa20.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Puma", + DisplayOrder = 21, + }); - #endregion sa20 manufacturer + #endregion sa20 manufacturer - #endregion new attributes + #region sa21 Watches for whom - var entities = new List - { - sa1,sa2,sa3,sa4,sa5,sa6,sa7,sa8,sa9,sa10,sa11,sa12,sa13,sa14,sa15,sa16,sa17,sa18,sa19,sa20 - }; + var sa21 = new SpecificationAttribute + { + Name = "For whom", + DisplayOrder = 21, + }; + sa21.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "For him", + DisplayOrder = 1, + }); + sa21.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "For her", + DisplayOrder = 2, + }); - this.Alter(entities); - return entities; - } + #endregion sa11 Watches for whom - public IList ProductAttributes() - { - var entities = new List - { - new ProductAttribute - { - Name = "Color", - Alias = "Color", - }, - new ProductAttribute - { - Name = "Custom Text", - Alias = "Custom Text", - }, - new ProductAttribute - { - Name = "HDD", - Alias = "HDD", - }, - new ProductAttribute - { - Name = "OS", - Alias = "OS", - }, - new ProductAttribute - { - Name = "Processor", - Alias = "Processor", - }, - new ProductAttribute - { - Name = "RAM", - Alias = "RAM", - }, - new ProductAttribute - { - Name = "Size", - Alias = "Size" - }, - new ProductAttribute - { - Name = "Software", - Alias = "Software", - }, - new ProductAttribute - { - Name = "Game", - Alias = "Game" - }, - new ProductAttribute - { - Name = "Color", - Alias = "iPhone color" - }, - new ProductAttribute - { - Name = "Memory capacity", - Alias = "Memory capacity" - } - }; + #region sa22 Offer - this.Alter(entities); - return entities; - } + var sa22 = new SpecificationAttribute + { + Name = "Offer", + DisplayOrder = 22, + }; - public IList ProductVariantAttributes() - { - var entities = new List(); + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Clearance", + DisplayOrder = 1, + }); - #region iPhone + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Permanent low price", + DisplayOrder = 2, + }); - var productIphone = _ctx.Set().First(x => x.Sku == "Apple-1001"); - var attributeMemoryCapacity = _ctx.Set().First(x => x.Alias == "Memory capacity"); - var attributeIphoneIphoneColor = _ctx.Set().First(x => x.Alias == "iPhone color"); + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Promotion", + DisplayOrder = 3, + }); - var attributeIphoneMemoryCapacity = new ProductVariantAttribute() - { - Product = productIphone, - ProductAttribute = attributeMemoryCapacity, - IsRequired = true, - DisplayOrder = 1, - AttributeControlType = AttributeControlType.DropdownList - }; + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Reduced price", + DisplayOrder = 4, + }); - attributeIphoneMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "16 GB", - Alias = "16gb", - IsPreSelected = true, - DisplayOrder = 1, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple - }); + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Special Buy", + DisplayOrder = 5, + }); - attributeIphoneMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "64 GB", - Alias = "64gb", - DisplayOrder = 2, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple, - PriceAdjustment = 100.0M - }); + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Offer of the day", + DisplayOrder = 6, + }); - attributeIphoneMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "128 GB", - Alias = "128gb", - DisplayOrder = 3, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple, - PriceAdjustment = 200.0M - }); + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Weekly offer", + DisplayOrder = 7, + }); - entities.Add(attributeIphoneMemoryCapacity); + sa22.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Best Price", + DisplayOrder = 8, + }); + #endregion sa22 Offer - var attributeIphoneColor = new ProductVariantAttribute() - { - Product = productIphone, - ProductAttribute = attributeIphoneIphoneColor, - IsRequired = true, - DisplayOrder = 2, - AttributeControlType = AttributeControlType.DropdownList - }; + #region sa23 Size - attributeIphoneColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Silver", - Alias = "silver", - IsPreSelected = true, - DisplayOrder = 1, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple - }); + var sa23 = new SpecificationAttribute + { + Name = "Size", + DisplayOrder = 23, + }; - attributeIphoneColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Gold", - Alias = "gold", - DisplayOrder = 2, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple - }); + sa23.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "XS", + DisplayOrder = 1, + }); - attributeIphoneColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Space gray", - Alias = "spacegray", - DisplayOrder = 3, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple - }); + sa23.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "S", + DisplayOrder = 2, + }); - entities.Add(attributeIphoneColor); + sa23.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "M", + DisplayOrder = 3, + }); - #endregion iPhone + sa23.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "L", + DisplayOrder = 4, + }); - #region attributeDualshock3ControllerColor + sa23.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "XL", + DisplayOrder = 5, + }); - var productPs3 = _ctx.Set().First(x => x.Sku == "Sony-PS399000"); - var attributeColor = _ctx.Set().First(x => x.Alias == "Color"); + sa23.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "XXL", + DisplayOrder = 6, + }); - var attributeDualshock3ControllerColor = new ProductVariantAttribute() - { - Product = productPs3, - ProductAttribute = attributeColor, - IsRequired = true, - DisplayOrder = 1, - AttributeControlType = AttributeControlType.DropdownList - }; - attributeDualshock3ControllerColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Black", - Alias = "black", - IsPreSelected = true, - DisplayOrder = 1, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple - }); + #endregion sa23 Size - attributeDualshock3ControllerColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "White", - Alias = "white", - PriceAdjustment = 10.0M, - DisplayOrder = 2, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.Simple - }); + #region sa24 diameter - entities.Add(attributeDualshock3ControllerColor); + var sa24 = new SpecificationAttribute + { + Name = "Diameter", + DisplayOrder = 24, + }; + + sa24.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "38mm", + DisplayOrder = 1, + }); + + sa24.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "40mm", + DisplayOrder = 2, + }); - #endregion attributeDualshock3ControllerColor + sa24.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "44mm", + DisplayOrder = 3, + }); - #region attributePs3OneGameFree + #endregion sa24 diameter - var productPs3OneGameFree = _ctx.Set().First(x => x.Sku == "Sony-PS310111"); - var attributeGames = _ctx.Set().First(x => x.Alias == "Game"); + #region sa25 closure - var attributePs3OneGameFree = new ProductVariantAttribute() - { - Product = productPs3OneGameFree, - ProductAttribute = attributeGames, - IsRequired = true, - DisplayOrder = 1, - AttributeControlType = AttributeControlType.DropdownList - }; + var sa25 = new SpecificationAttribute + { + Name = "Closure", + DisplayOrder = 25, + }; - attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Assassin's Creed III", - Alias = "Assassin's Creed III", - DisplayOrder = 1, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.ProductLinkage, - LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-acreed3").Id - }); + sa25.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "snap closure", + DisplayOrder = 1, + }); - attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Watch Dogs", - Alias = "Watch Dogs", - DisplayOrder = 2, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.ProductLinkage, - LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-watchdogs").Id - }); + sa25.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "folding clasp", + DisplayOrder = 2, + }); - attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Prince of Persia \"The Forgotten Sands\"", - Alias = "Prince of Persia \"The Forgotten Sands\"", - DisplayOrder = 3, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.ProductLinkage, - LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-princepersia").Id - }); + sa25.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "thorn close", + DisplayOrder = 3, + }); - attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() - { - Name = "Driver San Francisco", - Alias = "Driver San Francisco", - DisplayOrder = 4, - Quantity = 1, - ValueType = ProductVariantAttributeValueType.ProductLinkage, - LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-driversanfrancisco").Id - }); + #endregion sa25 closure - entities.Add(attributePs3OneGameFree); + #region sa26 facial shape - #endregion attributePs3OneGameFree + var sa26 = new SpecificationAttribute + { + Name = "Facial shape", + DisplayOrder = 26, + }; - this.Alter(entities); - return entities; - } + sa26.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "oval", + DisplayOrder = 1, + }); - public IList ProductVariantAttributeCombinations() - { - var entities = new List(); + sa26.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "round", + DisplayOrder = 2, + }); - string attributeXml = "{1}"; + sa26.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "heart shaped", + DisplayOrder = 3, + }); - var productPs3 = _ctx.Set().First(x => x.Sku == "Sony-PS399000"); - var ps3PictureIds = productPs3.ProductPictures.Select(pp => pp.PictureId).ToList(); - var picturesPs3 = _ctx.Set().Where(x => ps3PictureIds.Contains(x.Id)).ToList(); + sa26.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "angular", + DisplayOrder = 4, + }); - var attributeColor = _ctx.Set().First(x => x.Alias == "Color"); - var productAttributeColor = _ctx.Set().First(x => x.ProductId == productPs3.Id && x.ProductAttributeId == attributeColor.Id); - var attributeColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == productAttributeColor.Id).ToList(); + #endregion sa26 facial shape - entities.Add(new ProductVariantAttributeCombination() - { - Product = productPs3, - Sku = productPs3.Sku + "-B", - AttributesXml = attributeXml.FormatWith(productAttributeColor.Id, attributeColorValues.First(x => x.Alias == "black").Id), - StockQuantity = 10000, - AllowOutOfStockOrders = true, - IsActive = true, - AssignedPictureIds = picturesPs3.First(x => x.SeoFilename.EndsWith("-black")).Id.ToString() - }); + #region sa27 storage capacity - entities.Add(new ProductVariantAttributeCombination() + var sa27 = new SpecificationAttribute + { + Name = "Storage capacity", + DisplayOrder = 27, + }; + + sa27.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "32 GB", + DisplayOrder = 1, + }); + + sa27.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "64 GB", + DisplayOrder = 2, + }); + + sa27.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "128 GB", + DisplayOrder = 3, + }); + + #endregion sa27 facial shape + + #region sa28 Dial window material type + + var sa28 = new SpecificationAttribute + { + Name = "Dial window material type", + DisplayOrder = 28, + }; + + sa28.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Mineral", + DisplayOrder = 1, + }); + + sa28.SpecificationAttributeOptions.Add(new SpecificationAttributeOption() + { + Name = "Sapphire", + DisplayOrder = 2, + }); + + #endregion sa28 Dial window material type + + var entities = new List { - Product = productPs3, - Sku = productPs3.Sku + "-W", - AttributesXml = attributeXml.FormatWith(productAttributeColor.Id, attributeColorValues.First(x => x.Alias == "white").Id), - StockQuantity = 10000, - AllowOutOfStockOrders = true, - IsActive = true, - AssignedPictureIds = picturesPs3.First(x => x.SeoFilename.EndsWith("-white")).Id.ToString() - }); + sa1,sa2,sa3,sa4,sa5,sa6,sa7,sa8,sa9,sa10,sa11,sa12,sa13,sa14,sa15,sa16,sa17,sa18,sa19,sa20,sa21,sa22,sa23,sa24,sa25,sa26,sa27,sa28 + }; + this.Alter(entities); return entities; } - public IList ProductTags() + public IList ProductAttributes() { - #region tag gift - var productTagGift = new ProductTag + var entities = new List { - Name = "gift" - }; + new ProductAttribute + { + Name = "Color", + Alias = "color" + }, + new ProductAttribute + { + Name = "Custom Text", + Alias = "custom-text" + }, + new ProductAttribute + { + Name = "HDD", + Alias = "hdd" + }, + new ProductAttribute + { + Name = "OS", + Alias = "os" + }, + new ProductAttribute + { + Name = "Processor", + Alias = "processor" + }, + new ProductAttribute + { + Name = "RAM", + Alias = "ram", + }, + new ProductAttribute + { + Name = "Size", + Alias = "size" + }, + new ProductAttribute + { + Name = "Software", + Alias = "software" + }, + new ProductAttribute + { + Name = "Game", + Alias = "game" + }, + new ProductAttribute + { + Name = "Color", + Alias = "iphone-color" + }, + new ProductAttribute + { + Name = "Color", + Alias = "ipad-color" + }, + new ProductAttribute + { + Name = "Memory capacity", + Alias = "memory-capacity" + }, + new ProductAttribute + { + Name = "Width", + Alias = "width" + }, + new ProductAttribute + { + Name = "Length", + Alias = "length" + }, + new ProductAttribute + { + Name = "Plate", + Alias = "plate" + }, + new ProductAttribute + { + Name = "Plate Thickness", + Alias = "plate-thickness" + }, + new ProductAttribute + { + Name = "Ballsize", + Alias = "ballsize" + }, + new ProductAttribute + { + Name = "Leather color", + Alias = "leather-color" + }, + new ProductAttribute + { + Name = "Seat Shell", + Alias = "seat-shell" + }, + new ProductAttribute + { + Name = "Base", + Alias = "base" + }, + new ProductAttribute + { + Name = "Material", + Alias = "material" + }, + new ProductAttribute + { + Name = "Style", + Alias = "style" + }, + new ProductAttribute + { + Name = "Controller", + Alias = "controller" + }, + new ProductAttribute + { + Name = "Framecolor", + Alias = "framecolor" + }, + new ProductAttribute + { + Name = "Lenscolor", + Alias = "lenscolor" + }, + new ProductAttribute + { + Name = "Lenstype", + Alias = "lenstype" + }, + new ProductAttribute + { + Name = "Lenscolor", + Alias = "wayfarerlenscolor" + }, + new ProductAttribute + { + Name = "Framecolor", + Alias = "wayfarerframecolor" + } + }; - _ctx.Set().Where(pt => pt.MetaTitle == "$5 Virtual Gift Card").First().ProductTags.Add(productTagGift); - _ctx.Set().Where(pt => pt.MetaTitle == "$25 Virtual Gift Card").First().ProductTags.Add(productTagGift); - _ctx.Set().Where(pt => pt.MetaTitle == "$50 Virtual Gift Card").First().ProductTags.Add(productTagGift); + this.Alter(entities); + return entities; + } - #endregion tag gift + public IList ProductAttributeOptionsSets() + { + var entities = new List(); + var colorAttribute = _ctx.Set().First(x => x.Alias == "color"); - #region tag computer - var productTagComputer = new ProductTag - { - Name = "computer" - }; + entities.Add(new ProductAttributeOptionsSet + { + Name = "General colors", + ProductAttributeId = colorAttribute.Id + }); - _ctx.Set().Where(pt => pt.MetaTitle == "Dell Optiplex 3010 DT Base").FirstOrDefault().ProductTags.Add(productTagComputer); - _ctx.Set().Where(pt => pt.MetaTitle == "Acer Aspire One 8.9").FirstOrDefault().ProductTags.Add(productTagComputer); - _ctx.Set().Where(pt => pt.MetaTitle == "Dell Inspiron One 23").FirstOrDefault().ProductTags.Add(productTagComputer); + this.Alter(entities); + return entities; + } - #endregion tag computer + public IList ProductAttributeOptions() + { + var entities = new List(); + var colorAttribute = _ctx.Set().First(x => x.Alias == "color"); + var sets = _ctx.Set().ToList(); - #region tag notebook - var productTagNotebook = new ProductTag + var generalColors = new[] { - Name = "notebook" + new { Color = "Red", Code = "#ff0000" }, + new { Color = "Green", Code = "#008000" }, + new { Color = "Blue", Code = "#0000ff" }, + new { Color = "Yellow", Code = "#ffff00" }, + new { Color = "Black", Code = "#000000" }, + new { Color = "White", Code = "#ffffff" }, + new { Color = "Gray", Code = "#808080" }, + new { Color = "Silver", Code = "#dddfde" }, + new { Color = "Brown", Code = "#a52a2a" }, }; - _ctx.Set().Where(pt => pt.MetaTitle == "Acer Aspire One 8.9").FirstOrDefault().ProductTags.Add(productTagNotebook); - - #endregion productTagNotebook - - #region tag compact - var productTagCompact = new ProductTag - { - Name = "compact" - }; + for (var i = 0; i < generalColors.Length; ++i) + { + entities.Add(new ProductAttributeOption + { + ProductAttributeOptionsSetId = sets[0].Id, + Alias = generalColors[i].Color.ToLower(), + Name = generalColors[i].Color, + Quantity = 1, + DisplayOrder = i + 1, + Color = generalColors[i].Code + }); + } - _ctx.Set().Where(pt => pt.MetaTitle == "Acer Aspire One 8.9").FirstOrDefault().ProductTags.Add(productTagCompact); + this.Alter(entities); + return entities; + } - #endregion productTagCompact + public IList ProductVariantAttributes() + { + var entities = new List(); + var attrColor = _ctx.Set().First(x => x.Alias == "color"); + var attrController = _ctx.Set().First(x => x.Alias == "controller"); + var attrSize = _ctx.Set().First(x => x.Alias == "size"); + var attrGames = _ctx.Set().First(x => x.Alias == "game"); + var attrBallsize = _ctx.Set().First(x => x.Alias == "ballsize"); + var attrMemoryCapacity = _ctx.Set().First(x => x.Alias == "memory-capacity"); + var attrLensType = _ctx.Set().First(x => x.Alias == "lenstype"); + var attrFramecolor = _ctx.Set().First(x => x.Alias =="framecolor"); + var attrLenscolor = _ctx.Set().First(x => x.Alias == "lenscolor"); + var attrIphoneColor = _ctx.Set().First(x => x.Alias == "iphone-color"); + var attr97iPadColor = _ctx.Set().First(x => x.Alias == "ipad-color"); + var attrWidth = _ctx.Set().First(x => x.Alias == "width"); + var attrLength = _ctx.Set().First(x => x.Alias == "length"); + var attrPlate = _ctx.Set().First(x => x.Alias == "plate"); + var attrPlateThickness = _ctx.Set().First(x => x.Alias == "plate-thickness"); + var attrLeatherColor = _ctx.Set().First(x => x.Alias == "leather-color"); + var attrSeatShell = _ctx.Set().First(x => x.Alias == "seat-shell"); + var attrBase = _ctx.Set().First(x => x.Alias == "base"); + var attrMaterial = _ctx.Set().First(x => x.Alias == "material"); + var attrWayfarerLenscolor = _ctx.Set().First(x => x.Alias == "wayfarerlenscolor"); + var attrWayfarerFramecolor = _ctx.Set().First(x => x.Alias == "wayfarerframecolor"); + + var generalColors = new[] + { + new { Name = "Black", Color = "#000000" }, + new { Name = "White", Color = "#ffffff" }, + new { Name = "Anthracite", Color = "#32312f" }, + new { Name = "Fuliginous", Color = "#5F5B5C" }, + new { Name = "Light grey", Color = "#e3e3e5" }, + new { Name = "Natural", Color = "#BBB98B" }, + new { Name = "Biscuit", Color = "#e0ccab" }, + new { Name = "Beige", Color = "#d1bc8a" }, + new { Name = "Hazelnut", Color = "#94703e" }, + new { Name = "Brown", Color = "#755232" }, + new { Name = "Dark brown", Color = "#27160F" }, + new { Name = "Dark green", Color = "#0a3210" }, + new { Name = "Blue", Color = "#0000ff" }, + new { Name = "Cognac", Color = "#e9aa1b" }, + new { Name = "Yellow", Color = "#e6e60c" }, + new { Name = "Orange", Color = "#ff6501" }, + new { Name = "Tomato red", Color = "#b10101" }, + new { Name = "Red", Color = "#fe0000" }, + new { Name = "Dark red", Color = "#5e0000" } + }; + + + #region Oakley custom flak + + var productCustomFlak = _ctx.Set().First(x => x.Sku == "P-3002"); + + var attributeLensType = new ProductVariantAttribute() + { + Product = productCustomFlak, + ProductAttribute = attrLensType, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.RadioList + }; - #region tag book - var productTagBook = new ProductTag - { - Name = "book" - }; + attributeLensType.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Standard", + Alias = "standard", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 100.0M + }); + + attributeLensType.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Polarized", + Alias = "polarized", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 200.0M + }); + attributeLensType.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Prizm", + Alias = "prizm", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 200.0M + }); - _ctx.Set().Where(pt => pt.MetaTitle == "Überman: The novel").First().ProductTags.Add(productTagBook); - _ctx.Set().Where(pt => pt.MetaTitle == "Best Grilling Recipes").First().ProductTags.Add(productTagBook); - _ctx.Set().Where(pt => pt.MetaTitle == "Car of superlatives").First().ProductTags.Add(productTagBook); - _ctx.Set().Where(pt => pt.MetaTitle == "Picture Atlas Motorcycles").First().ProductTags.Add(productTagBook); - _ctx.Set().Where(pt => pt.MetaTitle == "The Car Book").First().ProductTags.Add(productTagBook); - _ctx.Set().Where(pt => pt.MetaTitle == "Fast Cars").First().ProductTags.Add(productTagBook); - _ctx.Set().Where(pt => pt.MetaTitle == "Motorcycle Adventures").First().ProductTags.Add(productTagBook); + entities.Add(attributeLensType); - #endregion tag book - #region tag cooking - var productTagCooking = new ProductTag - { - Name = "cooking" - }; + var attributeFramecolor = new ProductVariantAttribute() + { + Product = productCustomFlak, + ProductAttribute = attrFramecolor, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes + }; - _ctx.Set().Where(pt => pt.MetaTitle == "Überman: The novel").FirstOrDefault().ProductTags.Add(productTagCooking); - _ctx.Set().Where(pt => pt.MetaTitle == "Best Grilling Recipes").FirstOrDefault().ProductTags.Add(productTagCooking); + attributeFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Matte Black", + Alias = "matteblack", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#2d2d2d" + }); + + attributeFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Polishedwhite", + Alias = "polishedwhite", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#f5f5f5" + }); - #endregion tag cooking + attributeFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Sky Blue", + Alias = "skyblue", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#4187f6" + }); - #region tag cars - var productTagCars = new ProductTag - { - Name = "cars" - }; + attributeFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Orange Flare", + Alias = "orangeflare", + DisplayOrder = 4, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#f55700" + }); - _ctx.Set().Where(pt => pt.MetaTitle == "The Car Book").FirstOrDefault().ProductTags.Add(productTagCars); - _ctx.Set().Where(pt => pt.MetaTitle == "Fast Cars").FirstOrDefault().ProductTags.Add(productTagCars); + attributeFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Redline", + Alias = "redline", + DisplayOrder = 5, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#cf0a02" + }); - #endregion tag cars + entities.Add(attributeFramecolor); - #region tag motorbikes - var productTagMotorbikes = new ProductTag - { - Name = "motorbikes" - }; + var attributeLenscolor = new ProductVariantAttribute() + { + Product = productCustomFlak, + ProductAttribute = attrLenscolor, + IsRequired = true, + DisplayOrder = 3, + AttributeControlType = AttributeControlType.Boxes + }; - _ctx.Set().Where(pt => pt.MetaTitle == "Fast Cars").FirstOrDefault().ProductTags.Add(productTagMotorbikes); - _ctx.Set().Where(pt => pt.MetaTitle == "Motorcycle Adventures").FirstOrDefault().ProductTags.Add(productTagMotorbikes); + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Gray", + Alias = "gray", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#7A798B" + }); + + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Sapphire Iridium", + Alias = "sapphireiridium", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#4460BB" + }); - #endregion tag motorbikes + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Violet Iridium", + Alias = "violetiridium", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#5C5A89" + }); - #region tag Intel - var productTagIntel = new ProductTag - { - Name = "Intel" - }; + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Jade Iridium", + Alias = "jadeiridium", + DisplayOrder = 4, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#376559" + }); - _ctx.Set().Where(pt => pt.MetaTitle == "Dell Inspiron One 23").FirstOrDefault().ProductTags.Add(productTagIntel); - _ctx.Set().Where(pt => pt.MetaTitle == "Dell Optiplex 3010 DT Base").FirstOrDefault().ProductTags.Add(productTagIntel); - _ctx.Set().Where(pt => pt.MetaTitle == "Acer Aspire One 8.9").FirstOrDefault().ProductTags.Add(productTagIntel); + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Ruby Iridium", + Alias = "rubyiridium", + DisplayOrder = 5, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#CCAD12" + }); - #endregion tag Intel + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "24K Iridium", + Alias = "24kiridium", + DisplayOrder = 6, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#CE9D12" + }); - #region tag Dell - var productTagDell = new ProductTag - { - Name = "Dell" - }; + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Positive Red Iridium", + Alias = "positiverediridium", + DisplayOrder = 7, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#764CDC" + }); + + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Clear", + Alias = "clear", + DisplayOrder = 7, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#e2e2e3" + }); + attributeLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Fire Iridium", + Alias = "fireiridium", + DisplayOrder = 7, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#E2C724" + }); - _ctx.Set().Where(pt => pt.MetaTitle == "Dell Inspiron One 23").FirstOrDefault().ProductTags.Add(productTagDell); - _ctx.Set().Where(pt => pt.MetaTitle == "Dell Optiplex 3010 DT Base").FirstOrDefault().ProductTags.Add(productTagDell); + entities.Add(attributeLenscolor); - #endregion tag Dell + #endregion Oakley custom flak - #region tag iPhone - var productTagIphone = new ProductTag - { - Name = "iPhone" - }; - _ctx.Set().Where(pt => pt.MetaTitle == "Apple iPhone 6").FirstOrDefault().ProductTags.Add(productTagIphone); + #region wayfarer - #endregion tag iPhone + var productWayfarer = _ctx.Set().First(x => x.Sku == "P-3003"); + var wayfarerFramePictures = _ctx.Set().Where(x => x.SeoFilename.StartsWith("wayfarer_")).ToList(); - #region tag mp3 - var productTagMP3 = new ProductTag - { - Name = "mp3" - }; + var attributeWayfarerLenscolor = new ProductVariantAttribute() + { + Product = productWayfarer, + ProductAttribute = attrWayfarerLenscolor, + IsRequired = true, + DisplayOrder = 3, + AttributeControlType = AttributeControlType.Boxes + }; - _ctx.Set().Where(pt => pt.MetaTitle == "Antonio Vivaldi: spring").FirstOrDefault().ProductTags.Add(productTagMP3); - _ctx.Set().Where(pt => pt.MetaTitle == "Ludwig van Beethoven: Für Elise").FirstOrDefault().ProductTags.Add(productTagMP3); + attributeWayfarerLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Blue-Gray classic", + Alias = "blue-gray-classic", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#3e4659" + }); + + attributeWayfarerLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Brown course", + Alias = "brown-course", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#3e4659" + }); - #endregion tag mp3 + attributeWayfarerLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Gray course", + Alias = "gray-course", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#727377" + }); - #region tag download - var productTagDownload = new ProductTag - { - Name = "download" - }; + attributeWayfarerLenscolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Green classic", + Alias = "green-classic", + DisplayOrder = 4, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#3c432e" + }); - _ctx.Set().Where(pt => pt.MetaTitle == "Antonio Vivaldi: spring").FirstOrDefault().ProductTags.Add(productTagDownload); - _ctx.Set().Where(pt => pt.MetaTitle == "Ludwig van Beethoven: Für Elise").FirstOrDefault().ProductTags.Add(productTagDownload); + entities.Add(attributeWayfarerLenscolor); - #endregion tag download + var attributeWayfarerFramecolor = new ProductVariantAttribute() + { + Product = productWayfarer, + ProductAttribute = attrWayfarerFramecolor, + IsRequired = true, + DisplayOrder = 3, + AttributeControlType = AttributeControlType.Boxes + }; - #region tag watches - var productTagWatches = new ProductTag - { - Name = "watches" - }; + var wayfarerFramePicture = wayfarerFramePictures.First(x => x.SeoFilename.EndsWith("_rayban_black")); - _ctx.Set().Where(pt => pt.MetaTitle == "Certina DS Podium Big Size").FirstOrDefault().ProductTags.Add(productTagWatches); + attributeWayfarerFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Black", + Alias = "rayban-black", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + //Color = "#3e4659" + PictureId = wayfarerFramePicture.Id + }); + + wayfarerFramePicture = wayfarerFramePictures.First(x => x.SeoFilename.EndsWith("_havana_black")); + attributeWayfarerFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Havana; Black", + Alias = "havana-black", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + //Color = "#3e4659" + PictureId = wayfarerFramePicture.Id + }); + + wayfarerFramePicture = wayfarerFramePictures.First(x => x.SeoFilename.EndsWith("_havana")); + attributeWayfarerFramecolor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Havana", + Alias = "havana", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + //Color = "#727377", + PictureId = wayfarerFramePicture.Id + }); - #endregion tag download - var entities = new List - { - productTagGift, productTagComputer, productTagNotebook, productTagCompact, productTagBook, productTagCooking, productTagCars, productTagMotorbikes, productTagIntel, productTagDell, productTagIphone, - productTagMP3, productTagDownload - }; + entities.Add(attributeWayfarerFramecolor); - this.Alter(entities); - return entities; - } + #endregion wayfarer - public IList CategoriesFirstLevel() - { - // pictures - var sampleImagesPath = this._sampleImagesPath; + #region 9,7 iPad - var categoryTemplateInGridAndLines = - this.CategoryTemplates().Where(pt => pt.ViewPath == "CategoryTemplate.ProductsInGridOrLines").FirstOrDefault(); + var product97iPad = _ctx.Set().First(x => x.Sku == "P-2004"); - //categories + var attribute97iPadMemoryCapacity = new ProductVariantAttribute() + { + Product = product97iPad, + ProductAttribute = attrMemoryCapacity, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.RadioList + }; - #region category definitions + attribute97iPadMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "64 GB", + Alias = "64gb", + IsPreSelected = true, + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 100.0M + }); - var categoryBooks = new Category - { - Name = "Books", - Alias = "Books", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "emblem_library.png"), "image/jpeg", GetSeName("Books")), - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Books" - }; + attribute97iPadMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "128 GB", + Alias = "128gb", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 200.0M + }); - var categoryComputers = new Category - { - Name = "Computers", - Alias = "Computers", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_computers.png"), "image/png", GetSeName("Computers")), - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Computers" - }; + entities.Add(attribute97iPadMemoryCapacity); - var categoryGaming = new Category - { - Name = "Gaming", - Alias = "Gaming", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming.png"), "image/png", GetSeName("Gaming")), - Published = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Gaming" - }; - var categoryCellPhones = new Category - { - Name = "Cell phones", - Alias = "Cell phones", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", + var attribute97iPadColor = new ProductVariantAttribute() + { + Product = product97iPad, + ProductAttribute = attr97iPadColor, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes + }; - //ParentCategoryId = categoryElectronics.Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_cellphone.png"), "image/png", GetSeName("Cell phones")), - Published = true, - DisplayOrder = 4, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Cell phones" - }; + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Silver", + Alias = "silver", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#dddfde" + }); + + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Gold", + Alias = "gold", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#e3d0ba" + }); - var categoryDigitalDownloads = new Category - { - Name = "Instant music", - Alias = "Instant music", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_digitaldownloads.jpg"), "image/jpeg", GetSeName("Digital downloads")), - Published = true, - DisplayOrder = 6, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Instant music" - }; + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Spacegray", + Alias = "spacegray", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#abaeb1" + }); - var categoryGiftCards = new Category - { - Name = "Gift Cards", - Alias = "Gift Cards", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_giftcards.png"), "image/png", GetSeName("Gift Cards")), - Published = true, - DisplayOrder = 12, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Gift cards" - }; + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Rose", + Alias = "rose", + DisplayOrder = 4, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#d9a6ad" + }); - var categoryWatches = new Category - { - Name = "Watches", - Alias = "Watches", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_watches.png"), "image/png", GetSeName("Watches")), - Published = true, - DisplayOrder = 10, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Watches" - }; + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Mint", + Alias = "mint", + DisplayOrder = 5, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#a6dbb1" + }); - #endregion category definitions + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Purple", + Alias = "purple", + DisplayOrder = 6, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#dba5d7" + }); - var entities = new List - { - categoryBooks, categoryComputers, categoryCellPhones, categoryDigitalDownloads, categoryGaming, categoryGiftCards, categoryWatches - }; + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Lightblue", + Alias = "lightblue", + DisplayOrder = 7, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#a6b9df" + }); + + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Turquoise", + Alias = "turquoise", + DisplayOrder = 8, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#a4dbde" + }); + + attribute97iPadColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Yellow", + Alias = "yellow", + DisplayOrder = 7, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#dfddb6" + }); - this.Alter(entities); - return entities; - } - public IList CategoriesSecondLevel() - { - // pictures - var sampleImagesPath = this._sampleImagesPath; + entities.Add(attribute97iPadColor); - var categoryTemplateInGridAndLines = - this.CategoryTemplates().Where(pt => pt.ViewPath == "CategoryTemplate.ProductsInGridOrLines").FirstOrDefault(); + #endregion 9,7 iPad - //categories + #region iPhone 7 plus - #region category definitions + var productIphone7Plus = _ctx.Set().First(x => x.Sku == "P-2001"); - var categoryBooksSpiegel = new Category - { - Name = "SPIEGEL-Bestseller", - Alias = "SPIEGEL-Bestseller", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000930_spiegel-bestseller.png"), "image/png", GetSeName("SPIEGEL-Bestseller")), - Published = true, - ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Books").First().Id, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "SPIEGEL-Bestseller" - }; + var attributeIphone7PlusMemoryCapacity = new ProductVariantAttribute() + { + Product = productIphone7Plus, + ProductAttribute = attrMemoryCapacity, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.RadioList + }; - var categoryBooksCookAndEnjoy = new Category - { - Name = "Cook and enjoy", - Alias = "Cook and enjoy", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000936_kochen-geniesen.jpeg"), "image/jpeg", GetSeName("Cook and enjoy")), - Published = true, - ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Books").First().Id, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Cook and enjoy" - }; + attributeIphone7PlusMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "64 GB", + Alias = "64gb", + IsPreSelected = true, + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 100.0M + }); - var categoryDesktops = new Category - { - Name = "Desktops", - Alias = "Desktops", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Computers").First().Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_desktops.png"), "image/png", GetSeName("Desktops")), - PriceRanges = "-1000;1000-1200;1200-;", - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Desktops" - }; + attributeIphone7PlusMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "128 GB", + Alias = "128gb", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 200.0M + }); - var categoryNotebooks = new Category - { - Name = "Notebooks", - Alias = "Notebooks", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Computers").First().Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_notebooks.png"), "image/png", GetSeName("Notebooks")), - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Notebooks" - }; + entities.Add(attributeIphone7PlusMemoryCapacity); - var categoryGamingAccessories = new Category - { - Name = "Gaming Accessories", - Alias = "Gaming Accessories", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Gaming").First().Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName("Gaming Accessories")), - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Gaming Accessories" - }; - var categoryGamingGames = new Category - { - Name = "Games", - Alias = "Games", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Gaming").First().Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_games.png"), "image/png", GetSeName("Games")), - Published = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Games" - }; + var attributeIphone7PlusColor = new ProductVariantAttribute() + { + Product = productIphone7Plus, + ProductAttribute = attrIphoneColor, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes + }; - #endregion category definitions + attributeIphone7PlusColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Silver", + Alias = "silver", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#dddfde" + }); + + attributeIphone7PlusColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Gold", + Alias = "gold", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#e3d0ba" + }); - var entities = new List - { - categoryBooksSpiegel, categoryBooksCookAndEnjoy, categoryDesktops, categoryNotebooks, categoryGamingAccessories, categoryGamingGames - }; + attributeIphone7PlusColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Red", + Alias = "red", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#af1e2d" + }); - this.Alter(entities); - return entities; - } + attributeIphone7PlusColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Rose", + Alias = "rose", + DisplayOrder = 4, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#d9a6ad" + }); - public IList Manufacturers() - { - //pictures - var sampleImagesPath = this._sampleImagesPath; + attributeIphone7PlusColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Black", + Alias = "black", + DisplayOrder = 5, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + Color = "#000000" + }); - var manufacturerTemplateInGridAndLines = - this.ManufacturerTemplates().Where(pt => pt.ViewPath == "ManufacturerTemplate.ProductsInGridOrLines").FirstOrDefault(); + entities.Add(attributeIphone7PlusColor); + + #endregion iPhone 7 plus + + #region iPhone + + // var productIphone = _ctx.Set().First(x => x.Sku == "Apple-1001"); + + //var attributeIphoneMemoryCapacity = new ProductVariantAttribute() + //{ + // Product = productIphone, + // ProductAttribute = attrMemoryCapacity, + // IsRequired = true, + // DisplayOrder = 1, + // AttributeControlType = AttributeControlType.DropdownList + //}; + + //attributeIphoneMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "16 GB", + // Alias = "16gb", + // IsPreSelected = true, + // DisplayOrder = 1, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.Simple + //}); + + //attributeIphoneMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "64 GB", + // Alias = "64gb", + // DisplayOrder = 2, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.Simple, + // PriceAdjustment = 100.0M + //}); + + //attributeIphoneMemoryCapacity.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "128 GB", + // Alias = "128gb", + // DisplayOrder = 3, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.Simple, + // PriceAdjustment = 200.0M + //}); + + //entities.Add(attributeIphoneMemoryCapacity); - //var categoryTemplateInGridAndLines = - // this.CategoryTemplates().Where(pt => pt.Name == "Products in Grid or Lines").FirstOrDefault(); - //categories + //var attributeIphoneColor = new ProductVariantAttribute() + //{ + // Product = productIphone, + // ProductAttribute = attrIphoneColor, + // IsRequired = true, + // DisplayOrder = 2, + // AttributeControlType = AttributeControlType.DropdownList + //}; + + //attributeIphoneColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "Silver", + // Alias = "silver", + // IsPreSelected = true, + // DisplayOrder = 1, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.Simple + //}); + + //attributeIphoneColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "Gold", + // Alias = "gold", + // DisplayOrder = 2, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.Simple + //}); + + //attributeIphoneColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "Space gray", + // Alias = "spacegray", + // DisplayOrder = 3, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.Simple + //}); + + //entities.Add(attributeIphoneColor); + + #endregion iPhone + + #region attributeDualshock3ControllerColor - #region Apple + var productPs3 = _ctx.Set().First(x => x.Sku == "Sony-PS399000"); - var manufacturerApple = new Manufacturer + var attributeDualshock3ControllerColor = new ProductVariantAttribute() { - Name = "Apple", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_apple.png"), "image/png", GetSeName("Apple")), - Published = true, + Product = productPs3, + ProductAttribute = attrController, + IsRequired = true, DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + AttributeControlType = AttributeControlType.RadioList }; - #endregion Apple + attributeDualshock3ControllerColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "without controller", + Alias = "without_controller", + IsPreSelected = true, + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple + }); - #region Android + attributeDualshock3ControllerColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "with controller", + Alias = "with_controller", + PriceAdjustment = 60.0M, + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple + }); - var manufacturerAndroid = new Manufacturer - { - Name = "Android", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-android.png"), "image/png", GetSeName("Android")), - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + entities.Add(attributeDualshock3ControllerColor); - #endregion Android + #endregion attributeDualshock3ControllerColor - #region LG + #region attribute Apple Airpod - var manufacturerLG = new Manufacturer + var productAirpod = _ctx.Set().First(x => x.Sku == "P-2003"); + + var attributeAirpod = new ProductVariantAttribute() { - Name = "LG", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-lg.png"), "image/png", GetSeName("LG")), - Published = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productAirpod, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes }; - #endregion LG + attributeAirpod.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Gold", + Alias = "gold", + DisplayOrder = 1, + Quantity = 1, + Color = "#e3d0ba", + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 5.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-acreed3").Id + }); + + attributeAirpod.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Rose", + Alias = "rose", + DisplayOrder = 2, + Quantity = 1, + Color = "#d9a6ad", + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 10.00M, + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-watchdogs").Id + }); + + attributeAirpod.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Mint", + Alias = "mint", + DisplayOrder = 3, + Quantity = 1, + Color= "#a6dbb1", + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 15.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-princepersia").Id + }); + + attributeAirpod.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Lightblue", + Alias = "lightblue", + DisplayOrder = 3, + Quantity = 1, + Color = "#a6b9df", + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 15.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-princepersia").Id + }); + + attributeAirpod.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "Turquoise", + Alias = "turquoise", + DisplayOrder = 3, + Quantity = 1, + Color = "#a4dbde", + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 15.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-princepersia").Id + }); + + attributeAirpod.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "White", + Alias = "white", + DisplayOrder = 3, + Quantity = 1, + Color = "#ffffff", + IsPreSelected = true, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 15.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-princepersia").Id + }); - #region Dell + entities.Add(attributeAirpod); - var manufacturerDell = new Manufacturer - { - Name = "Dell", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-dell.png"), "image/png", GetSeName("Dell")), - Published = true, - DisplayOrder = 4, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + #endregion attribute Apple Airpod - #endregion Dell + #region attribute Evopower 5.3 Trainer HS Ball - #region HP + var productEvopower = _ctx.Set().First(x => x.Sku == "P-5003"); - var manufacturerHP = new Manufacturer + var attributeEvopower = new ProductVariantAttribute() { - Name = "HP", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-hp.png"), "image/png", GetSeName("HP")), - Published = true, - DisplayOrder = 5, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productEvopower, + ProductAttribute = attrBallsize, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.RadioList }; - #endregion HP + attributeEvopower.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "3", + Alias = "ballsize-3", + DisplayOrder = 1, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 5.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-acreed3").Id + }); + + attributeEvopower.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "4", + Alias = "ballsize-4", + DisplayOrder = 2, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 10.00M, + IsPreSelected = true + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-watchdogs").Id + }); + + attributeEvopower.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + { + Name = "5", + Alias = "ballsize-5", + DisplayOrder = 3, + Quantity = 1, + ValueType = ProductVariantAttributeValueType.Simple, + PriceAdjustment = 15.00M + //LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-princepersia").Id + }); - #region Microsoft + + entities.Add(attributeEvopower); - var manufacturerMicrosoft = new Manufacturer - { - Name = "Microsoft", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-microsoft.png"), "image/png", GetSeName("Microsoft")), - Published = true, - DisplayOrder = 6, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + #endregion attribute Evopower 5.3 Trainer HS Ball - #endregion Microsoft + #region attributePs3OneGameFree - #region Samsung + // var productPs3OneGameFree = _ctx.Set().First(x => x.Sku == "Sony-PS310111"); - var manufacturerSamsung = new Manufacturer - { - Name = "Samsung", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-samsung.png"), "image/png", GetSeName("Samsung")), - Published = true, - DisplayOrder = 7, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + //var attributePs3OneGameFree = new ProductVariantAttribute() + //{ + // Product = productPs3OneGameFree, + // ProductAttribute = attrGames, + // IsRequired = true, + // DisplayOrder = 1, + // AttributeControlType = AttributeControlType.DropdownList + //}; - #endregion Samsung + //attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "Minecraft - Playstation 4 Edition", + // Alias = "minecraft-playstation4edition", + // DisplayOrder = 1, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.ProductLinkage, + // LinkedProductId = _ctx.Set().First(x => x.Sku == "PD-Minecraft4ps4").Id + //}); - #region Acer + //attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "Watch Dogs", + // Alias = "watch-dogs", + // DisplayOrder = 2, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.ProductLinkage, + // LinkedProductId = _ctx.Set().First(x => x.Sku == "Ubi-watchdogs").Id + //}); - var manufacturerAcer = new Manufacturer - { - Name = "Acer", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "acer-logo.jpg"), "image/pjpeg", GetSeName("Acer")), - Published = true, - DisplayOrder = 8, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + //attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "Horizon Zero Dawn - PlayStation 4", + // Alias = "horizon-zero-dawn-playStation-4", + // DisplayOrder = 3, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.ProductLinkage, + // LinkedProductId = _ctx.Set().First(x => x.Sku == "PD-ZeroDown4PS4").Id + //}); - #endregion Acer + //attributePs3OneGameFree.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue() + //{ + // Name = "LEGO Worlds - PlayStation 4", + // Alias = "lego-worlds-playstation_4", + // DisplayOrder = 4, + // Quantity = 1, + // ValueType = ProductVariantAttributeValueType.ProductLinkage, + // LinkedProductId = _ctx.Set().First(x => x.Sku == "Gaming-Lego-001").Id + //}); - #region TrekStor + //entities.Add(attributePs3OneGameFree); - var manufacturerTrekStor = new Manufacturer - { - Name = "TrekStor", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-trekstor.png"), "image/png", GetSeName("TrekStor")), - Published = true, - DisplayOrder = 9, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + #endregion attributePs3OneGameFree - #endregion TrekStor + #region Fashion - Converse All Star - #region Western Digital + var productAllStar = _ctx.Set().First(x => x.Sku == "Fashion-112355"); + var allStarColors = new string[] { "Charcoal", "Maroon", "Navy", "Purple", "White" }; + var allStarPictures = _ctx.Set().Where(x => x.SeoFilename.StartsWith("all-star-")).ToList(); - var manufacturerWesternDigital = new Manufacturer + var attrAllStarColor = new ProductVariantAttribute { - Name = "Western Digital", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-westerndigital.png"), "image/png", GetSeName("Western Digital")), - Published = true, - DisplayOrder = 10, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productAllStar, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes }; - #endregion Western Digital - - #region MSI + for (var i = 0; i < allStarColors.Length; ++i) + { + var allStarPicture = allStarPictures.First(x => x.SeoFilename.EndsWith(allStarColors[i].ToLower())); + attrAllStarColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = allStarColors[i], + Alias = allStarColors[i].ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + PictureId = allStarPicture.Id + }); + } + entities.Add(attrAllStarColor); - var manufacturerMSI = new Manufacturer + var attrAllStarSize = new ProductVariantAttribute { - Name = "MSI", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-msi.png"), "image/png", GetSeName("MSI")), - Published = true, - DisplayOrder = 11, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productAllStar, + ProductAttribute = attrSize, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes }; + attrAllStarSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = "42", + Alias = "42", + DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true + }); + attrAllStarSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = "43", + Alias = "43", + DisplayOrder = 2, + Quantity = 1 + }); + attrAllStarSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = "44", + Alias = "44", + DisplayOrder = 3, + Quantity = 1 + }); + entities.Add(attrAllStarSize); - #endregion MSI + #endregion - #region Canon + #region Fashion - Shirt Meccanica - var manufacturerCanon = new Manufacturer + var productShirtMeccanica = _ctx.Set().First(x => x.Sku == "Fashion-987693502"); + var shirtMeccanicaSizes = new string[] { "XS", "S", "M", "L", "XL" }; + var shirtMeccanicaColors = new[] { - Name = "Canon", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-canon.png"), "image/png", GetSeName("Canon")), - Published = true, - DisplayOrder = 12, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + new { Color = "Red", Code = "#fe0000" }, + new { Color = "Black", Code = "#000000" } }; - #endregion Canon - - #region Casio - - var manufacturerCasio = new Manufacturer + var attrShirtMeccanicaColor = new ProductVariantAttribute { - Name = "Casio", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-casio.png"), "image/png", GetSeName("Casio")), - Published = true, - DisplayOrder = 13, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productShirtMeccanica, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes }; - #endregion Casio - - #region Panasonic - - var manufacturerPanasonic = new Manufacturer + for (var i = 0; i < shirtMeccanicaColors.Length; ++i) { - Name = "Panasonic", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-panasonic.png"), "image/png", GetSeName("Panasonic")), - Published = true, - DisplayOrder = 14, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - - #endregion Panasonic - - #region BlackBerry + attrShirtMeccanicaColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = shirtMeccanicaColors[i].Color, + Alias = shirtMeccanicaColors[i].Color.ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = shirtMeccanicaColors[i].Code, + IsPreSelected = shirtMeccanicaColors[i].Color == "Red" + }); + } + entities.Add(attrShirtMeccanicaColor); - var manufacturerBlackBerry = new Manufacturer + var attrShirtMeccanicaSize = new ProductVariantAttribute { - Name = "BlackBerry", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-blackberry.png"), "image/png", GetSeName("BlackBerry")), - Published = true, - DisplayOrder = 15, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productShirtMeccanica, + ProductAttribute = attrSize, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes }; - #endregion BlackBerry - - #region HTC - - var manufacturerHTC = new Manufacturer + for (var i = 0; i < shirtMeccanicaSizes.Length; ++i) { - Name = "HTC", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-htc.png"), "image/png", GetSeName("HTC")), - Published = true, - DisplayOrder = 16, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; + attrShirtMeccanicaSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = shirtMeccanicaSizes[i], + Alias = shirtMeccanicaSizes[i].ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = shirtMeccanicaSizes[i] == "XS" + }); + } + entities.Add(attrShirtMeccanicaSize); - #endregion HTC + #endregion - #region Festina + #region Fashion - Ladies Jacket - var manufacturerFestina = new Manufacturer + var productLadiesJacket = _ctx.Set().First(x => x.Sku == "Fashion-JN1107"); + var ladiesJacketSizes = new string[] { "XS", "S", "M", "L", "XL" }; + var ladiesJacketColors = new[] { - Name = "Festina", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_festina.png"), "image/png", GetSeName("Festina")), - Published = true, - DisplayOrder = 17, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + new { Color = "Red", Code = "#CE1F1C" }, + new { Color = "Orange", Code = "#EB7F01" }, + new { Color = "Green", Code = "#24B87E" }, + new { Color = "Blue", Code = "#0F8CCE" }, + new { Color = "Navy", Code = "#525671" }, + new { Color = "Silver", Code = "#ABB0B3" }, + new { Color = "Black", Code = "#404040" } }; - #endregion Festina - - #region Certina - - var manufacturerCertina = new Manufacturer + var attrLadiesJacketColor = new ProductVariantAttribute { - Name = "Certina", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-certina.png"), "image/png", GetSeName("Certina")), - Published = true, - DisplayOrder = 18, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productLadiesJacket, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes }; - #endregion Certina - - #region Sony - - var manufacturerSony = new Manufacturer + for (var i = 0; i < ladiesJacketColors.Length; ++i) { - Name = "Sony", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_sony.png"), "image/png", GetSeName("Sony")), - Published = true, - DisplayOrder = 19, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - - #endregion Sony - - #region Ubisoft + attrLadiesJacketColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = ladiesJacketColors[i].Color, + Alias = ladiesJacketColors[i].Color.ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = ladiesJacketColors[i].Code, + IsPreSelected = ladiesJacketColors[i].Color == "Red" + }); + } + entities.Add(attrLadiesJacketColor); - var manufacturerUbisoft = new Manufacturer + var attrLadiesJacketSize = new ProductVariantAttribute { - Name = "Ubisoft", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 12, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "12,18,36,72,150", - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_ubisoft.png"), "image/png", GetSeName("Ubisoft")), - Published = true, - DisplayOrder = 20, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow + Product = productLadiesJacket, + ProductAttribute = attrSize, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.RadioList }; - #endregion Ubisoft - - var entities = new List + for (var i = 0; i < ladiesJacketSizes.Length; ++i) { - manufacturerApple,manufacturerSamsung,manufacturerLG,manufacturerTrekStor, manufacturerWesternDigital,manufacturerDell, manufacturerMSI, - manufacturerCanon, manufacturerCasio, manufacturerPanasonic, manufacturerBlackBerry, manufacturerHTC, manufacturerFestina, manufacturerCertina, - manufacturerHP, manufacturerAcer, manufacturerSony, manufacturerUbisoft - }; - - this.Alter(entities); - return entities; - } - - public IList Products() - { - #region definitions - - //pictures - var sampleImagesPath = this._sampleImagesPath; - - //downloads - var sampleDownloadsPath = this._sampleDownloadsPath; - - //templates - var productTemplateSimple = _ctx.Set().First(x => x.ViewPath == "ProductTemplate.Simple"); - var productTemplateGrouped = _ctx.Set().First(x => x.ViewPath == "ProductTemplate.Grouped"); - - var firstDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 0); - - #endregion definitions + attrLadiesJacketSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = ladiesJacketSizes[i], + Alias = ladiesJacketSizes[i].ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = ladiesJacketSizes[i] == "XS" + }); + } + entities.Add(attrLadiesJacketSize); - #region category Gift Cards + #endregion - var categoryGiftCards = this._ctx.Set().First(c => c.Alias == "Gift Cards"); + #region Fashion - Clark Jeans - #region product5GiftCard + var productClarkJeans = _ctx.Set().First(x => x.Sku == "Fashion-65986524"); + var clarkJeansWidth = new string[] { "31", "32", "33", "34", "35", "36", "38", "40", "42", "44", "46" }; + var clarkJeansLength = new string[] { "30", "32", "34" }; - var product5GiftCard = new Product() + var attrClarkJeansWidth = new ProductVariantAttribute { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$5 Virtual Gift Card", - IsEsd = true, - ShortDescription = "$5 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "$5 Virtual Gift Card", - Price = 5M, - IsGiftCard = true, - GiftCardType = GiftCardType.Virtual, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false + Product = productClarkJeans, + ProductAttribute = attrWidth, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes }; - product5GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); + for (var i = 0; i < clarkJeansWidth.Length; ++i) + { + attrClarkJeansWidth.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = clarkJeansWidth[i], + Alias = clarkJeansWidth[i], + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = clarkJeansWidth[i] == "31" + }); + } + entities.Add(attrClarkJeansWidth); - //var productTag = _productTagRepository.Table.Where(pt => pt.Name == "gift").FirstOrDefault(); - //productTag.ProductCount++; - //productTag.Products.Add(product5GiftCard); - //_productTagRepository.Update(productTag); + var attrClarkJeansLength = new ProductVariantAttribute + { + Product = productClarkJeans, + ProductAttribute = attrLength, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes + }; - product5GiftCard.ProductPictures.Add(new ProductPicture() + for (var i = 0; i < clarkJeansLength.Length; ++i) { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_giftcart.png"), "image/png", GetSeName(product5GiftCard.Name)), - DisplayOrder = 1, - }); + attrClarkJeansLength.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = clarkJeansLength[i], + Alias = clarkJeansLength[i], + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = clarkJeansLength[i] == "30" + }); + } + entities.Add(attrClarkJeansLength); + + #endregion Fashion - Clark Jeans - #endregion product5GiftCard + #region Furniture - Le Corbusier LC 6 table - #region product25GiftCard + var productCorbusierTable = _ctx.Set().First(x => x.Sku == "Furniture-lc6"); - var product25GiftCard = new Product() + var attrCorbusierTablePlate = new ProductVariantAttribute { - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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 + Product = productCorbusierTable, + ProductAttribute = attrPlate, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes }; - - product25GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); - - product25GiftCard.ProductPictures.Add(new ProductPicture() + attrCorbusierTablePlate.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_giftcart.png"), "image/png", GetSeName(product25GiftCard.Name)), + Name = "Clear glass", + Alias = "clear-glass", DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true }); + attrCorbusierTablePlate.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = "Sandblasted glass", + Alias = "sandblasted-glass", + DisplayOrder = 2, + Quantity = 1 + }); + entities.Add(attrCorbusierTablePlate); - #endregion product25GiftCard - - #region product50GiftCard - - var product50GiftCard = new Product() + var attrCorbusierTableThickness = new ProductVariantAttribute { - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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 + Product = productCorbusierTable, + ProductAttribute = attrPlateThickness, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes }; - - product50GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); - - product50GiftCard.ProductPictures.Add(new ProductPicture() + attrCorbusierTableThickness.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_giftcart.png"), "image/png", GetSeName(product50GiftCard.Name)), + Name = "15 mm", + Alias = "15mm", DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true + }); + attrCorbusierTableThickness.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = "19 mm", + Alias = "19mm", + DisplayOrder = 2, + Quantity = 1 }); + entities.Add(attrCorbusierTableThickness); - #endregion product50GiftCard + #endregion - #endregion category Gift Cards + #region Soccer Adidas TANGO SALA BALL - #region category books + var productAdidasTANGOSALABALL = _ctx.Set().First(x => x.Sku == "P-5001"); + var productAdidasTANGOSALABALLSizes = new string[] { "3", "4", "5" }; + var productAdidasTANGOSALABALLColors = new[] + { + new { Color = "Red", Code = "#ff0000" }, + new { Color = "Yellow", Code = " #ffff00" }, + new { Color = "Green", Code = "#008000" }, + new { Color = "Blue", Code = "#0000ff" }, + new { Color = "Gray", Code = "#808080" }, + new { Color = "White", Code = "#ffffff" }, + new { Color = "Brown", Code = "#a52a2a" } + }; - var categorySpiegelBestseller = this._ctx.Set().First(c => c.Alias == "SPIEGEL-Bestseller"); - var categoryCookAndEnjoy = this._ctx.Set().First(c => c.Alias == "Cook and enjoy"); - var categoryBooks = this._ctx.Set().First(c => c.Alias == "Books"); + var attrAdidasTANGOSALABALLColor = new ProductVariantAttribute + { + Product = productAdidasTANGOSALABALL, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes + }; - #region productBooksUberMan + for (var i = 0; i < productAdidasTANGOSALABALLColors.Length; ++i) + { + attrAdidasTANGOSALABALLColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = productAdidasTANGOSALABALLColors[i].Color, + Alias = productAdidasTANGOSALABALLColors[i].Color.ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = productAdidasTANGOSALABALLColors[i].Code, + IsPreSelected = productAdidasTANGOSALABALLColors[i].Color == "White" + }); + } + entities.Add(attrAdidasTANGOSALABALLColor); + + var attrAdidasTANGOSALABALLSize = new ProductVariantAttribute + { + Product = productAdidasTANGOSALABALL, + ProductAttribute = attrSize, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.RadioList + }; - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Überman: The novel", - Price = 16.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true - }; + for (var i = 0; i < productAdidasTANGOSALABALLSizes.Length; ++i) + { + attrAdidasTANGOSALABALLSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = productAdidasTANGOSALABALLSizes[i], + Alias = productAdidasTANGOSALABALLSizes[i].ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = productAdidasTANGOSALABALLSizes[i] == "5" + }); + } + entities.Add(attrAdidasTANGOSALABALLSize); + + #endregion Soccer Adidas TANGO SALA BALL + + #region Torfabrik official game ball + + var productTorfabrikBall = _ctx.Set().First(x => x.Sku == "P-5002"); + var productTorfabrikBallSizes = new string[] { "3", "4", "5" }; + var productTorfabrikBallColors = new[] + { + new { Color = "Red", Code = "#ff0000" }, + new { Color = "Yellow", Code = " #ffff00" }, + new { Color = "Green", Code = "#008000" }, + new { Color = "Blue", Code = "#0000ff" }, + new { Color = "White", Code = "#ffffff" }, + }; - productBooksUberMan.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); + var attrTorfabrikBallColor = new ProductVariantAttribute + { + Product = productTorfabrikBall, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.Boxes + }; - //pictures - productBooksUberMan.ProductPictures.Add(new ProductPicture() + for (var i = 0; i < productTorfabrikBallColors.Length; ++i) + { + attrTorfabrikBallColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = productTorfabrikBallColors[i].Color, + Alias = productTorfabrikBallColors[i].Color.ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = productTorfabrikBallColors[i].Code, + IsPreSelected = productTorfabrikBallColors[i].Color == "White" + }); + } + entities.Add(attrTorfabrikBallColor); + + var attrTorfabrikSize = new ProductVariantAttribute + { + Product = productTorfabrikBall, + ProductAttribute = attrSize, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.RadioList + }; + + for (var i = 0; i < productTorfabrikBallSizes.Length; ++i) + { + attrTorfabrikSize.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = productTorfabrikBallSizes[i], + Alias = productTorfabrikBallSizes[i].ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = productTorfabrikBallSizes[i] == "5" + }); + } + entities.Add(attrTorfabrikSize); + + #endregion Soccer Torfabrik official game ball + + #region Furniture - Ball chair + + var productBallChair = _ctx.Set().First(x => x.Sku == "Furniture-ball-chair"); + + var attrBallChairMaterial = new ProductVariantAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000932_uberman-der-roman.jpeg"), "image/jpeg", GetSeName(productBooksUberMan.Name)), + Product = productBallChair, + ProductAttribute = attrMaterial, + IsRequired = true, DisplayOrder = 1, - }); - - //attributes - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + AttributeControlType = AttributeControlType.DropdownList + }; + attrBallChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + Name = "Leather Special", + Alias = "leather-special", + DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + attrBallChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() + Name = "Leather Aniline", + Alias = "leather-aniline", + DisplayOrder = 2, + Quantity = 1 }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + attrBallChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, + Name = "Mixed Linen", + Alias = "mixed-linen", DisplayOrder = 3, - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + Quantity = 1 }); - #endregion productBooksUberMan + entities.Add(attrBallChairMaterial); - #region productBooksGefangeneDesHimmels - - var productBooksGefangeneDesHimmels = new Product() + var attrBallChairColor = new ProductVariantAttribute { - 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 = productTemplateSimple.Id, - Sku = "P-1004", - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() + Product = productBallChair, + ProductAttribute = attrColor, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes }; - - productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - - //pictures - productBooksGefangeneDesHimmels.ProductPictures.Add(new ProductPicture() + attrBallChairColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000935_der-gefangene-des-himmels-roman_300.jpeg"), "image/jpeg", GetSeName(productBooksGefangeneDesHimmels.Name)), + Name = "White", + Alias = "white", + Color = "#ffffff", DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true }); - - //attributes - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + attrBallChairColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Edition -> bound - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + Name = "Black", + Alias = "black", + Color = "#000000", + DisplayOrder = 2, + Quantity = 1 }); + entities.Add(attrBallChairColor); - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + var attrBallChairLeatherColor = new ProductVariantAttribute { - AllowFiltering = true, - ShowOnProductPage = true, + Product = productBallChair, + ProductAttribute = attrLeatherColor, + IsRequired = 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() + AttributeControlType = AttributeControlType.Boxes + }; + + for (var i = 0; i < generalColors.Length; ++i) { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Language -> German - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); + attrBallChairLeatherColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = generalColors[i].Name, + Alias = generalColors[i].Name.Replace(" ", "-").ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = generalColors[i].Color, + IsPreSelected = (generalColors[i].Name == "Tomato red") + }); + } + entities.Add(attrBallChairLeatherColor); - #endregion productBooksGefangeneDesHimmels + #endregion - #region productBooksBestGrillingRecipes + #region Furniture - Lounge chair - 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 = productTemplateSimple.Id, - Sku = "P-1005", - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + var productLoungeChair = _ctx.Set().First(x => x.Sku == "Furniture-lounge-chair"); - productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - - //pictures - productBooksBestGrillingRecipes.ProductPictures.Add(new ProductPicture() + var attrLoungeChairMaterial = new ProductVariantAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_bestgrillingrecipes.jpg"), "image/jpeg", GetSeName(productBooksBestGrillingRecipes.Name)), + Product = productLoungeChair, + ProductAttribute = attrMaterial, + IsRequired = true, 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() + AttributeControlType = AttributeControlType.DropdownList + }; + attrLoungeChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - 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() + Name = "Leather Special", + Alias = "leather-special", + DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + attrLoungeChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Language -> German - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() + Name = "Leather Aniline", + Alias = "leather-aniline", + DisplayOrder = 2, + Quantity = 1 }); + entities.Add(attrLoungeChairMaterial); - #endregion productBooksBestGrillingRecipes - - #region productBooksCookingForTwo - - var productBooksCookingForTwo = new Product() + var loungeChairSeatShells = new string[] { "Palisander", "Cherry", "Walnut", "Wooden black lacquered" }; + var attrLoungeChairSeatShell = new ProductVariantAttribute { - 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 = productTemplateSimple.Id, - Sku = "P-1006", - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() + Product = productLoungeChair, + ProductAttribute = attrSeatShell, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.DropdownList }; - productBooksCookingForTwo.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - - //pictures - productBooksCookingForTwo.ProductPictures.Add(new ProductPicture() + for (var i = 0; i < loungeChairSeatShells.Length; ++i) { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_cookingfortwo.jpg"), "image/jpeg", GetSeName(productBooksCookingForTwo.Name)), - DisplayOrder = 1, - }); + attrLoungeChairSeatShell.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = loungeChairSeatShells[i], + Alias = loungeChairSeatShells[i].Replace(" ", "-").ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + IsPreSelected = (i == 0), + PriceAdjustment = (loungeChairSeatShells[i] == "Wooden black lacquered" ? 100.00M : decimal.Zero) + }); + } + entities.Add(attrLoungeChairSeatShell); - //attributes - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + var attrLoungeChairBase = new ProductVariantAttribute { - AllowFiltering = true, - ShowOnProductPage = true, + Product = productLoungeChair, + ProductAttribute = attrBase, + IsRequired = 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() + AttributeControlType = AttributeControlType.DropdownList + }; + attrLoungeChairBase.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - 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() + Name = "Top edge polished", + Alias = "top-edge-polished", + DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + attrLoungeChairBase.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Language -> German - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() + Name = "Completely polished", + Alias = "completely-polished", + DisplayOrder = 2, + Quantity = 1, + PriceAdjustment = 150.00M }); + entities.Add(attrLoungeChairBase); - #endregion productBooksCookingForTwo - - #region productBooksAutosDerSuperlative - - var productBooksAutosDerSuperlative = new Product() + var attrLoungeChairLeatherColor = new ProductVariantAttribute { - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() + Product = productLoungeChair, + ProductAttribute = attrLeatherColor, + IsRequired = true, + DisplayOrder = 4, + AttributeControlType = AttributeControlType.Boxes }; - 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() + for (var i = 0; i < generalColors.Length; ++i) { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Language -> German - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); + attrLoungeChairLeatherColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = generalColors[i].Name, + Alias = generalColors[i].Name.Replace(" ", "-").ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = generalColors[i].Color, + IsPreSelected = (generalColors[i].Name == "White") + }); + } + entities.Add(attrLoungeChairLeatherColor); + #endregion - #endregion productBooksAutosDerSuperlative + #region Furniture - Cube chair - #region productBooksBildatlasMotorraeder + var productCubeChair = _ctx.Set().First(x => x.Sku == "Furniture-cube-chair"); - var productBooksBildatlasMotorraeder = new Product() + var attrCubeChairMaterial = new ProductVariantAttribute { - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() + Product = productCubeChair, + ProductAttribute = attrMaterial, + IsRequired = true, + DisplayOrder = 1, + AttributeControlType = AttributeControlType.DropdownList }; - - productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); - - //pictures - productBooksBildatlasMotorraeder.ProductPictures.Add(new ProductPicture() + attrCubeChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"), "image/jpeg", GetSeName(productBooksBildatlasMotorraeder.Name)), + Name = "Leather Special", + Alias = "leather-special", DisplayOrder = 1, + Quantity = 1, + IsPreSelected = true }); - - //attributes - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + attrCubeChairMaterial.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Edition -> bound - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + Name = "Leather Aniline", + Alias = "leather-aniline", + DisplayOrder = 2, + Quantity = 1, + PriceAdjustment = 400.00M }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + entities.Add(attrCubeChairMaterial); + + var attrCubeChairLeatherColor = new ProductVariantAttribute { - 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() + Product = productCubeChair, + ProductAttribute = attrLeatherColor, + IsRequired = true, + DisplayOrder = 2, + AttributeControlType = AttributeControlType.Boxes + }; + + for (var i = 0; i < generalColors.Length; ++i) { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Language -> German - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); + attrCubeChairLeatherColor.ProductVariantAttributeValues.Add(new ProductVariantAttributeValue + { + Name = generalColors[i].Name, + Alias = generalColors[i].Name.Replace(" ", "-").ToLower(), + DisplayOrder = i + 1, + Quantity = 1, + Color = generalColors[i].Color, + IsPreSelected = (generalColors[i].Name == "Black") + }); + } + entities.Add(attrCubeChairLeatherColor); - #endregion productBooksBildatlasMotorraeder + #endregion - #region productBooksAutoBuch + this.Alter(entities); + return entities; + } - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + public IList ProductVariantAttributeCombinations() + { + var sb = new StringBuilder(); + var entities = new List(); + var attrColor = _ctx.Set().First(x => x.Alias == "color"); + var attrController = _ctx.Set().First(x => x.Alias == "controller"); + var attrSize = _ctx.Set().First(x => x.Alias == "size"); + var attrMemoryCapacity = _ctx.Set().First(x => x.Alias == "memory-capacity"); + var attrColorIphoneColors = _ctx.Set().First(x => x.Alias == "iphone-color"); + var attr97iPadColors = _ctx.Set().First(x => x.Alias == "ipad-color"); + var attrPlate = _ctx.Set().First(x => x.Alias == "plate"); + var attrPlateThickness = _ctx.Set().First(x => x.Alias == "plate-thickness"); + var attrMaterial = _ctx.Set().First(x => x.Alias == "material"); + var attrLeatherColor = _ctx.Set().First(x => x.Alias == "leather-color"); + var attrSeatShell = _ctx.Set().First(x => x.Alias == "seat-shell"); + var attrBase = _ctx.Set().First(x => x.Alias == "base"); + var attrFlakLenstype = _ctx.Set().First(x => x.Alias == "lenstype"); + var attrFlakFramecolor = _ctx.Set().First(x => x.Alias == "framecolor"); + var attrFlakLenscolor = _ctx.Set().First(x => x.Alias == "lenscolor"); + var attrWayfarerLenscolor = _ctx.Set().First(x => x.Alias == "wayfarerlenscolor"); + var attrWayfarerFramecolor = _ctx.Set().First(x => x.Alias == "wayfarerframecolor"); + + + #region ORIGINAL WAYFARER AT COLLECTION + + var productWayfarer = _ctx.Set().First(x => x.Sku == "P-3003"); + var wayfarerPictureIds = productWayfarer.ProductPictures.Select(pp => pp.PictureId).ToList(); + var picturesWayfarer = _ctx.Set().Where(x => wayfarerPictureIds.Contains(x.Id)).ToList(); + + //var attributeColorIphone7Plus = _ctx.Set().First(x => x.ProductId == productIphone7Plus.Id && x.ProductAttributeId == attrColor.Id); + + var wayfarerLenscolor = _ctx.Set().First(x => x.ProductId == productWayfarer.Id && x.ProductAttributeId == attrWayfarerLenscolor.Id); + var wayfarerLenscolorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == wayfarerLenscolor.Id).ToList(); + + var wayfarerFramecolor = _ctx.Set().First(x => x.ProductId == productWayfarer.Id && x.ProductAttributeId == attrWayfarerFramecolor.Id); + var wayfarerFramecolorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == wayfarerFramecolor.Id).ToList(); + + #region blue-gray-classic-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_blue-gray-classic-black", - productBooksAutoBuch.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "blue-gray-classic").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "rayban-black").Id), - //pictures - productBooksAutoBuch.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"), "image/jpeg", GetSeName(productBooksAutoBuch.Name)), - DisplayOrder = 1, - }); + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() + }); - //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 blue-gray-classic-black - #endregion productBooksAutoBuch + #region gray-course-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_gray-course-black", - #region productBooksFastCars + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "gray-course").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "rayban-black").Id), - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() + }); - productBooksFastCars.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + #endregion gray-course-black - //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, - }); + #region brown-course-havana + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_brown-course-havana", - //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() - }); + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "brown-course").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana").Id), - #endregion productBooksFastCars + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() + }); - #region productBooksMotorradAbenteuer + #endregion brown-course-havana - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + #region green-classic-havana-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_green-classic-havana-black", - productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "green-classic").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana-black").Id), - //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, - }); + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() + }); - //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 green-classic-havana-black - #endregion productBooksMotorradAbenteuer + // not available products not available products not available products not available products not available products - #endregion category books + #region blue-gray-classic-havana-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_blue-gray-classic-havana-black", - #region computer + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "blue-gray-classic").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana-black").Id), - 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"); + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() + }); - #region productComputerDellInspiron23 + #endregion green-classic-havana-black - 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, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + #region blue-gray-classic-havana + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_blue-gray-classic-havana", - productComputerDellInspiron23.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - productComputerDellInspiron23.ProductCategories.Add(new ProductCategory() { Category = categoryDesktops, DisplayOrder = 1 }); + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "blue-gray-classic").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana").Id), - #region pictures + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() + }); - //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 green-classic-rayban-black - #endregion pictures + // gray-course + #region gray-course-havana-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_gray-course-havana-black", - #region manufacturer + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "gray-course").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana-black").Id), - //manufacturer - productComputerDellInspiron23.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Dell").Single(), - DisplayOrder = 1, - }); + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = true, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() + }); - #endregion manufacturer + #endregion gray-course-havana-black + + #region gray-course-havana + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_gray-course-havana", - #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 + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "gray-course").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana").Id), - #endregion productComputerDellInspiron23 + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() + }); - #region productComputerDellOptiplex3010 + #endregion gray-course-rayban-black - 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, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + #region green-classic-rayban-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_green-classic-rayban-black", - productComputerDellOptiplex3010.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - productComputerDellOptiplex3010.ProductCategories.Add(new ProductCategory() { Category = categoryDesktops, DisplayOrder = 1 }); + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "green-classic").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "rayban-black").Id), - #region pictures + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() + }); - //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 green-classic-rayban-black - #endregion pictures + #region green-classic-havana + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_green-classic-havana", - #region manufacturer + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "green-classic").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana").Id), - //manufacturer - productComputerDellOptiplex3010.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Dell").Single(), - DisplayOrder = 1, - }); + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() + }); - #endregion manufacturer + #endregion gray-course-rayban-black - #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 + // brown-course + #region brown-course-havana-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_brown-course-havana-black", - #endregion productComputerDellOptiplex3010 + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "brown-course").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "havana-black").Id), - #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, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() + }); - productComputerAcerAspireOne.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - productComputerAcerAspireOne.ProductCategories.Add(new ProductCategory() { Category = categoryNotebooks, DisplayOrder = 1 }); + #endregion brown-course-havana-black - #region manufacturer + #region brown-course-rayban-black + entities.Add(new ProductVariantAttributeCombination() + { + Product = productWayfarer, + Sku = productWayfarer.Sku + "_brown-course-rayban-black", - //manufacturer - productComputerAcerAspireOne.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Acer").Single(), - DisplayOrder = 1, - }); + AttributesXml = FormatAttributeXml( + wayfarerLenscolor.Id, wayfarerLenscolorValues.First(x => x.Alias == "brown-course").Id, + wayfarerFramecolor.Id, wayfarerFramecolorValues.First(x => x.Alias == "rayban-black").Id), - #endregion manufacturer + StockQuantity = 0, + AllowOutOfStockOrders = true, + IsActive = false, + //Price = 299M, + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() + }); - #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 + #endregion brown-course-rayban-black - #region pictures - productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_aceraspiresl1500.png"), "image/png", GetSeName(productComputerAcerAspireOne.Name)), - DisplayOrder = 1, - }); - productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "01-12Hand_Aspire1.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), - DisplayOrder = 2, - }); - productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "58_00007561.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), - DisplayOrder = 3, - }); + #endregion ORIGINAL WAYFARER AT COLLECTION - #endregion tierPrieces + #region Custom Flak - #endregion productComputerAcerAspireOne + var productFlak = _ctx.Set().First(x => x.Sku == "P-3002"); + var flakPictureIds = productFlak.ProductPictures.Select(pp => pp.PictureId).ToList(); + var picturesFlak = _ctx.Set().Where(x => flakPictureIds.Contains(x.Id)).ToList(); - #endregion computer + //var attributeColorIphone7Plus = _ctx.Set().First(x => x.ProductId == productIphone7Plus.Id && x.ProductAttributeId == attrColor.Id); - #region Smartphones + var flakLenscolor = _ctx.Set().First(x => x.ProductId == productFlak.Id && x.ProductAttributeId == attrFlakLenscolor.Id); + var flakLenscolorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == flakLenscolor.Id).ToList(); - var categoryCellPhones = this._ctx.Set().First(c => c.Alias == "Cell phones"); + var flakLenstype = _ctx.Set().First(x => x.ProductId == productFlak.Id && x.ProductAttributeId == attrFlakLenstype.Id); + var flakLenstypeValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == flakLenstype.Id).ToList(); - #region productSmartPhonesAppleIphone + var flakFramecolor = _ctx.Set().First(x => x.ProductId == productFlak.Id && x.ProductAttributeId == attrFlakFramecolor.Id); + var flakFramecolorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == flakFramecolor.Id).ToList(); - 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, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + //#region matteblack-gray-standard - productSmartPhonesAppleIphone.ProductCategories.Add(new ProductCategory() { Category = categoryCellPhones, DisplayOrder = 1 }); + foreach (var lenscolorValue in flakLenscolorValues) + { + foreach (var framecolorValue in flakFramecolorValues) + { + + foreach (var lenstypeValue in flakLenstypeValues) + { + try { + entities.Add(new ProductVariantAttributeCombination + { + Product = productFlak, + Sku = productFlak.Sku + string.Concat("-", framecolorValue.Alias, "-", lenscolorValue.Alias, "-",lenstypeValue.Alias), + AttributesXml = FormatAttributeXml(flakLenscolor.Id, lenscolorValue.Id, flakLenstype.Id, lenstypeValue.Id, flakFramecolor.Id, framecolorValue.Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + + AssignedPictureIds = picturesFlak.First(x => x.SeoFilename.Contains(framecolorValue.Alias + "_" + lenscolorValue.Alias)).Id.ToString(), + + //Price = ballChairPrice + }); + } + catch + { + Console.WriteLine("An error occurred: '{0}'", framecolorValue.Alias + "_" + lenscolorValue.Alias); + } + } + } + } - #region pictures - //pictures - productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000789-apple-iphone.jpg"), "image/jpeg", GetSeName(productSmartPhonesAppleIphone.Name)), - DisplayOrder = 1, - }); - productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000785-apple-iphone.png"), "image/png", GetSeName(productSmartPhonesAppleIphone.Name)), - DisplayOrder = 2, - }); - productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000786-apple-iphone.png"), "image/png", GetSeName(productSmartPhonesAppleIphone.Name)), - DisplayOrder = 3, - }); - productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000787-apple-iphone.jpg"), "image/jpeg", GetSeName(productSmartPhonesAppleIphone.Name)), - DisplayOrder = 4, - }); - productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000788-apple-iphone.png"), "image/png", GetSeName(productSmartPhonesAppleIphone.Name)), - DisplayOrder = 5, - }); + + #endregion Custom Flak + #region ps3 - #endregion pictures + var productPs3 = _ctx.Set().First(x => x.Sku == "Sony-PS399000"); + var ps3PictureIds = productPs3.ProductPictures.Select(pp => pp.PictureId).ToList(); + var picturesPs3 = _ctx.Set().Where(x => ps3PictureIds.Contains(x.Id)).ToList(); - #region manufacturer + var productAttributeColor = _ctx.Set().First(x => x.ProductId == productPs3.Id && x.ProductAttributeId == attrController.Id); + var attributeColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == productAttributeColor.Id).ToList(); - //manufacturer - productSmartPhonesAppleIphone.ProductManufacturers.Add(new ProductManufacturer() + entities.Add(new ProductVariantAttributeCombination() { - Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - DisplayOrder = 1, - }); - - #endregion manufacturer - - #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() + Product = productPs3, + Sku = productPs3.Sku + "-B", + AttributesXml = FormatAttributeXml(productAttributeColor.Id, attributeColorValues.First(x => x.Alias == "with_controller").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesPs3.First(x => x.SeoFilename.EndsWith("-controller")).Id.ToString() }); - productSmartPhonesAppleIphone.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + + entities.Add(new ProductVariantAttributeCombination() { - 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() + Product = productPs3, + Sku = productPs3.Sku + "-W", + AttributesXml = FormatAttributeXml(productAttributeColor.Id, attributeColorValues.First(x => x.Alias == "without_controller").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesPs3.First(x => x.SeoFilename.EndsWith("-single")).Id.ToString() }); - #endregion SpecificationAttributes - #endregion productSmartPhonesAppleIphone + #endregion ps3 - #endregion Smartphones + #region Apple Airpod - #region Instant Download Music + var productAirpod = _ctx.Set().First(x => x.Sku == "P-2003"); + var airpodPictureIds = productAirpod.ProductPictures.Select(pp => pp.PictureId).ToList(); + var picturesAirpod = _ctx.Set().Where(x => airpodPictureIds.Contains(x.Id)).ToList(); - var categoryMusic = this._ctx.Set().First(c => c.Alias == "Instant music"); + var airpodAttributeColor = _ctx.Set().First(x => x.ProductId == productAirpod.Id && x.ProductAttributeId == attrColor.Id); + var airpodAttributeColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == airpodAttributeColor.Id).ToList(); - #region Antonio Vivaldi: then spring + entities.Add(new ProductVariantAttributeCombination() + { + Product = productAirpod, + Sku = productAirpod.Sku + "-gold", + AttributesXml = FormatAttributeXml(airpodAttributeColor.Id, airpodAttributeColorValues.First(x => x.Alias == "gold").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesAirpod.First(x => x.SeoFilename.EndsWith("-gold")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productAirpod, + Sku = productAirpod.Sku + "-rose", + AttributesXml = FormatAttributeXml(airpodAttributeColor.Id, airpodAttributeColorValues.First(x => x.Alias == "rose").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesAirpod.First(x => x.SeoFilename.EndsWith("-rose")).Id.ToString() + }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productAirpod, + Sku = productAirpod.Sku + "-mint", + AttributesXml = FormatAttributeXml(airpodAttributeColor.Id, airpodAttributeColorValues.First(x => x.Alias == "mint").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesAirpod.First(x => x.SeoFilename.EndsWith("-mint")).Id.ToString() + }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productAirpod, + Sku = productAirpod.Sku + "-lightblue", + AttributesXml = FormatAttributeXml(airpodAttributeColor.Id, airpodAttributeColorValues.First(x => x.Alias == "lightblue").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesAirpod.First(x => x.SeoFilename.EndsWith("-lightblue")).Id.ToString() + }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productAirpod, + Sku = productAirpod.Sku + "-turquoise", + AttributesXml = FormatAttributeXml(airpodAttributeColor.Id, airpodAttributeColorValues.First(x => x.Alias == "turquoise").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesAirpod.First(x => x.SeoFilename.EndsWith("-turquoise")).Id.ToString() + }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productAirpod, + Sku = productAirpod.Sku + "-white", + AttributesXml = FormatAttributeXml(airpodAttributeColor.Id, airpodAttributeColorValues.First(x => x.Alias == "white").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesAirpod.First(x => x.SeoFilename.EndsWith("-white")).Id.ToString() + }); - var productInstantDownloadVivaldi = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Antonio Vivaldi: spring", - IsEsd = true, - ShortDescription = "MP3, 320 kbit/s", - FullDescription = "

Antonio Vivaldi: Spring

Antonio Lucio Vivaldi (March 4, 1678 in Venice, † 28 July 1741 in Vienna) was a Venetian composer and violinist in the Baroque.

The Four Seasons (Le quattro stagioni Italian) is perhaps the most famous works of Antonio Vivaldi. It's four violin concertos with extra-musical programs, each portraying a concert season. This is the individual concerts one - probably written by Vivaldi himself - Sonnet preceded by consecutive letters in front of the lines and in the appropriate places in the score arrange the verbal description of the music.

Vivaldi had previously always been experimenting with non-musical programs, which often reflected in his tracks, the exact interpretation of the individual points score is unusual for him. His experience as a virtuoso violinist allowed him access to particularly effective playing techniques, as an opera composer, he had developed a strong sense of effects, both of which benefitted from him.

As the title suggests, especially to imitate natural phenomena - gentle winds, severe storms and thunderstorms are elements that are common to all four concerts. There are also various birds and even a dog, further human activities such as hunting, a barn dance, ice skating, including stumbling and falling to the heavy sleep of a drunkard.

The work dates from 1725 and is available in two print editions, which appeared more or less simultaneously published in Amsterdam and Paris.

", - Sku = "P-1016", - ProductTemplateId = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Antonio Vivaldi: spring", - Price = 1.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = "audio/mp3", - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "vivaldi-four-seasons-spring.mp3"), - Extension = ".mp3", - Filename = "vivaldi-four-seasons-spring", - IsNew = true, - } - }; + #endregion Apple Airpod - productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory() { Category = categoryMusic, DisplayOrder = 1 }); - - #region pictures + #region 9,7 Ipad - //pictures - productInstantDownloadVivaldi.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000740-antonio-vivaldi-der-fruhling-100.jpg"), "image/jpeg", GetSeName(productInstantDownloadVivaldi.Name)), - DisplayOrder = 1, - }); + var productiPad97 = _ctx.Set().First(x => x.Sku == "P-2004"); + var iPad97PictureIds = productiPad97.ProductPictures.Select(pp => pp.PictureId).ToList(); + var picturesiPad97 = _ctx.Set().Where(x => iPad97PictureIds.Contains(x.Id)).ToList(); - #endregion pictures + //var attributeColorIphone7Plus = _ctx.Set().First(x => x.ProductId == productIphone7Plus.Id && x.ProductAttributeId == attrColor.Id); - #region SpecificationAttributes - //attributes - 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() - }); - 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() - }); + var iPad97Color = _ctx.Set().First(x => x.ProductId == productiPad97.Id && x.ProductAttributeId == attr97iPadColors.Id); + var iPad97ColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == iPad97Color.Id).ToList(); - #endregion SpecificationAttributes + var ipad97Capacity = _ctx.Set().First(x => x.ProductId == productiPad97.Id && x.ProductAttributeId == attrMemoryCapacity.Id); + var iPad97CapacityValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == ipad97Capacity.Id).ToList(); - #endregion Antonio Vivildi: then spring + #region silver + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-silver-64gb", - #region Beethoven für Elise + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "silver").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - var productInstantDownloadBeethoven = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Ludwig van Beethoven: For Elise", - IsEsd = true, - ShortDescription = "Ludwig van Beethoven's most popular compositions", - FullDescription = "

The score was not published until 1867, 40 years after the composer's death in 1827. The discoverer of the piece, Ludwig Nohl, affirmed that the original autographed manuscript, now lost, was dated 27 April 1810.[4] The version of \"Für Elise\" we hear today is an earlier version that was transcribed by Ludwig Nohl. There is a later version, with drastic changes to the accompaniment which was transcribed from a later manuscript by Barry Cooper. The most notable difference is in the first theme, the left-hand arpeggios are delayed by a 16th note beat. There are a few extra bars in the transitional section into the B section; and finally, the rising A minor arpeggio figure is moved later into the piece. The tempo marking Poco Moto is believed to have been on the manuscript that Ludwig Nohl transcribed (now lost). The later version includes the marking Molto Grazioso. It is believed that Beethoven intended to add the piece to a cycle of bagatelles.[citation needed]

Therese Malfatti, widely believed to be the dedicatee of \"Für Elise\" The pianist and musicologist Luca Chiantore (es) argued in his thesis and his 2010 book Beethoven al piano that Beethoven might not have been the person who gave the piece the form that we know today. Chiantore suggested that the original signed manuscript, upon which Ludwig Nohl claimed to base his transcription, may never have existed.[5] On the other hand, the musicologist Barry Cooper stated, in a 1984 essay in The Musical Times, that one of two surviving sketches closely resembles the published version.[6]

", - Sku = "P-1017", - ProductTemplateId = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Ludwig van Beethoven: Für Elise", - ShowOnHomePage = true, - Price = 1.89M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download() - { - DownloadGuid = Guid.NewGuid(), - ContentType = "audio/mp3", - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "beethoven-fur-elise.mp3"), - Extension = ".mp3", - Filename = "beethoven-fur-elise.mp3", - IsNew = true - } - }; + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + Price = 299M, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-silver")).Id.ToString() + }); - productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory() { Category = categoryMusic, DisplayOrder = 1 }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "silver-128gb", - #region pictures + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "silver").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), - //pictures - productInstantDownloadBeethoven.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "Beethoven.jpg"), "image/jpeg", GetSeName(productInstantDownloadBeethoven.Name)), - DisplayOrder = 1, - }); + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-silver")).Id.ToString() + }); - #endregion pictures + #endregion silver - #region SpecificationAttributes - //attributes - 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() - }); - 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() - }); + #region gold + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-gold-64gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "gold").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), + Price = 279M, + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-gold")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "gold-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "gold").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-gold")).Id.ToString() + }); + #endregion gold + + #region spacegray + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-spacegray-64gb", - #endregion SpecificationAttributes + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "spacegray").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - #endregion Beethoven für Elise + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-spacegray")).Id.ToString() + }); - #endregion Instant Download Music + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "spacegray-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "spacegray").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-spacegray")).Id.ToString() + }); + #endregion spacegray + + #region rose + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-rose-64gb", - #region watches + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "rose").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - var categoryWatches = this._ctx.Set().First(c => c.Alias == "Watches"); + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-rose")).Id.ToString() + }); - #region productWatchesCertinaDSPodiumBigSize + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "rose-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "rose").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-rose")).Id.ToString() + }); + #endregion rose + + #region mint + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-mint-64gb", - 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-1018", - ProductTemplateId = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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() - }; + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "mint").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-mint")).Id.ToString() + }); - #region pictures + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "mint-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "mint").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-mint")).Id.ToString() + }); + #endregion mint + + #region purple + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-purple-64gb", - //pictures - productWatchesCertinaDSPodiumBigSize.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_certina_ds_podium_big.png"), "image/png", GetSeName(productWatchesCertinaDSPodiumBigSize.Name)), - DisplayOrder = 1, - }); + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "purple").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - #endregion pictures + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-purple")).Id.ToString() + }); - #region manufacturer + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "purple-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "purple").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-purple")).Id.ToString() + }); + #endregion purple + + #region lightblue + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-lightblue-64gb", - //manufacturer - productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Certina").Single(), - DisplayOrder = 1, - }); + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "lightblue").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - #endregion manufacturer + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-lightblue")).Id.ToString() + }); - #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() - { - 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() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 5, - // movement -> quarz - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() - }); - #endregion SpecificationAttributes + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "lightblue-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "lightblue").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-lightblue")).Id.ToString() + }); + #endregion lightblue + + #region yellow + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-yellow-64gb", - #endregion productWatchesCertinaDSPodiumBigSize + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "yellow").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - #endregion watches + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-yellow")).Id.ToString() + }); - #region gaming + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "yellow-128gb", + + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "yellow").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), + + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-yellow")).Id.ToString() + }); + #endregion yellow + + #region turquoise + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "-turquoise-64gb", - var manuSony = _ctx.Set().First(c => c.Name == "Sony"); - 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"); + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "turquoise").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "64gb").Id), - #region bundlePs3AssassinCreed + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-turquoise")).Id.ToString() + }); - var productPs3 = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS399000", - Name = "Playstation 3 Super Slim", - ShortDescription = "The Sony PlayStation 3 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 @3.2GHz
  • 1 VMX vector unit per core
  • 512KB L2 cache
  • 7 x SPE @3.2GHz
  • 7 x 128b 128 SIMD GPRs
  • 7 x 256KB 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Playstation 3 Super Slim", - Price = 189.00M, - OldPrice = 199.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + entities.Add(new ProductVariantAttributeCombination() + { + Product = productiPad97, + Sku = productiPad97.Sku + "turquoise-128gb", - productPs3.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs3.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 4 }); + AttributesXml = FormatAttributeXml( + iPad97Color.Id, iPad97ColorValues.First(x => x.Alias == "turquoise").Id, + ipad97Capacity.Id, iPad97CapacityValues.First(x => x.Alias == "128gb").Id), - productPs3.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps3_black.png"), "image/png", GetSeName(productPs3.Name) + "-black"), - DisplayOrder = 1 - }); - productPs3.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-ps3-white.jpg"), "image/jpeg", GetSeName(productPs3.Name) + "-white"), - DisplayOrder = 2 - }); + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesiPad97.First(x => x.SeoFilename.EndsWith("-turquoise")).Id.ToString() + }); + #endregion turquoise + #endregion 9,7 Ipad - var productDualshock3Controller = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS399004", - Name = "DUALSHOCK 3 Wireless Controller", - ShortDescription = "Equipped with SIXAXIS™ motion sensing technology and pressure sensors in each action button, the DUALSHOCK®3 wireless controller for the PlayStation®3 provides the most intuitive game play experience.", - FullDescription = "
  • Weights and Measurements

    • Dimensions (Approx.) : 5.56\"(w) x 8.5\"(h) x 3.63\" (d)
", - ProductTemplateId = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "DUALSHOCK 3 Wireless Controller", - Price = 54.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + #region Iphone 7 plus - productDualshock3Controller.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productDualshock3Controller.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 1 }); + var productIphone7Plus = _ctx.Set().First(x => x.Sku == "P-2001"); + var Iphone7PlusPictureIds = productIphone7Plus.ProductPictures.Select(pp => pp.PictureId).ToList(); + var picturesIphone7Plus = _ctx.Set().Where(x => Iphone7PlusPictureIds.Contains(x.Id)).ToList(); - productDualshock3Controller.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_playstation3dualshock3.png"), "image/png", GetSeName(productDualshock3Controller.Name)), - DisplayOrder = 1 - }); + //var attributeColorIphone7Plus = _ctx.Set().First(x => x.ProductId == productIphone7Plus.Id && x.ProductAttributeId == attrColor.Id); + var Iphone7PlusColor = _ctx.Set().First(x => x.ProductId == productIphone7Plus.Id && x.ProductAttributeId == attrColorIphoneColors.Id); + var Iphone7PlusColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == Iphone7PlusColor.Id).ToList(); - var productAssassinsCreed3 = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Ubi-acreed3", - Name = "Assassin's Creed III", - ShortDescription = "Third-person action-adventure title set.", - FullDescription = "Assassin's Creed III is set in an open world and presented from the third-person perspective with a primary focus on using Desmond and Connor's combat and stealth abilities to eliminate targets and explore the environment. Connor is able to freely explore 18th-century Boston, New York and the American frontier to complete side missions away from the primary storyline. The game also features a multiplayer component, allowing players to compete online to complete solo and team based objectives including assassinations and evading pursuers. Ubisoft developed a new game engine, Anvil Next, for the game.", - ProductTemplateId = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Assassin's Creed III", - Price = 49.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + var Iphone7PlusCapacity = _ctx.Set().First(x => x.ProductId == productIphone7Plus.Id && x.ProductAttributeId == attrMemoryCapacity.Id); + var Iphone7PlusCapacityValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == Iphone7PlusCapacity.Id).ToList(); - productAssassinsCreed3.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - productAssassinsCreed3.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 4 }); - productAssassinsCreed3.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-assassins-creed-3.jpg"), "image/jpeg", GetSeName("Assassin Creed 3")), - DisplayOrder = 1 - }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-black-64gb", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "black").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "64gb").Id), - var productBundlePs3AssassinCreed = new Product() - { - ProductType = ProductType.BundledProduct, - VisibleIndividually = true, - Sku = "Sony-PS399105", - Name = "PlayStation 3 Assassin's Creed III Bundle", - ShortDescription = "500GB PlayStation®3 system, 2 × DUALSHOCK®3 wireless controller and Assassin's Creed® III.", - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "PlayStation 3 Assassin's Creed III 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 - }; + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-black")).Id.ToString() + }); - productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 1 }); + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-black-128gb", - productBundlePs3AssassinCreed.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "sony-PS3AssassinsCreedBundle.png"), "image/png", GetSeName(productBundlePs3AssassinCreed.Name)), - DisplayOrder = 1 - }); + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "black").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "128gb").Id), - #endregion bundlePs3AssassinCreed + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-black")).Id.ToString() + }); - #region bundlePs4 + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-red-64", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "red").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "64gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-red")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-red-128", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "red").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "128gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-red")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-silver-64", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "silver").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "64gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-silver")).Id.ToString() + }); + + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-silver-128", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "silver").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "128gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-silver")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-rose-64", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "rose").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "64gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-rose")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-rose-128", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "rose").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "128gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-rose")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-gold-64", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "gold").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "64gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-gold")).Id.ToString() + }); + + entities.Add(new ProductVariantAttributeCombination() + { + Product = productIphone7Plus, + Sku = productIphone7Plus.Sku + "-gold-128", + AttributesXml = FormatAttributeXml( + Iphone7PlusColor.Id, Iphone7PlusColorValues.First(x => x.Alias == "gold").Id, + Iphone7PlusCapacity.Id, Iphone7PlusCapacityValues.First(x => x.Alias == "128gb").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = picturesIphone7Plus.First(x => x.SeoFilename.EndsWith("-gold")).Id.ToString() + }); + + #endregion Iphone 7 plus + + #region Fashion - Converse All Star + + var productAllStar = _ctx.Set().First(x => x.Sku == "Fashion-112355"); + var allStarPictureIds = productAllStar.ProductPictures.Select(x => x.PictureId).ToList(); + var allStarPictures = _ctx.Set().Where(x => allStarPictureIds.Contains(x.Id)).ToList(); + + var allStarColor = _ctx.Set().First(x => x.ProductId == productAllStar.Id && x.ProductAttributeId == attrColor.Id); + var allStarColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == allStarColor.Id).ToList(); + + var allStarSize = _ctx.Set().First(x => x.ProductId == productAllStar.Id && x.ProductAttributeId == attrSize.Id); + var allStarSizeValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == allStarSize.Id).ToList(); + + var allStarCombinations = new[] + { + new { Color = "Charcoal", Size = "42" }, + new { Color = "Charcoal", Size = "43" }, + new { Color = "Charcoal", Size = "44" }, + new { Color = "Maroon", Size = "42" }, + new { Color = "Maroon", Size = "43" }, + new { Color = "Maroon", Size = "44" }, + new { Color = "Navy", Size = "42" }, + new { Color = "Navy", Size = "43" }, + new { Color = "Navy", Size = "44" }, + new { Color = "Purple", Size = "42" }, + new { Color = "Purple", Size = "43" }, + new { Color = "Purple", Size = "44" }, + new { Color = "White", Size = "42" }, + new { Color = "White", Size = "43" }, + new { Color = "White", Size = "44" }, + }; + + foreach (var comb in allStarCombinations) + { + var lowerColor = comb.Color.ToLower(); + entities.Add(new ProductVariantAttributeCombination + { + Product = productAllStar, + Sku = productAllStar.Sku + string.Concat("-", lowerColor, "-", comb.Size), + AttributesXml = FormatAttributeXml( + allStarColor.Id, allStarColorValues.First(x => x.Alias == lowerColor).Id, + allStarSize.Id, allStarSizeValues.First(x => x.Alias == comb.Size).Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = allStarPictures.First(x => x.SeoFilename.EndsWith(lowerColor)).Id.ToString() + }); + } - var productPs4 = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS410034", - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "PlayStation 4", - Price = 399.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 3, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + #endregion - productPs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 5 }); + #region Fashion - Shirt Meccanica - productPs4.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4.png"), "image/png", GetSeName(productPs4.Name)), - DisplayOrder = 1 - }); + var productShirtMeccanica = _ctx.Set().First(x => x.Sku == "Fashion-987693502"); + var shirtMeccanicaPictureIds = productShirtMeccanica.ProductPictures.Select(x => x.PictureId).ToList(); + var shirtMeccanicaPictures = _ctx.Set().Where(x => shirtMeccanicaPictureIds.Contains(x.Id)).ToList(); + + var shirtMeccanicaColor = _ctx.Set().First(x => x.ProductId == productShirtMeccanica.Id && x.ProductAttributeId == attrColor.Id); + var shirtMeccanicaColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == shirtMeccanicaColor.Id).ToList(); + var shirtMeccanicaSize = _ctx.Set().First(x => x.ProductId == productShirtMeccanica.Id && x.ProductAttributeId == attrSize.Id); + var shirtMeccanicaSizeValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == shirtMeccanicaSize.Id).ToList(); - var productDualshock4Controller = new Product() + var shirtMeccanicaCombinations = new[] { - 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, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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 + new { Color = "Red", Size = "XS" }, + new { Color = "Red", Size = "S" }, + new { Color = "Red", Size = "M" }, + new { Color = "Red", Size = "L" }, + new { Color = "Red", Size = "XL" }, + new { Color = "Black", Size = "XS" }, + new { Color = "Black", Size = "S" }, + new { Color = "Black", Size = "M" }, + new { Color = "Black", Size = "L" }, + new { Color = "Black", Size = "XL" } }; - productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productDualshock4Controller.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 2 }); + foreach (var comb in shirtMeccanicaCombinations) + { + var lowerColor = comb.Color.ToLower(); + var lowerSize = comb.Size.ToLower(); + var pictureIds = shirtMeccanicaPictures.Where(x => x.SeoFilename.Contains($"_{lowerColor}_")).Select(x => x.Id); + + entities.Add(new ProductVariantAttributeCombination + { + Product = productShirtMeccanica, + Sku = productShirtMeccanica.Sku + string.Concat("-", lowerColor, "-", lowerSize), + AttributesXml = FormatAttributeXml( + shirtMeccanicaColor.Id, shirtMeccanicaColorValues.First(x => x.Alias == lowerColor).Id, + shirtMeccanicaSize.Id, shirtMeccanicaSizeValues.First(x => x.Alias == lowerSize).Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = string.Join(",", pictureIds) + }); + } + + #endregion + + #region Fashion - Ladies Jacket + + var productLadiesJacket = _ctx.Set().First(x => x.Sku == "Fashion-JN1107"); + var ladiesJacketPictureIds = productLadiesJacket.ProductPictures.Select(x => x.PictureId).ToList(); + var ladiesJacketPictures = _ctx.Set().Where(x => ladiesJacketPictureIds.Contains(x.Id)).ToList(); + + var ladiesJacketColor = _ctx.Set().First(x => x.ProductId == productLadiesJacket.Id && x.ProductAttributeId == attrColor.Id); + var ladiesJacketColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == ladiesJacketColor.Id).ToList(); + + var ladiesJacketSize = _ctx.Set().First(x => x.ProductId == productLadiesJacket.Id && x.ProductAttributeId == attrSize.Id); + var ladiesJacketSizeValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == ladiesJacketSize.Id).ToList(); + + var ladiesJacketCombinations = new[] + { + new { Color = "Red", Size = "XS" }, + new { Color = "Red", Size = "S" }, + new { Color = "Red", Size = "M" }, + new { Color = "Red", Size = "L" }, + new { Color = "Red", Size = "XL" }, + new { Color = "Orange", Size = "XS" }, + new { Color = "Orange", Size = "S" }, + new { Color = "Orange", Size = "M" }, + new { Color = "Orange", Size = "L" }, + new { Color = "Orange", Size = "XL" }, + new { Color = "Green", Size = "XS" }, + new { Color = "Green", Size = "S" }, + new { Color = "Green", Size = "M" }, + new { Color = "Green", Size = "L" }, + new { Color = "Green", Size = "XL" }, + new { Color = "Blue", Size = "XS" }, + new { Color = "Blue", Size = "S" }, + new { Color = "Blue", Size = "M" }, + new { Color = "Blue", Size = "L" }, + new { Color = "Blue", Size = "XL" }, + new { Color = "Navy", Size = "XS" }, + new { Color = "Navy", Size = "S" }, + new { Color = "Navy", Size = "M" }, + new { Color = "Navy", Size = "L" }, + new { Color = "Navy", Size = "XL" }, + new { Color = "Silver", Size = "XS" }, + new { Color = "Silver", Size = "S" }, + new { Color = "Silver", Size = "M" }, + new { Color = "Silver", Size = "L" }, + new { Color = "Silver", Size = "XL" }, + new { Color = "Black", Size = "XS" }, + new { Color = "Black", Size = "S" }, + new { Color = "Black", Size = "M" }, + new { Color = "Black", Size = "L" }, + new { Color = "Black", Size = "XL" } + }; + + foreach (var comb in ladiesJacketCombinations) + { + var lowerColor = comb.Color.ToLower(); + var lowerSize = comb.Size.ToLower(); + + entities.Add(new ProductVariantAttributeCombination + { + Product = productLadiesJacket, + Sku = productLadiesJacket.Sku + string.Concat("-", lowerColor, "-", lowerSize), + AttributesXml = FormatAttributeXml( + ladiesJacketColor.Id, ladiesJacketColorValues.First(x => x.Alias == lowerColor).Id, + ladiesJacketSize.Id, ladiesJacketSizeValues.First(x => x.Alias == lowerSize).Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = ladiesJacketPictures.First(x => x.SeoFilename.EndsWith(lowerColor)).Id.ToString() + }); + } - productDualshock4Controller.ProductPictures.Add(new ProductPicture() + #endregion + + #region Furniture - Le Corbusier LC 6 table + + var productCorbusierTable = _ctx.Set().First(x => x.Sku == "Furniture-lc6"); + + var corbusierTablePlate = _ctx.Set().First(x => x.ProductId == productCorbusierTable.Id && x.ProductAttributeId == attrPlate.Id); + var corbusierTablePlateValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == corbusierTablePlate.Id).ToList(); + + var corbusierTablePlateThickness = _ctx.Set().First(x => x.ProductId == productCorbusierTable.Id && x.ProductAttributeId == attrPlateThickness.Id); + var corbusierTablePlateThicknessValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == corbusierTablePlateThickness.Id).ToList(); + + entities.Add(new ProductVariantAttributeCombination { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productDualshock4Controller.Name)), - DisplayOrder = 1 + Product = productCorbusierTable, + Sku = productCorbusierTable.Sku + "-clear-15", + AttributesXml = FormatAttributeXml( + corbusierTablePlate.Id, corbusierTablePlateValues.First(x => x.Alias == "clear-glass").Id, + corbusierTablePlateThickness.Id, corbusierTablePlateThicknessValues.First(x => x.Alias == "15mm").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + Price = 749.00M + }); + entities.Add(new ProductVariantAttributeCombination + { + Product = productCorbusierTable, + Sku = productCorbusierTable.Sku + "-clear-19", + AttributesXml = FormatAttributeXml( + corbusierTablePlate.Id, corbusierTablePlateValues.First(x => x.Alias == "clear-glass").Id, + corbusierTablePlateThickness.Id, corbusierTablePlateThicknessValues.First(x => x.Alias == "19mm").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + Price = 899.00M + }); + entities.Add(new ProductVariantAttributeCombination + { + Product = productCorbusierTable, + Sku = productCorbusierTable.Sku + "-sandblasted-15", + AttributesXml = FormatAttributeXml( + corbusierTablePlate.Id, corbusierTablePlateValues.First(x => x.Alias == "sandblasted-glass").Id, + corbusierTablePlateThickness.Id, corbusierTablePlateThicknessValues.First(x => x.Alias == "15mm").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + Price = 849.00M + }); + entities.Add(new ProductVariantAttributeCombination + { + Product = productCorbusierTable, + Sku = productCorbusierTable.Sku + "-sandblasted-19", + AttributesXml = FormatAttributeXml( + corbusierTablePlate.Id, corbusierTablePlateValues.First(x => x.Alias == "sandblasted-glass").Id, + corbusierTablePlateThickness.Id, corbusierTablePlateThicknessValues.First(x => x.Alias == "19mm").Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + Price = 999.00M }); + #endregion + + #region Soccer Adidas TANGO SALA BALL + + var productAdidasTANGOSALABALL = _ctx.Set().First(x => x.Sku == "P-5001"); + var adidasTANGOSALABALLPictureIds = productAdidasTANGOSALABALL.ProductPictures.Select(x => x.PictureId).ToList(); + var adidasTANGOSALABALLJacketPictures = _ctx.Set().Where(x => adidasTANGOSALABALLPictureIds.Contains(x.Id)).ToList(); + + var adidasTANGOSALABALLColor = _ctx.Set().First(x => x.ProductId == productAdidasTANGOSALABALL.Id && x.ProductAttributeId == attrColor.Id); + var adidasTANGOSALABALLColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == adidasTANGOSALABALLColor.Id).ToList(); + + var adidasTANGOSALABALLSize = _ctx.Set().First(x => x.ProductId == productAdidasTANGOSALABALL.Id && x.ProductAttributeId == attrSize.Id); + var adidasTANGOSALABALLSizeValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == adidasTANGOSALABALLSize.Id).ToList(); + + var adidasTANGOSALABALLCombinations = new[] + { + new { Color = "Red", Size = "3" }, + new { Color = "Red", Size = "4" }, + new { Color = "Red", Size = "5" }, + + new { Color = "Yellow", Size = "3" }, + new { Color = "Yellow", Size = "4" }, + new { Color = "Yellow", Size = "5" }, + + new { Color = "Green", Size = "3" }, + new { Color = "Green", Size = "4" }, + new { Color = "Green", Size = "5" }, + + new { Color = "Blue", Size = "3" }, + new { Color = "Blue", Size = "4" }, + new { Color = "Blue", Size = "5" }, + + new { Color = "Gray", Size = "3" }, + new { Color = "Gray", Size = "4" }, + new { Color = "Gray", Size = "5" }, + + new { Color = "White", Size = "3" }, + new { Color = "White", Size = "4" }, + new { Color = "White", Size = "5" }, + + new { Color = "Brown", Size = "3" }, + new { Color = "Brown", Size = "4" }, + new { Color = "Brown", Size = "5" }, + + }; + + foreach (var comb in adidasTANGOSALABALLCombinations) + { + var lowerColor = comb.Color.ToLower(); + var lowerSize = comb.Size.ToLower(); + + entities.Add(new ProductVariantAttributeCombination + { + Product = productAdidasTANGOSALABALL, + Sku = productAdidasTANGOSALABALL.Sku + string.Concat("-", lowerColor, "-", lowerSize), + AttributesXml = FormatAttributeXml( + adidasTANGOSALABALLColor.Id, adidasTANGOSALABALLColorValues.First(x => x.Alias == lowerColor).Id, + adidasTANGOSALABALLSize.Id, adidasTANGOSALABALLSizeValues.First(x => x.Alias == lowerSize).Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = adidasTANGOSALABALLJacketPictures.First(x => x.SeoFilename.EndsWith(lowerColor)).Id.ToString() + }); + } + + #endregion Soccer Adidas TANGO SALA BALL + + #region Soccer Torfabrik official game ball + + var productTorfabrikBall = _ctx.Set().First(x => x.Sku == "P-5002"); + var torfabrikBallPictureIds = productTorfabrikBall.ProductPictures.Select(x => x.PictureId).ToList(); + var torfabrikBallPictures = _ctx.Set().Where(x => torfabrikBallPictureIds.Contains(x.Id)).ToList(); + + var torfabrikBallColor = _ctx.Set().First(x => x.ProductId == productTorfabrikBall.Id && x.ProductAttributeId == attrColor.Id); + var torfabrikBallColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == torfabrikBallColor.Id).ToList(); + + var torfabrikBallSize = _ctx.Set().First(x => x.ProductId == productTorfabrikBall.Id && x.ProductAttributeId == attrSize.Id); + var torfabrikBallSizeValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == torfabrikBallSize.Id).ToList(); + + var torfabrikBallSizeCombinations = new[] + { + new { Color = "Red", Size = "3" }, + new { Color = "Red", Size = "4" }, + new { Color = "Red", Size = "5" }, + + new { Color = "Yellow", Size = "3" }, + new { Color = "Yellow", Size = "4" }, + new { Color = "Yellow", Size = "5" }, + + new { Color = "Green", Size = "3" }, + new { Color = "Green", Size = "4" }, + new { Color = "Green", Size = "5" }, + + new { Color = "Blue", Size = "3" }, + new { Color = "Blue", Size = "4" }, + new { Color = "Blue", Size = "5" }, - var productPs4Camera = new Product() + new { Color = "White", Size = "3" }, + new { Color = "White", Size = "4" }, + new { Color = "White", Size = "5" }, + + }; + + foreach (var comb in torfabrikBallSizeCombinations) + { + var lowerColor = comb.Color.ToLower(); + var lowerSize = comb.Size.ToLower(); + + entities.Add(new ProductVariantAttributeCombination + { + Product = productTorfabrikBall, + Sku = productTorfabrikBall.Sku + string.Concat("-", lowerColor, "-", lowerSize), + AttributesXml = FormatAttributeXml( + torfabrikBallColor.Id, torfabrikBallColorValues.First(x => x.Alias == lowerColor).Id, + torfabrikBallSize.Id, torfabrikBallSizeValues.First(x => x.Alias == lowerSize).Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = torfabrikBallPictures.First(x => x.SeoFilename.EndsWith(lowerColor)).Id.ToString() + }); + } + + #endregion Soccer Torfabrik official game ball + + #region Furniture - Ball chair + + var productBallChair = _ctx.Set().First(x => x.Sku == "Furniture-ball-chair"); + var ballChairPictureIds = productBallChair.ProductPictures.Select(x => x.PictureId).ToList(); + var ballChairPictures = _ctx.Set().Where(x => ballChairPictureIds.Contains(x.Id)).ToList(); + + var ballChairMaterial = _ctx.Set().First(x => x.ProductId == productBallChair.Id && x.ProductAttributeId == attrMaterial.Id); + var ballChairMaterialValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == ballChairMaterial.Id).ToList(); + + var ballChairColor = _ctx.Set().First(x => x.ProductId == productBallChair.Id && x.ProductAttributeId == attrColor.Id); + var ballChairColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == ballChairColor.Id).ToList(); + + var ballChairLeatherColor = _ctx.Set().First(x => x.ProductId == productBallChair.Id && x.ProductAttributeId == attrLeatherColor.Id); + var ballChairLeatherColorValues = _ctx.Set().Where(x => x.ProductVariantAttributeId == ballChairLeatherColor.Id).ToList(); + + foreach (var materialValue in ballChairMaterialValues) { - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, + foreach (var colorValue in ballChairColorValues) + { + decimal ballChairPrice = 2199.00M; + + if (materialValue.Alias.StartsWith("leather-special")) + { + ballChairPrice = 2599.00M; + } + else if (materialValue.Alias.StartsWith("leather-aniline")) + { + ballChairPrice = 2999.00M; + } + + foreach (var leatherColorValue in ballChairLeatherColorValues) + { + entities.Add(new ProductVariantAttributeCombination + { + Product = productBallChair, + Sku = productBallChair.Sku + string.Concat("-", colorValue.Alias, "-", materialValue.Alias), + AttributesXml = FormatAttributeXml(ballChairMaterial.Id, materialValue.Id, ballChairColor.Id, colorValue.Id, ballChairLeatherColor.Id, leatherColorValue.Id), + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true, + AssignedPictureIds = ballChairPictures.First(x => x.SeoFilename.EndsWith(colorValue.Alias)).Id.ToString(), + Price = ballChairPrice + }); + } + } + } + + #endregion + + return entities; + } + + public IList ProductTags() + { + #region tag apple + var productTagApple = new ProductTag + { + Name = "apple" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "iPhone Plus").First().ProductTags.Add(productTagApple); + + #endregion tag apple + + #region tag gift + var productTagGift = new ProductTag + { + Name = "gift" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "$10 Virtual Gift Card").First().ProductTags.Add(productTagGift); + _ctx.Set().Where(pt => pt.MetaTitle == "$25 Virtual Gift Card").First().ProductTags.Add(productTagGift); + _ctx.Set().Where(pt => pt.MetaTitle == "$50 Virtual Gift Card").First().ProductTags.Add(productTagGift); + _ctx.Set().Where(pt => pt.MetaTitle == "$100 Virtual Gift Card").First().ProductTags.Add(productTagGift); + + #endregion tag gift + + #region tag book + var productTagBook = new ProductTag + { + Name = "book" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "Überman: The novel").First().ProductTags.Add(productTagBook); + _ctx.Set().Where(pt => pt.MetaTitle == "Best Grilling Recipes").First().ProductTags.Add(productTagBook); + _ctx.Set().Where(pt => pt.MetaTitle == "Car of superlatives").First().ProductTags.Add(productTagBook); + _ctx.Set().Where(pt => pt.MetaTitle == "Picture Atlas Motorcycles").First().ProductTags.Add(productTagBook); + _ctx.Set().Where(pt => pt.MetaTitle == "The Car Book").First().ProductTags.Add(productTagBook); + _ctx.Set().Where(pt => pt.MetaTitle == "Fast Cars").First().ProductTags.Add(productTagBook); + _ctx.Set().Where(pt => pt.MetaTitle == "Motorcycle Adventures").First().ProductTags.Add(productTagBook); + + #endregion tag book + + #region tag cooking + var productTagCooking = new ProductTag + { + Name = "cooking" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "Überman: The novel").FirstOrDefault().ProductTags.Add(productTagCooking); + _ctx.Set().Where(pt => pt.MetaTitle == "Best Grilling Recipes").FirstOrDefault().ProductTags.Add(productTagCooking); + + #endregion tag cooking + + #region tag cars + var productTagCars = new ProductTag + { + Name = "cars" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "The Car Book").FirstOrDefault().ProductTags.Add(productTagCars); + _ctx.Set().Where(pt => pt.MetaTitle == "Fast Cars").FirstOrDefault().ProductTags.Add(productTagCars); + + #endregion tag cars + + #region tag motorbikes + var productTagMotorbikes = new ProductTag + { + Name = "motorbikes" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "Fast Cars").FirstOrDefault().ProductTags.Add(productTagMotorbikes); + _ctx.Set().Where(pt => pt.MetaTitle == "Motorcycle Adventures").FirstOrDefault().ProductTags.Add(productTagMotorbikes); + + #endregion tag motorbikes + + #region tag mp3 + var productTagMP3 = new ProductTag + { + Name = "mp3" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "Antonio Vivaldi: spring").FirstOrDefault().ProductTags.Add(productTagMP3); + _ctx.Set().Where(pt => pt.MetaTitle == "Ludwig van Beethoven: Für Elise").FirstOrDefault().ProductTags.Add(productTagMP3); + + #endregion tag mp3 + + #region tag download + var productTagDownload = new ProductTag + { + Name = "download" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "Antonio Vivaldi: spring").FirstOrDefault().ProductTags.Add(productTagDownload); + _ctx.Set().Where(pt => pt.MetaTitle == "Ludwig van Beethoven: Für Elise").FirstOrDefault().ProductTags.Add(productTagDownload); + + #endregion tag download + + #region tag watches + var productTagWatches = new ProductTag + { + Name = "watches" + }; + + _ctx.Set().Where(pt => pt.MetaTitle == "Certina DS Podium Big Size").FirstOrDefault().ProductTags.Add(productTagWatches); + + #endregion tag download + + var entities = new List + { + productTagGift, productTagBook, productTagCooking, productTagCars, productTagMotorbikes, + productTagMP3, productTagDownload + }; + + this.Alter(entities); + return entities; + } + + public IList CategoriesFirstLevel() + { + var sampleImagesPath = this._sampleImagesPath; + var categoryTemplateInGridAndLines = this.CategoryTemplates().Where(pt => pt.ViewPath == "CategoryTemplate.ProductsInGridOrLines").FirstOrDefault(); + + #region category definitions + + var categoryFurniture = new Category + { + Name = "Furniture", + Alias = "Furniture", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_furniture.jpg"), "image/jpeg", GetSeName("Furniture")), + Published = true, + DisplayOrder = 1, + MetaTitle = "Furniture", + ShowOnHomePage = true + }; + + var categoryApple = new Category + { + Name = "Apple", + Alias = "Apple", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/png", GetSeName("Apple")), + Published = true, + DisplayOrder = 1, + MetaTitle = "Apple", + ShowOnHomePage = true + }; + + var categorySports = new Category + { + Name = "Sports", + Alias = "Sports", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_sports.jpg"), "image/jpeg", GetSeName("Sports")), + Published = true, + DisplayOrder = 1, + MetaTitle = "Sports", + ShowOnHomePage = true + }; + + var categoryBooks = new Category + { + Name = "Books", + Alias = "Books", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "emblem_library.png"), "image/jpeg", GetSeName("Books")), Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "PlayStation 4 Camera", - Price = 59.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DisplayOrder = 1, + MetaTitle = "Books" + }; + + //var categoryComputers = new Category + //{ + // Name = "Computers", + // Alias = "Computers", + // CategoryTemplateId = categoryTemplateInGridAndLines.Id, + // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_computers.png"), "image/png", GetSeName("Computers")), + // Published = true, + // DisplayOrder = 2, + // MetaTitle = "Computers" + //}; + + var categoryFashion = new Category + { + Name = "Fashion", + Alias = "Fashion", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_fashion.jpg"), "image/png", GetSeName("Fashion")), + Published = true, + DisplayOrder = 2, + MetaTitle = "Fashion", + ShowOnHomePage = true, + BadgeText = "SALE", + BadgeStyle = 4 + }; + + var categoryGaming = new Category + { + Name = "Gaming", + Alias = "Gaming", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ps4_bundle_minecraft.jpg"), "image/png", GetSeName("Gaming")), + Published = true, + DisplayOrder = 3, + ShowOnHomePage = true, + MetaTitle = "Gaming" }; - productPs4Camera.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4Camera.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 3 }); + //var categoryCellPhones = new Category + //{ + // Name = "Cell phones", + // Alias = "Cell phones", + // CategoryTemplateId = categoryTemplateInGridAndLines.Id, + // //ParentCategoryId = categoryElectronics.Id, + // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_cellphone.png"), "image/png", GetSeName("Cell phones")), + // Published = true, + // DisplayOrder = 4, + // MetaTitle = "Cell phones" + //}; + + var categoryDigitalDownloads = new Category + { + Name = "Digital Products", + Alias = "Digital Products", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_digitalproducts.jpg"), "image/jpeg", GetSeName("Digital Products")), + Published = true, + DisplayOrder = 6, + MetaTitle = "Digital Products", + ShowOnHomePage = true + }; + + var categoryGiftCards = new Category + { + Name = "Gift Cards", + Alias = "Gift Cards", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gift-cards.png"), "image/png", GetSeName("Gift Cards")), + Published = true, + DisplayOrder = 12, + MetaTitle = "Gift cards", + ShowOnHomePage = true, + }; + + var categoryWatches = new Category + { + Name = "Watches", + Alias = "Watches", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_watches.png"), "image/png", GetSeName("Watches")), + Published = true, + DisplayOrder = 10, + MetaTitle = "Watches", + ShowOnHomePage = true, + BadgeText = "%", + BadgeStyle = 5 + }; + + #endregion + + var entities = new List + { + categoryApple, categorySports, categoryBooks, categoryFurniture, categoryDigitalDownloads, categoryGaming, + categoryGiftCards, categoryFashion, categoryWatches + }; + + this.Alter(entities); + return entities; + } + + public IList CategoriesSecondLevel() + { + var sampleImagesPath = this._sampleImagesPath; + var categoryTemplateInGridAndLines = this.CategoryTemplates().Where(pt => pt.ViewPath == "CategoryTemplate.ProductsInGridOrLines").FirstOrDefault(); + + #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", + Alias = "Golf", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_golf.jpg"), "image/png", GetSeName("Golf")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Sports").First().Id, + DisplayOrder = 1, + MetaTitle = "Golf", + ShowOnHomePage = true + }; + + var categorySportsSunglasses = new Category + { + Name = "Sunglasses", + Alias = "Sunglasses", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_glasses.png"), "image/png", GetSeName("Sunglasses")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Fashion").First().Id, + DisplayOrder = 1, + MetaTitle = "Sunglasses", + ShowOnHomePage = true + }; + + var categorySportsSoccer = new Category + { + Name = "Soccer", + Alias = "Soccer", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_soccer.png"), "image/png", GetSeName("Soccer")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Sports").First().Id, + DisplayOrder = 1, + MetaTitle = "Soccer", + ShowOnHomePage = true + }; + + var categorySportsBasketball = new Category + { + Name = "Basketball", + Alias = "Basketball", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_basketball.png"), "image/png", GetSeName("Basketball")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Sports").First().Id, + DisplayOrder = 1, + MetaTitle = "Basketball", + ShowOnHomePage = true + }; + + var categoryBooksSpiegel = new Category + { + Name = "SPIEGEL-Bestseller", + Alias = "SPIEGEL-Bestseller", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000930_spiegel-bestseller.png"), "image/png", GetSeName("SPIEGEL-Bestseller")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Books").First().Id, + DisplayOrder = 1, + MetaTitle = "SPIEGEL-Bestseller" + }; + + var categoryBooksCookAndEnjoy = new Category + { + Name = "Cook and enjoy", + Alias = "Cook and enjoy", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000936_kochen-geniesen.jpeg"), "image/jpeg", GetSeName("Cook and enjoy")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Books").First().Id, + DisplayOrder = 2, + MetaTitle = "Cook and enjoy" + }; + + //var categoryDesktops = new Category + //{ + // Name = "Desktops", + // Alias = "Desktops", + // CategoryTemplateId = categoryTemplateInGridAndLines.Id, + // ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Computers").First().Id, + // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_desktops.png"), "image/png", GetSeName("Desktops")), + // Published = true, + // DisplayOrder = 1, + // MetaTitle = "Desktops" + //}; + + //var categoryNotebooks = new Category + //{ + // Name = "Notebooks", + // Alias = "Notebooks", + // CategoryTemplateId = categoryTemplateInGridAndLines.Id, + // ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Computers").First().Id, + // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_notebooks.png"), "image/png", GetSeName("Notebooks")), + // Published = true, + // DisplayOrder = 2, + // MetaTitle = "Notebooks" + //}; + + var categoryGamingAccessories = new Category + { + Name = "Gaming Accessories", + Alias = "Gaming Accessories", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Gaming").First().Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName("Gaming Accessories")), + Published = true, + DisplayOrder = 2, + MetaTitle = "Gaming Accessories" + }; + + var categoryGamingGames = new Category + { + Name = "Games", + Alias = "Games", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Gaming").First().Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_games.jpg"), "image/png", GetSeName("Games")), + Published = true, + DisplayOrder = 3, + MetaTitle = "Games" + }; + + #endregion + + var entities = new List + { + categorySportsSunglasses,categorySportsSoccer, categorySportsBasketball,categorySportsGolf, categoryBooksSpiegel, categoryBooksCookAndEnjoy, + categoryGamingAccessories, categoryGamingGames, categoryFashionJackets, categoryFashionLeatherJackets, categoryFashionShoes, categoryFashionTrousers, + categoryFashionTracksuits + }; + + this.Alter(entities); + return entities; + } + + public IList Manufacturers() + { + //pictures + var sampleImagesPath = this._sampleImagesPath; + + var manufacturerTemplateInGridAndLines = + this.ManufacturerTemplates().Where(pt => pt.ViewPath == "ManufacturerTemplate.ProductsInGridOrLines").FirstOrDefault(); + + //var categoryTemplateInGridAndLines = + // this.CategoryTemplates().Where(pt => pt.Name == "Products in Grid or Lines").FirstOrDefault(); + + // + + #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 manufacturerEASports = new Manufacturer + { + Name = "EA Sports", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_EA_Sports.png"), "image/png", GetSeName("EA Sports")), + Published = true, + DisplayOrder = 1 + }; + + #endregion EA Sports + + #region Warner Home Video Games + + var manufacturerWarnerHome = new Manufacturer + { + Name = "Warner Home Video Games", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_wb.png"), "image/png", GetSeName("Warner Home Video Games")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Warner Home Video Games + + #region Breitling + + var manufacturerBreitling = new Manufacturer + { + Name = "Breitling", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_breitling.png"), "image/png", GetSeName("Breitling")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Breitling + + #region Tissot + + var manufacturerTissot = new Manufacturer + { + Name = "Tissot", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_Tissot.png"), "image/png", GetSeName("Tissot")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Tissot + + #region Seiko + + var manufacturerSeiko = new Manufacturer + { + Name = "Seiko", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_seiko.png"), "image/png", GetSeName("Seiko")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Seiko + + #region Titleist + + var manufacturerTitleist = new Manufacturer + { + Name = "Titleist", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_titleist.png"), "image/png", GetSeName("Titleist")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Titleist + + #region Puma + + var manufacturerPuma = new Manufacturer + { + Name = "Puma", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_puma.jpg"), "image/png", GetSeName("Puma")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Puma + + #region Nike + + var manufacturerNike = new Manufacturer + { + Name = "Nike", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_nike.png"), "image/png", GetSeName("Nike")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Nike + + #region Wilson + + var manufacturerWilson = new Manufacturer + { + Name = "Wilson", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_wilson.png"), "image/png", GetSeName("Wilson")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Wilson + + #region Adidas + + var manufacturerAdidas = new Manufacturer + { + Name = "Adidas", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_adidas.png"), "image/png", GetSeName("Adidas")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Adidas + + #region Ray-ban + + var manufacturerRayban = new Manufacturer + { + Name = "Ray-Ban", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_ray-ban.jpg"), "image/png", GetSeName("Ray-Ban")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Ray-ban + + #region Oakley + + var manufacturerOakley = new Manufacturer + { + Name = "Oakley", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_oakley.png"), "image/png", GetSeName("Oakley")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Oakley + + #region Apple + + var manufacturerApple = new Manufacturer + { + Name = "Apple", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_apple.png"), "image/png", GetSeName("Apple")), + Published = true, + DisplayOrder = 1 + }; + + #endregion Apple + + #region Android + + var manufacturerAndroid = new Manufacturer + { + Name = "Android", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-android.png"), "image/png", GetSeName("Android")), + Published = true, + DisplayOrder = 2 + }; + + #endregion Android + + #region LG + + var manufacturerLG = new Manufacturer + { + Name = "LG", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-lg.png"), "image/png", GetSeName("LG")), + Published = true, + DisplayOrder = 3 + }; + + #endregion LG + + #region Dell + + var manufacturerDell = new Manufacturer + { + Name = "Dell", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-dell.png"), "image/png", GetSeName("Dell")), + Published = true, + DisplayOrder = 4 + }; + + #endregion Dell + + #region HP + + var manufacturerHP = new Manufacturer + { + Name = "HP", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-hp.png"), "image/png", GetSeName("HP")), + Published = true, + DisplayOrder = 5 + }; + + #endregion HP + + #region Microsoft + + var manufacturerMicrosoft = new Manufacturer + { + Name = "Microsoft", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-microsoft.png"), "image/png", GetSeName("Microsoft")), + Published = true, + DisplayOrder = 6 + }; + + #endregion Microsoft + + #region Samsung + + var manufacturerSamsung = new Manufacturer + { + Name = "Samsung", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-samsung.png"), "image/png", GetSeName("Samsung")), + Published = true, + DisplayOrder = 7 + }; + + #endregion Samsung + + #region Acer + + var manufacturerAcer = new Manufacturer + { + Name = "Acer", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "acer-logo.jpg"), "image/pjpeg", GetSeName("Acer")), + Published = true, + DisplayOrder = 8 + }; + + #endregion Acer + + #region TrekStor + + var manufacturerTrekStor = new Manufacturer + { + Name = "TrekStor", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-trekstor.png"), "image/png", GetSeName("TrekStor")), + Published = true, + DisplayOrder = 9 + }; + + #endregion TrekStor + + #region Western Digital + + var manufacturerWesternDigital = new Manufacturer + { + Name = "Western Digital", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-westerndigital.png"), "image/png", GetSeName("Western Digital")), + Published = true, + DisplayOrder = 10 + }; + + #endregion Western Digital + + #region MSI + + var manufacturerMSI = new Manufacturer + { + Name = "MSI", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-msi.png"), "image/png", GetSeName("MSI")), + Published = true, + DisplayOrder = 11 + }; + + #endregion MSI + + #region Canon + + var manufacturerCanon = new Manufacturer + { + Name = "Canon", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-canon.png"), "image/png", GetSeName("Canon")), + Published = true, + DisplayOrder = 12 + }; + + #endregion Canon + + #region Casio + + var manufacturerCasio = new Manufacturer + { + Name = "Casio", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-casio.png"), "image/png", GetSeName("Casio")), + Published = true, + DisplayOrder = 13 + }; + + #endregion Casio + + #region Panasonic + + var manufacturerPanasonic = new Manufacturer + { + Name = "Panasonic", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-panasonic.png"), "image/png", GetSeName("Panasonic")), + Published = true, + DisplayOrder = 14 + }; + + #endregion Panasonic + + #region BlackBerry + + var manufacturerBlackBerry = new Manufacturer + { + Name = "BlackBerry", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-blackberry.png"), "image/png", GetSeName("BlackBerry")), + Published = true, + DisplayOrder = 15 + }; + + #endregion BlackBerry + + #region HTC + + var manufacturerHTC = new Manufacturer + { + Name = "HTC", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-htc.png"), "image/png", GetSeName("HTC")), + Published = true, + DisplayOrder = 16 + }; + + #endregion HTC + + #region Festina + + var manufacturerFestina = new Manufacturer + { + Name = "Festina", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_festina.png"), "image/png", GetSeName("Festina")), + Published = true, + DisplayOrder = 17 + }; + + #endregion Festina + + #region Certina + + var manufacturerCertina = new Manufacturer + { + Name = "Certina", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer-certina.png"), "image/png", GetSeName("Certina")), + Published = true, + DisplayOrder = 18 + }; + + #endregion Certina + + #region Sony + + var manufacturerSony = new Manufacturer + { + Name = "Sony", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_sony.png"), "image/png", GetSeName("Sony")), + Published = true, + DisplayOrder = 19 + }; + + #endregion Sony + + #region Ubisoft + + var manufacturerUbisoft = new Manufacturer + { + Name = "Ubisoft", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_ubisoft.png"), "image/png", GetSeName("Ubisoft")), + Published = true, + DisplayOrder = 20 + }; + + #endregion Ubisoft + + var entities = new List + { + 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, manufacturerMeyAndEdlich, manufacturerJackWolfskin, manufacturerMicrosoft + }; + + this.Alter(entities); + return entities; + } + + private List GetFashionProducts(Dictionary specAttributes) + { + var result = new List(); + var productTemplateSimple = _ctx.Set().First(x => x.ViewPath == "Product"); + var firstDeliveryTime = _ctx.Set().First(x => x.DisplayOrder == 0); + var fashionCategory = _ctx.Set().First(x => x.Alias == "Fashion"); + var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); + var specOptionCotton = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 9); + + // Converse All Star + var converseAllStar = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Converse All Star", + MetaTitle = "Converse All Star", + ShortDescription = "The classical sneaker!", + FullDescription = "

Since 1912 and to this day unrivalled: the converse All Star sneaker. A shoe for every occasion.

", + Sku = "Fashion-112355", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + ShowOnHomePage = true, + Price = 79.90M, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = firstDeliveryTime, + DisplayOrder = 1 + }; + + converseAllStar.ProductCategories.Add(new ProductCategory + { + Category = fashionCategory, + DisplayOrder = 1 + }); + + var allStarImages = new string[] { "product_allstar_converse.jpg", "product_allstar_hi_charcoal.jpg", "product_allstar_hi_maroon.jpg", "product_allstar_hi_navy.jpg", + "product_allstar_hi_purple.jpg", "product_allstar_hi_white.jpg" }; + + for (var i = 0; i < allStarImages.Length; ++i) + { + converseAllStar.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + allStarImages[i]), "image/jpeg", allStarImages[i].Replace("product_", "").Replace(".jpg", "")), + DisplayOrder = i + 1 + }); + } + + converseAllStar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + + result.Add(converseAllStar); + + // Shirt Meccanica + var shirtMeccanica = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Sleeveless shirt Meccanica", + MetaTitle = "Sleeveless shirt Meccanica", + ShortDescription = "Woman shirt with trendy imprint", + FullDescription = "

Also in summer, the Ducati goes with fashion style! With the sleeveless shirt Meccanica, every woman can express her passion for Ducati with a comfortable and versatile piece of clothing. The shirt is available in black and vintage red. It carries on the front the traditional lettering in plastisol print, which makes it even clearer and more radiant, while on the back in the neck area is the famous logo with the typical \"wings\" of the fifties.

", + Sku = "Fashion-987693502", + ManufacturerPartNumber = "987693502", + Gtin = "987693502", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + Price = 38.00M, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = firstDeliveryTime, + DisplayOrder = 4 + }; + + shirtMeccanica.ProductCategories.Add(new ProductCategory + { + Category = fashionCategory, + DisplayOrder = 1 + }); + + shirtMeccanica.TierPrices.Add(new TierPrice + { + Quantity = 10, + Price = 36.00M + }); + shirtMeccanica.TierPrices.Add(new TierPrice + { + Quantity = 50, + Price = 29.00M + }); + + 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) + { + shirtMeccanica.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + shirtMeccanicaImages[i]), "image/jpeg", shirtMeccanicaImages[i].Replace("product_", "").Replace(".jpg", "")), + DisplayOrder = i + 1 + }); + } + + shirtMeccanica.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + + result.Add(shirtMeccanica); + + // Ladies jacket + var ladiesJacket = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Ladies Sports Jacket", + MetaTitle = "Ladies Sports Jacket", + FullDescription = "

Lightweight wind and water repellent fabric, lining of soft single jersey knit cuffs on arm and waistband. 2 side pockets with zipper, hood in slightly waisted cut.

    Material: 100% polyamideLining: 65% polyester, 35% cottonLining 2: 100% polyester.
", + Sku = "Fashion-JN1107", + ManufacturerPartNumber = "JN1107", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + Price = 55.00M, + OldPrice = 60.00M, + ProductCost = 20.00M, + SpecialPrice = 52.99M, + SpecialPriceStartDateTimeUtc = new DateTime(2017, 5, 1, 0, 0, 0), + SpecialPriceEndDateTimeUtc = specialPriceEndDate, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = firstDeliveryTime, + DisplayOrder = 2 + }; + + ladiesJacket.ProductCategories.Add(new ProductCategory + { + Category = fashionCategory, + DisplayOrder = 1 + }); + + 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) + { + ladiesJacket.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + ladiesJacketImages[i]), "image/jpeg", ladiesJacketImages[i].Replace("product_", "").Replace(".jpg", "")), + DisplayOrder = i + 1 + }); + } + + ladiesJacket.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 11) + }); + + result.Add(ladiesJacket); + + // Clark Premium Blue Jeans + var clarkJeans = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Clark Premium Blue Jeans", + MetaTitle = "Clark Premium Blue Jeans", + ShortDescription = "Modern Jeans in Easy Comfort Fit", + FullDescription = "

Real five-pocket jeans by Joker with additional, set-up pocket. Thanks to easy comfort fit with normal rise and comfortable leg width suitable for any character.

  • Material: softer, lighter premium denim made of 100% cotton.
  • Waist (inch): 29-46
  • leg (inch): 30 to 38
", + Sku = "Fashion-65986524", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + Price = 109.90M, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = firstDeliveryTime, + DisplayOrder = 5 + }; + + clarkJeans.ProductCategories.Add(new ProductCategory + { + Category = fashionCategory, + DisplayOrder = 1 + }); + + clarkJeans.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_clark_premium_jeans.jpg"), "image/jpeg", "clark_premium_jeans"), + DisplayOrder = 1 + }); + + clarkJeans.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + + result.Add(clarkJeans); + + return result; + } + + 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"); + + // Le Corbusier LC 6 table + var corbusierTable = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Le Corbusier LC 6 dining table (1929)", + MetaTitle = "Le Corbusier LC 6 dining table (1929)", + ShortDescription = "Dining table LC 6, designer: Le Corbusier, W x H x D: 225 x 69/74 (adjustable) x 85 cm, substructure: steel pipe, glass plate: Clear or sandblasted, 15 or 19 mm, height-adjustable.", + FullDescription = "

Four small plates carry a glass plate. The structure of the steel pipe is covered in clear structures. The LC6 is a true classic of Bauhaus art and is used in combination with the swivel chairs LC7 as a form-beautiful Le Corbusier dining area. In addition, the table is also increasingly found in offices or in halls. It is height-adjustable and can thus be perfectly adapted to the respective purpose.

Le Corbusier's beautifully shaped table is available with a clear or sandblasted glass plate. The substructure consists of oval steel tubes.

", + Sku = "Furniture-lc6", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + Price = 749.00M, + HasTierPrices = true, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = thirdDeliveryTime + }; + + corbusierTable.ProductCategories.Add(new ProductCategory + { + Category = furnitureCategory, + DisplayOrder = 1 + }); + + var corbusierTableImages = new string[] { "product_corbusier_lc6_table_1.jpg", "product_corbusier_lc6_table_2.jpg", "product_corbusier_lc6_table_3.jpg", + "product_corbusier_lc6_table_4.jpg" }; + + for (var i = 0; i < corbusierTableImages.Length; ++i) + { + corbusierTable.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + corbusierTableImages[i]), "image/jpeg", corbusierTableImages[i].Replace("product_", "").Replace(".jpg", "")), + DisplayOrder = i + 1 + }); + } + + corbusierTable.TierPrices.Add(new TierPrice + { + Quantity = 2, + Price = 647.10M + }); + corbusierTable.TierPrices.Add(new TierPrice + { + Quantity = 4, + Price = 636.65M + }); + + corbusierTable.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + corbusierTable.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 14) + }); + + result.Add(corbusierTable); + + // Ball Chair + var ballChair = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Eero Aarnio Ball Chair (1966)", + MetaTitle = "Eero Aarnio Ball Chair (1966)", + FullDescription = "

The ball chair, or also called the globe chair, is a real masterpiece of the legendary designer Eero Aarnio. The ball chair from the Sixties has written designer history. The egg designed armchair rests on a trumpet foot and is not lastly appreciated due to its shape and the quiet atmosphere inside this furniture. The design of the furniture body allows noise and disturbing outer world elements in the Hintergurnd us. A place as created for resting and relaxing. With its wide range of colours, the eyeball chair fits in every living and working environment. A chair that stands out for its timeless design and always has the modern look. The ball chair is 360° to rotate to change the view of the surroundings. The outer shell in fiberglass white or black. The upholstery is mixed in leather or linen.

Dimension: Width 102 cm, depth 87 cm, height 124 cm, seat height: 44 cm.

", + Sku = "Furniture-ball-chair", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + Price = 2199.00M, + HasTierPrices = true, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = thirdDeliveryTime + }; + + ballChair.ProductCategories.Add(new ProductCategory + { + Category = furnitureCategory, + DisplayOrder = 1 + }); + + ballChair.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_ball_chair_white.jpg"), "image/jpeg", "ball_chair_white"), + DisplayOrder = 1 + }); + ballChair.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_ball_chair_black.jpg"), "image/jpeg", "ball_chair_black"), + DisplayOrder = 2 + }); + + ballChair.TierPrices.Add(new TierPrice + { + Quantity = 2, + Price = 1979.10M + }); + ballChair.TierPrices.Add(new TierPrice + { + Quantity = 4, + Price = 1869.15M + }); + + ballChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + ballChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + + result.Add(ballChair); + + // Lounge chair + var loungeChair = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Charles Eames Lounge Chair (1956)", + MetaTitle = "Charles Eames Lounge Chair (1956)", + ShortDescription = "Club lounge chair, designer: Charles Eames, width 80 cm, depth 80 cm, height 60 cm, seat shell: plywood, foot (rotatable): Aluminium casting, cushion (upholstered) with leather cover.", + FullDescription = "

That's how you sit in a baseball glove. In any case, this was one of the ideas Charles Eames had in mind when designing this club chair. The lounge chair should be a comfort armchair, in which one can sink luxuriously. Through the construction of three interconnected, movable seat shells and a comfortable upholstery Charles Eames succeeded in the implementation. In fact, the club armchair with a swiveling foot is a contrast to the Bauhaus characteristics that emphasized minimalism and functionality. Nevertheless, he became a classic of Bauhaus history and still provides in many living rooms and clubs for absolute comfort with style.

Dimensions: Width 80 cm, depth 60 cm, height total 80 cm (height backrest: 60 cm). CBM: 0.70.

Lounge chair with seat shell of laminated curved plywood with rosewood veneer, walnut nature or in black. Rotatable base made of aluminium cast black with polished edges or optionally fully chromed. Elaborate upholstery of pillows in leather.

All upholstery units are removable at the Eames Lounge chair (seat, armrest, backrest, headrest).

", + Sku = "Furniture-lounge-chair", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + ShowOnHomePage = true, + Price = 1799.00M, + OldPrice = 1999.00M, + HasTierPrices = true, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = thirdDeliveryTime + }; + + loungeChair.ProductCategories.Add(new ProductCategory + { + Category = furnitureCategory, + DisplayOrder = 1 + }); + + loungeChair.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_charles_eames_lounge_chair_white.jpg"), "image/jpeg", "charles_eames_lounge_chair_white"), + DisplayOrder = 1 + }); + loungeChair.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_charles_eames_lounge_chair_black.jpg"), "image/jpeg", "charles_eames_lounge_chair_black"), + DisplayOrder = 2 + }); + + loungeChair.TierPrices.Add(new TierPrice + { + Quantity = 2, + Price = 1709.05M + }); + loungeChair.TierPrices.Add(new TierPrice + { + Quantity = 4, + Price = 1664.08M + }); + loungeChair.TierPrices.Add(new TierPrice + { + Quantity = 6, + Price = 1619.10M + }); + + loungeChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 13) + }); + loungeChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + loungeChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 4) + }); + + result.Add(loungeChair); + + // Cube chair + var cubeChair = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Josef Hoffmann cube chair (1910)", + MetaTitle = "Josef Hoffmann cube chair (1910)", + ShortDescription = "Armchair Cube, Designer: Josef Hoffmann, width 93 cm, depth 72 cm, height 77 cm, basic frame: solid beech wood, upholstery: solid polyurethane foam (shape resistant), Upholstery: leather", + FullDescription = "

The cube chair by Josef Hoffmann holds what the name promises and that is the same in two respects. It consists of many squares, both in terms of construction and in relation to the design of the surface. In addition, the cube, with its purely geometric form, was a kind of harbinger of cubism. The chair by Josef Hoffmann was designed in 1910 and still stands today as a replica in numerous business and residential areas.

Originally, the cube was a club chair. Together with the two-and three-seater sofa of the series, a cosy sitting area with a sophisticated charisma is created. The basic frame of the armchair is made of wood. The form-resistant upholstery is covered with leather and has been shaped visually to squares with a special sewing.

Dimensions: Width 93 cm, depth 72 cm, height 77 cm. CBM: 0.70.

", + Sku = "Furniture-cube-chair", + ProductTemplateId = productTemplateSimple.Id, + AllowCustomerReviews = true, + Published = true, + ShowOnHomePage = true, + Price = 2299.00M, + HasTierPrices = true, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = thirdDeliveryTime + }; + + cubeChair.ProductCategories.Add(new ProductCategory + { + Category = furnitureCategory, + DisplayOrder = 1 + }); + + cubeChair.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_hoffmann_cube_chair_black.jpg"), "image/jpeg", "hoffmann_cube_chair_black"), + DisplayOrder = 1 + }); + + cubeChair.TierPrices.Add(new TierPrice + { + Quantity = 4, + Price = 1899.05M + }); + cubeChair.TierPrices.Add(new TierPrice + { + Quantity = 6, + Price = 1799.10M + }); + + cubeChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + + result.Add(cubeChair); + + return result; + } + + public IList Products() + { + var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); + + 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); + + 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); + + + //#region category shoes + + //var categoryShoes = _ctx.Set().First(x => x.Alias == "Shoes"); + + //#region product Jack Wolfskin COOGEE LOW M + + //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 + + + #region category golf + + #region product Titleist SM6 Tour Chrome + + var productTitleistSM6TourChrome = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Titleist SM6 Tour Chrome", + IsEsd = false, + ShortDescription = "For golfers who want maximum impact control and feedback.", + FullDescription = "​​

Inspired by the best iron players in the world

The new 'Spin Milled 6' wages establish a new performance class in three key areas of the Wedge game: precise length steps, bounce and maximum spin.


For each loft the center of gravity of the wedge is determined individually. Therefore, the SM6 offers a particularly precise length and flight curve control combined with great impact.
Bob Vokey's tourer-puffed sole cleat allows all golfers more bounce, adapted to their personal swing profile and the respective ground conditions.


A new, parallel face texture was developed for the absolutely exact and with 100% quality control machined grooves. The result is a consistently higher edge sharpness for more spin.

  • Precise lengths and flight curve control thanks to progressively placed center of gravity.
  • Improved bounce due to Bob Vokey's proven soles.
  • TX4 grooves produce more spin through a new surface and edge sharpness.
  • Multiple personalization options.
", + Sku = "P-7004", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Titleist SM6 Tour Chrome", + Price = 164.95M, + OldPrice = 199.95M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productTitleistSM6TourChrome, "product_titleist_sm6_tour_chrome.jpg"); + + productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory { Category = categories["Golf"], 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Titleist Pro V1x", + IsEsd = false, + ShortDescription = "Golf ball with high ball flight", + FullDescription = "​​The top players rely on the new Titleist Pro V1x. High ball flight, soft feel and more spin in the short game are the advantages of the V1x version. Perfect performance from the leading manufacturer. The new Titleist Pro V1 golf ball is exactly defined and promises penetrating ball flight with very soft hit feeling.", + Sku = "P-7001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Titleist Pro V1x", + Price = 2.1M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productTitleistProV1x, "product_titleist-pro-v1x.jpg"); + + productTitleistProV1x.ProductCategories.Add(new ProductCategory { Category = categories["Golf"], 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Supreme Golfball", + IsEsd = false, + ShortDescription = "Training balls with perfect flying characteristics", + FullDescription = "​Perfect golf exercise ball with the characteristics like the 'original', but in a glass-fracture-proof execution. Massive core, an ideal training ball for yard and garden. Colors: white, yellow, orange.", + Sku = "P-7002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Supreme Golfball", + Price = 1.9M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productSupremeGolfball, "product_supremeGolfball_1.jpg", "golfball-1"); + AddProductPicture(productSupremeGolfball, "product_supremeGolfball_2.jpg", "golfball-2"); + + productSupremeGolfball.ProductCategories.Add(new ProductCategory { Category = categories["Golf"], 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "GBB Epic Sub Zero Driver", + IsEsd = false, + ShortDescription = "Low spin for good golfing!", + FullDescription = "Your game wins with the GBB Epic Sub Zero Driver. A golf club with an extremely low spin and the phenomenal high-speed characteristic.", + Sku = "P-7003", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "GBB Epic Sub Zero Driver", + Price = 489M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productGBBEpicSubZeroDriver, "product_gbb-epic-sub-zero-driver.jpg"); + + productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory { Category = categories["Golf"], DisplayOrder = 1 }); + + productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Titleist"], DisplayOrder = 1 }); + + #endregion product GBB Epic Sub Zero Driver + + #endregion category golf + + #region category Soccer + + #region product Nike Strike Football + + 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.
", + Sku = "P-5004", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Nike Strike Football", + Price = 59.90M, + OldPrice = 69.90M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id, + HasTierPrices = true + }; + + AddProductPicture(productNikeStrikeFootball, "products_nike-strike-football.jpg"); + + productNikeStrikeFootball.ProductCategories.Add(new ProductCategory { Category = categories["Soccer"], DisplayOrder = 1 }); + + productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Nike"], DisplayOrder = 1 }); + + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Manufacturer -> Nike + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 20) + }); + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Material -> rubber + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 12) + }); + + 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Evopower 5.3 Trainer HS Ball", + IsEsd = false, + ShortDescription = "Entry level training ball.", + FullDescription = "

Entry level training ball.< p > Constructed from 32 panels with equal surface areas for reduced seam-stress and a perfectly round shape.Handstitched panels with multilayered woven backing for enhanced stability and aerodynamics.", + Sku = "P-5003", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Evopower 5.3 Trainer HS Ball", + Price = 59.90M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productNikeEvoPowerBall, "product_nike-vopower-53-trainer-hs-ball.jpg"); + + productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory { Category = categories["Soccer"], DisplayOrder = 1 }); + + productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Nike"], DisplayOrder = 1 }); + + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Manufacturer -> Nike + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 20) + }); + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Material -> leather + 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Torfabrik official game ball", + IsEsd = false, + ShortDescription = "Available in different colors", + FullDescription = "", + Sku = "P-5002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Torfabrik official game ball", + Price = 59.90M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + 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.ProductCategories.Add(new ProductCategory { Category = categories["Soccer"], DisplayOrder = 1 }); + + productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); + + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Manufacturer -> Adidas + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 19) + }); + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Material -> leather + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + + #endregion Torfabrik official game ball + + #region product Adidas TANGO SALA BALL + + var productAdidasTangoSalaBall = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Adidas TANGO SALA BALL", + IsEsd = false, + ShortDescription = "In different colors", + FullDescription = "

TANGO SALA BALL
A SALA BALL TO MATCH YOUR INDOOR PLAYMAKING.

Take the game indoors. With a design nod to the original Tango ball that set the performance standard, this indoor soccer is designed for low rebound and enhanced control for futsal. Machine-stitched for a soft touch and high durability.

  • Machine-stitched for soft touch and high durability
  • Low rebound for enhanced ball control
  • Butyl bladder for best air retention
  • Requires inflation
  • 100% natural rubber
  • Imported

", + Sku = "P-5001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Adidas TANGO SALA BALL", + Price = 59.90M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + 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.ProductCategories.Add(new ProductCategory { Category = categories["Soccer"], DisplayOrder = 1 }); + + productAdidasTangoSalaBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); + + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Manufacturer -> Adidas + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 19) + }); + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Material -> leather + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + + #endregion Adidas TANGO SALA BALL + + #endregion category Soccer + + #region category Basketball + + #region Wilson Evolution High School Game Basketball + + var productEvolutionHighSchoolGameBasketball = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Evolution High School Game Basketball", + IsEsd = false, + ShortDescription = "For all positions on all levels, match day and every day", + FullDescription = "

The Wilson Evolution High School Game Basketball has exclusive microfiber composite leather construction with deep embossed pebbles to give you the ultimate in feel and control.

Its patented Cushion Core Technology enhances durability for longer play. This microfiber composite Evolution high school basketball is pebbled with composite channels for better grip, helping players raise their game to the next level.

For all positions at all levels of play, game day and every day, Wilson delivers the skill-building performance that players demand.

This regulation-size 29.5' Wilson basketball is an ideal basketball for high school players, and is designed for either recreational use or for league games. It is NCAA and NFHS approved, so you know it's a high-quality basketball that will help you hone your shooting, passing and ball-handling skills.

Take your team all the way to the championship with the Wilson Evolution High School Game Basketball.

", + Sku = "P-4001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Evolution High School Game Basketball", + Price = 25.90M, + OldPrice = 29.90M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id, + HasTierPrices = true + }; + + AddProductPicture(productEvolutionHighSchoolGameBasketball, "product_evolution-high-school-game-basketball.jpg"); + + productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory { Category = categories["Basketball"], DisplayOrder = 1 }); + + 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "All-Court Basketball", + IsEsd = false, + ShortDescription = "A durable Basketball for all surfaces", + FullDescription = "

All-Court Prep Ball

A durable basketball for all surfaces.

Whether on parquet or on asphalt - the adidas All-Court Prep Ball hat has only one goal: the basket. This basketball is made of durable artificial leather, was also predestined for indoor games also for outdoor games.

  • Composite cover made of artificial leather
  • suitable for indoors and outdoors
  • Delivered unpumped
", + Sku = "P-4002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "All-Court Basketball", + Price = 25.90M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productAllCourtBasketball, "product_all-court-basketball.png"); + + productAllCourtBasketball.ProductCategories.Add(new ProductCategory { Category = categories["Basketball"], DisplayOrder = 1 }); + + productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); + + #endregion All Court Basketball + + #endregion category Basketball + + #region category sunglasses + + #region product Top bar + + var productRayBanTopBar = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Ray-Ban Top Bar RB 3183", + IsEsd = false, + ShortDescription = "The Ray-Ban Original Wayfarer is the most famous style in the history of sunglasses. With the original design from 1952 the Wayfarer is popular with celebrities, musicians, artists and fashion experts.", + FullDescription = "

The Ray-Ban ® RB3183 sunglasses give me their aerodynamic shape a reminiscence of speed.

A rectangular shape and the classic Ray-Ban logo imprinted on the straps characterize this light Halbrand model.

", + Sku = "P-3004", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Ray-Ban Top Bar RB 3183", + Price = 139M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + 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.ProductCategories.Add(new ProductCategory { Category = categories["Sunglasses"], 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "ORIGINAL WAYFARER AT COLLECTION", + IsEsd = false, + ShortDescription = "The Ray-Ban Original Wayfarer is the most famous style in the history of sunglasses. With the original design from 1952 the Wayfarer is popular with celebrities, musicians, artists and fashion experts.", + FullDescription = "

Radar® EV Path™ PRIZM™ Road

A new milestone in the heritage of performance, Radar® EV takes breakthroughs of a revolutionary design even further with a taller lens that extends the upper field of view. From the comfort and protection of the O Matter® frame to the grip of its Unobtanium® components, this premium design builds on the legacy of Radar innovation and style.

Features

  • PRIZM™ is a revolutionary lens technology that fine-tunes vision for specific sports and environments. See what you’ve been missing. Click here to learn more about Prizm Lens Technology.
  • Path lenses enhance performance if traditional lenses touch your cheeks and help extend the upper field of view
  • Engineered for maximized airflow for optimal ventilation to keep you cool
  • Unobtanium® earsocks and nosepads keep glasses in place, increasing grip despite perspiration
  • Interchangeable Lenses let you change lenses in seconds to optimize vision in any sport environment
", + Sku = "P-3003", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "ORIGINAL WAYFARER AT COLLECTION", + Price = 149M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + 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.ProductCategories.Add(new ProductCategory { Category = categories["Sunglasses"], 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Radar EV Prizm Sports Sunglasses", + IsEsd = false, + ShortDescription = "", + FullDescription = "

Radar® EV Path™ PRIZM™ Road

A new milestone in the heritage of performance, Radar® EV takes breakthroughs of a revolutionary design even further with a taller lens that extends the upper field of view. From the comfort and protection of the O Matter® frame to the grip of its Unobtanium® components, this premium design builds on the legacy of Radar innovation and style.

Features

  • PRIZM™ is a revolutionary lens technology that fine-tunes vision for specific sports and environments. See what you’ve been missing. Click here to learn more about Prizm Lens Technology.
  • Path lenses enhance performance if traditional lenses touch your cheeks and help extend the upper field of view
  • Engineered for maximized airflow for optimal ventilation to keep you cool
  • Unobtanium® earsocks and nosepads keep glasses in place, increasing grip despite perspiration
  • Interchangeable Lenses let you change lenses in seconds to optimize vision in any sport environment
", + Sku = "P-3001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Radar EV Prizm Sports Sunglasses", + Price = 149M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productRadarEVPrizmSportsSunglasses, "product_radar_ev_prizm.jpg"); + + productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory { Category = categories["Sunglasses"], 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 + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Custom Flak Sunglasses", + IsEsd = false, + ShortDescription = "", + FullDescription = "

Radar® EV Path™ PRIZM™ Road

A new milestone in the heritage of performance, Radar® EV takes breakthroughs of a revolutionary design even further with a taller lens that extends the upper field of view. From the comfort and protection of the O Matter® frame to the grip of its Unobtanium® components, this premium design builds on the legacy of Radar innovation and style.

Features

  • PRIZM™ is a revolutionary lens technology that fine-tunes vision for specific sports and environments. See what you’ve been missing. Click here to learn more about Prizm Lens Technology.
  • Path lenses enhance performance if traditional lenses touch your cheeks and help extend the upper field of view
  • Engineered for maximized airflow for optimal ventilation to keep you cool
  • Unobtanium® earsocks and nosepads keep glasses in place, increasing grip despite perspiration
  • Interchangeable Lenses let you change lenses in seconds to optimize vision in any sport environment
", + Sku = "P-3002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Custom Flak Sunglasses", + Price = 179M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + 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 }); + + #endregion product Custom Flak Sunglasses + + + #endregion category sunglasses + + #region category apple + + #region product iphone plus + + var productIphoneplus = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "iPhone Plus", + IsEsd = false, + ShortDescription = "iPhone 7 dramatically improves the most important aspects of the iPhone experience. It introduces advanced new camera systems. The best performance and battery life ever in an iPhone. Immersive stereo speakers. The brightest, most colorful iPhone display. Splash and water resistance.1 And it looks every bit as powerful as it is. This is iPhone 7.", + FullDescription = "", + Sku = "P-2001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "iPhone Plus", + Price = 878M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 9, + StockQuantity = 10000, + DisplayStockAvailability = true, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + IsFreeShipping = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productIphoneplus, "product_iphone-plus_all_colors.jpg", "iphone-plus-all-colors"); + AddProductPicture(productIphoneplus, "product_iphoneplus_1.jpg", "iphone-plus-default"); + AddProductPicture(productIphoneplus, "product_iphone-plus_red.jpg", "iphone-plus-red"); + AddProductPicture(productIphoneplus, "product_iphone-plus_silver.jpg", "iphone-plus-silver"); + AddProductPicture(productIphoneplus, "product_iphone-plus_black.jpg", "iphone-plus-black"); + AddProductPicture(productIphoneplus, "product_iphone-plus_rose.jpg", "iphone-plus-rose"); + AddProductPicture(productIphoneplus, "product_iphone-plus_gold.jpg", "iphone-plus-gold"); + + productIphoneplus.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); + + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // offer type -> Permanent low price + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // storage capacity -> 64gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // storage capacity -> 128gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = specAttributes[5].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + + #endregion product iphone plus + + #region product Watch Series 2 + + var productWatchSeries2 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = false, + Name = "Watch Series 2", + IsEsd = false, + ShortDescription = "Live a better day. Built-in GPS. Water resistance to 50 meters.1 A lightning-fast dual‑core processor. And a display that’s two times brighter than before. Full of features that help you stay active, motivated, and connected, Apple Watch Series 2 is the perfect partner for a healthy life.", + FullDescription = "", + Sku = "P-2002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Watch Series 2", + Price = 299M, + OldPrice = 399M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productWatchSeries2, "product_watchseries2_1.jpg", "watchseries-1"); + AddProductPicture(productWatchSeries2, "product_watchseries2_2.jpg", "watchseries-2"); + + productWatchSeries2.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); + + productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 1 }); + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // offer type -> offer of the day + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) + }); + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // storage capacity -> 32gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = specAttributes[5].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + + #endregion product Watch Series 2 + + #region product Airpods + + var productAirpods = 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", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "AirPods", + Price = 999M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productAirpods, "products_airpods_white.jpg", "airpods-white"); + AddProductPicture(productAirpods, "products_airpods_turquoise.jpg", "airpods-turquoise"); + AddProductPicture(productAirpods, "products_airpods_lightblue.jpg", "airpods-lightblue"); + AddProductPicture(productAirpods, "products_airpods_rose.jpg", "airpods-rose"); + AddProductPicture(productAirpods, "products_airpods_gold.jpg", "airpods-gold"); + AddProductPicture(productAirpods, "products_airpods_mint.jpg", "airpods-mint"); + + productAirpods.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); + + productAirpods.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 7 }); + + #endregion product Airpods + + #region product Ultimate Apple Pro Hipster Bundle + + var productAppleProHipsterBundle = new Product + { + 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 + }; + + AddProductPicture(productAppleProHipsterBundle, "product_ultimate-apple-pro-hipster-bundle.jpg", "apple-pro-hipster-bundle"); + AddProductPicture(productAppleProHipsterBundle, "products_airpods_white.jpg", "bundle-airpods-white"); + AddProductPicture(productAppleProHipsterBundle, "product_watchseries2_2.jpg", "bundle-watchseries"); + AddProductPicture(productAppleProHipsterBundle, "product_iphoneplus_2.jpg", "bundle-iphoneplus"); + AddProductPicture(productAppleProHipsterBundle, "category_apple.png", "bundle-apple"); + + productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); + + productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 1 }); + + #endregion product Ultimate Apple Pro Hipster Bundle + + #region product 9,7 iPad + + var product97ipad = 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", + 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, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(product97ipad, "product_ipad_1.jpg", "ipad-1"); + AddProductPicture(product97ipad, "product_ipad_2.jpg", "ipad-2"); + AddProductPicture(product97ipad, "product_97-ipad-yellow.jpg", "ipad-yellow"); + AddProductPicture(product97ipad, "product_97-ipad-turquoise.jpg", "ipad-turquoise"); + AddProductPicture(product97ipad, "product_97-ipad-lightblue.jpg", "ipad-lightblue"); + AddProductPicture(product97ipad, "product_97-ipad-purple.jpg", "ipad-purple"); + AddProductPicture(product97ipad, "product_97-ipad-mint.jpg", "ipad-mint"); + AddProductPicture(product97ipad, "product_97-ipad-rose.jpg", "ipad-rose"); + AddProductPicture(product97ipad, "product_97-ipad-spacegray.jpg", "ipad-spacegray"); + AddProductPicture(product97ipad, "product_97-ipad-gold.jpg", "ipad-gold"); + AddProductPicture(product97ipad, "product_97-ipad-silver.jpg", "ipad-silver"); + + product97ipad.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); + + product97ipad.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 1 }); + + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // offer type -> promotion + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // storage capacity -> 64gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // storage capacity -> 128gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = specAttributes[5].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + + #endregion product 9,7 iPad + + #endregion category apple + + #region category 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 + }; + + AddProductPicture(product10GiftCard, "product_gift_card_10.png"); + product10GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], 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 + }; + + AddProductPicture(product25GiftCard, "product_gift_card_25.png"); + product25GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], DisplayOrder = 1 }); + + #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 + }; + + AddProductPicture(product50GiftCard, "product_gift_card_50.png"); + product50GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], DisplayOrder = 1 }); + + #endregion product50GiftCard + + #region product100GiftCard + + var product100GiftCard = 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", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + 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, + DisplayOrder = 4, + }; + + AddProductPicture(product100GiftCard, "product_gift_card_100.png"); + product100GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], DisplayOrder = 1 }); + + #endregion product100GiftCard + + #endregion category Gift Cards + + #region category 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 + }; + + AddProductPicture(productBooksUberMan, "0000932_uberman-der-roman.jpeg"); + productBooksUberMan.ProductCategories.Add(new ProductCategory { Category = categories["SPIEGEL-Bestseller"], DisplayOrder = 1 }); + + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 7) + }); + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + + #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, + 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 = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> bound + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 7) + }); + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + + #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, + DeliveryTimeId = firstDeliveryTime.Id + }; + + AddProductPicture(productBooksBestGrillingRecipes, "product_bestgrillingrecipes.jpg"); + productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory { Category = categories["Cook and enjoy"], DisplayOrder = 1 }); + + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + 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 + + #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, + 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, + DeliveryTimeId = secondDeliveryTime.Id + }; + + AddProductPicture(productBooksCookingForTwo, "product_cookingfortwo.jpg"); + productBooksCookingForTwo.ProductCategories.Add(new ProductCategory { Category = categories["Cook and enjoy"], DisplayOrder = 1 }); + + 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 + + #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.

", + 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, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + 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 }); + + 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 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, + DeliveryTimeId = firstDeliveryTime.Id + }; + + AddProductPicture(productBooksBildatlasMotorraeder, "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"); + productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); + + 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 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, + DeliveryTimeId = firstDeliveryTime.Id + }; + + 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 }); + + 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) + }); + + #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, + 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 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, + DeliveryTimeId = secondDeliveryTime.Id + }; + + AddProductPicture(productBooksMotorradAbenteuer, "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"); + productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); + + 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 productBooksMotorradAbenteuer + + #endregion category books + + #region Instant Download Music / Digital Products + + #region product Books Stone of the Wise + + var productBooksStoneOfTheWise = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Ebook 'Stone of the Wise' in 'Lorem ipsum'", + IsEsd = true, + ShortDescription = "E-Book, 465 pages", + FullDescription = "", + Sku = "P-6001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Ebook 'Stone of the Wise' in 'Lorem ipsum'", + Price = 9.90M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsDownload = true, + HasSampleDownload = true + }; + + AddProductPicture(productBooksStoneOfTheWise, "stone_of_wisdom.jpg"); + productBooksStoneOfTheWise.ProductCategories.Add(new ProductCategory { Category = categories["Digital Products"], DisplayOrder = 1 }); + + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cars + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) + }); + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + + #endregion product Books Stone of the Wise + + + #region Antonio Vivaldi: then spring + + var productInstantDownloadVivaldi = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Antonio Vivaldi: spring", + IsEsd = true, + ShortDescription = "MP3, 320 kbit/s", + FullDescription = "

Antonio Vivaldi: Spring

Antonio Lucio Vivaldi (March 4, 1678 in Venice, † 28 July 1741 in Vienna) was a Venetian composer and violinist in the Baroque.

The Four Seasons (Le quattro stagioni Italian) is perhaps the most famous works of Antonio Vivaldi. It's four violin concertos with extra-musical programs, each portraying a concert season. This is the individual concerts one - probably written by Vivaldi himself - Sonnet preceded by consecutive letters in front of the lines and in the appropriate places in the score arrange the verbal description of the music.

Vivaldi had previously always been experimenting with non-musical programs, which often reflected in his tracks, the exact interpretation of the individual points score is unusual for him. His experience as a virtuoso violinist allowed him access to particularly effective playing techniques, as an opera composer, he had developed a strong sense of effects, both of which benefitted from him.

As the title suggests, especially to imitate natural phenomena - gentle winds, severe storms and thunderstorms are elements that are common to all four concerts. There are also various birds and even a dog, further human activities such as hunting, a barn dance, ice skating, including stumbling and falling to the heavy sleep of a drunkard.

The work dates from 1725 and is available in two print editions, which appeared more or less simultaneously published in Amsterdam and Paris.

", + Sku = "P-1016", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Antonio Vivaldi: spring", + Price = 1.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsDownload = true, + HasSampleDownload = true + }; + + AddProductPicture(productInstantDownloadVivaldi, "vivaldi.jpg"); + productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory { Category = categories["Digital Products"], DisplayOrder = 1 }); + + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // mp3 quality > 320 kbit/S + SpecificationAttributeOption = specAttributes[18].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + // genre > classic + SpecificationAttributeOption = specAttributes[19].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) + }); + + #endregion Antonio Vivildi: then spring + + #region Beethoven für Elise + + var productInstantDownloadBeethoven = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Ludwig van Beethoven: For Elise", + IsEsd = true, + ShortDescription = "Ludwig van Beethoven's most popular compositions", + FullDescription = "

The score was not published until 1867, 40 years after the composer's death in 1827. The discoverer of the piece, Ludwig Nohl, affirmed that the original autographed manuscript, now lost, was dated 27 April 1810.[4] The version of \"Für Elise\" we hear today is an earlier version that was transcribed by Ludwig Nohl. There is a later version, with drastic changes to the accompaniment which was transcribed from a later manuscript by Barry Cooper. The most notable difference is in the first theme, the left-hand arpeggios are delayed by a 16th note beat. There are a few extra bars in the transitional section into the B section; and finally, the rising A minor arpeggio figure is moved later into the piece. The tempo marking Poco Moto is believed to have been on the manuscript that Ludwig Nohl transcribed (now lost). The later version includes the marking Molto Grazioso. It is believed that Beethoven intended to add the piece to a cycle of bagatelles.[citation needed]

Therese Malfatti, widely believed to be the dedicatee of \"Für Elise\" The pianist and musicologist Luca Chiantore (es) argued in his thesis and his 2010 book Beethoven al piano that Beethoven might not have been the person who gave the piece the form that we know today. Chiantore suggested that the original signed manuscript, upon which Ludwig Nohl claimed to base his transcription, may never have existed.[5] On the other hand, the musicologist Barry Cooper stated, in a 1984 essay in The Musical Times, that one of two surviving sketches closely resembles the published version.[6]

", + Sku = "P-1017", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Ludwig van Beethoven: Für Elise", + ShowOnHomePage = true, + Price = 1.89M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsDownload = true, + HasSampleDownload = true + }; + + AddProductPicture(productInstantDownloadBeethoven, "Beethoven.jpg"); + productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory { Category = categories["Digital Products"], DisplayOrder = 1 }); + + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // mp3 quality > 320 kbit/S + SpecificationAttributeOption = specAttributes[18].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + // genre > classic + SpecificationAttributeOption = specAttributes[19].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) + }); + + #endregion Beethoven für Elise + + #endregion Instant Download Music + + #region watches + + #region productTRANSOCEANCHRONOGRAPH + + var productTRANSOCEANCHRONOGRAPH = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "TRANSOCEAN CHRONOGRAPH", + ShortDescription = "The Transocean Chronograph interprets the factual aesthetics of classic chronographs of the 1950s and 1960s in a decidedly contemporary style.", + FullDescription = "

The Transocean Chronograph interprets the factual aesthetics of classic chronographs of the 1950s and 1960s in a decidedly contemporary style. The high-performance caliber 01, designed and manufactured entirely in the Breitling studios, works in its form, which is reduced to the essentials.

Caliber Breitling 01 (Manufactory caliber)
Movement Mechanically, Automatic
Power reserve Min. 70 hour
Chronograph 1/4-Seconds, 30 Minutes, 12 Hours
Half vibrations 28 800 a/h
Rubies 47 Rubies
Calendar Window
", + Sku = "P-9001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "TRANSOCEAN CHRONOGRAPH", + ShowOnHomePage = true, + Price = 24110.00M, + OldPrice = 26230.00M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productTRANSOCEANCHRONOGRAPH, "product_transocean-chronograph.jpg"); + productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); + + productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Breitling"], DisplayOrder = 1 }); + + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // offer > promotion + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + // manufacturer > Breitling + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 18) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // housing > steel + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // material -> leather + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // Gender -> gentlemen + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // movement -> mechanical, self winding + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // diameter -> 44mm + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // closure -> folding clasp + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + + #endregion productTRANSOCEANCHRONOGRAPH + + #region productTissotT-TouchExpertSolar + + var productTissotTTouchExpertSolar = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Tissot T-Touch Expert Solar", + ShortDescription = "The beam of the Tissot T-Touch Expert Solar on the dial ensures that the Super-LumiNova®-coated indexes and hands illuminate in the dark, and on the other hand, charges the battery of the watch. This model is a force package in every respect.", + FullDescription = "

The T-Touch Expert Solar is an important new model in the Tissot range.

Tissot’s pioneering spirit is what led to the creation of tactile watches in 1999.

Today, it is the first to present a touch-screen watch powered by solar energy, confirming its position as leader in tactile technology in watchmaking.

Extremely well designed, it showcases clean lines in both sports and timeless pieces.

Powered by solar energy with 25 features including weather forecasting, altimeter, second time zone and a compass it is the perfect travel companion.

", + Sku = "P-9002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + 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, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar.jpg", "tissot-1"); + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar-t091_2.jpg", "tissot-2"); + + productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); + + productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Tissot"], DisplayOrder = 1 }); + + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // offer > best price + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 8) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + // manufacturer > Tissot + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 17) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // housing > steel + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // material -> silicone + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 7) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // Gender -> gentlemen + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // movement -> Automatic, self-winding + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // diameter -> 44mm + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // closure -> thorn close + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + + #endregion productTissotT-TouchExpertSolar + + #region productSeikoSRPA49K1 + + var productSeikoSRPA49K1 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Seiko Mechanical Automatic SRPA49K1", + ShortDescription = "Seiko Mechanical Automatic SRPA49K1", + FullDescription = "

Seiko 5 Sports Automatic Watch SRPA49K1 SRPA49

  • Unidirectional Rotating Bezel
  • Day And Date Display
  • See Through Case Back
  • 100M Water Resistance
  • Stainless Steel Case
  • Automatic Movement
  • 24 Jewels
  • Caliber: 4R36
", + Sku = "P-9003", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Seiko Mechanical Automatic SRPA49K1", + ShowOnHomePage = true, + Price = 269.00M, + OldPrice = 329.00M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productSeikoSRPA49K1, "product_SeikoSRPA49K1.jpg"); + productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); + + productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Seiko"], DisplayOrder = 1 }); + + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 1, + // housing > steel + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // material -> stainless steel + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + // manufacturer > Seiko + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 16) + }); + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // Gender -> gentlemen + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // movement -> quarz + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // closure -> folding clasp + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // diameter -> 44mm + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + + #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).

", + 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, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productWatchesCertinaDSPodiumBigSize, "product_certina_ds_podium_big.png"); + productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); + + productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Certina"], DisplayOrder = 1 }); + + 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 = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); + 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 = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // closure -> folding clasp + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // diameter -> 40mm + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); + + #endregion productWatchesCertinaDSPodiumBigSize + + #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 = _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, + //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, + DeliveryTimeId = firstDeliveryTime.Id + }; + + 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, + OldPrice = 59.90M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; + + 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, + //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", + MetaTitle = "Minecraft - Playstation 4 Edition", + + Price = 49.90M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; + + 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, + MetaTitle = "PlayStation 4 Minecraft Bundle", + Price = 269.97M, + 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 + }; + + 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 + + #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, + DeliveryTimeId = firstDeliveryTime.Id + }; + + AddProductPicture(productPs4, "product_sony_ps4.png", "sony-ps4"); + AddProductPicture(productPs4, "product_sony_dualshock4_wirelesscontroller.png", "sony-ps4-dualshock"); - productPs4Camera.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_camera.png"), "image/png", GetSeName(productPs4Camera.Name)), - DisplayOrder = 1 - }); + productPs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productPs4.ProductCategories.Add(new ProductCategory { Category = categories["Gaming"], DisplayOrder = 5 }); - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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" - }; + 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 + }; - productBundlePs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productBundlePs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 2 }); + 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 }); - productBundlePs4.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_bundle.png"), "image/png", GetSeName(productBundlePs4.Name)), - DisplayOrder = 1 - }); - #endregion bundlePs4 + 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" + }; - #region groupAccessories + 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 }); - 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 = productTemplateGrouped.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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 - }; + #endregion bundlePs4 - productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productGroupAccessories.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 3 }); + #region groupAccessories - productGroupAccessories.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName(productGroupAccessories.Name)), - DisplayOrder = 1 - }); + 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 + }; - #endregion groupAccessories + 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 }); - #region Ps3PlusOneGame + #endregion groupAccessories - 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, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Watch Dogs", - Price = 49.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + #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, + DeliveryTimeId = firstDeliveryTime.Id + }; - productWatchDogs.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - productWatchDogs.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 1 }); + 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 }); - productWatchDogs.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-watchdogs.jpg"), "image/jpeg", GetSeName(productWatchDogs.Name)), - DisplayOrder = 1 - }); + #endregion Ps3PlusOneGame + #region Horizon Zero Down - 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 = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - 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 productHorizonZeroDown = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + //Sku = "Ubi-princepersia", + Sku = "PD-ZeroDown4PS4", + Name = "Horizon Zero Dawn - PlayStation 4", + ShortDescription = "Experience A Vibrant, Lush World Inhabited By Mysterious Mechanized Creatures", + FullDescription = "
  • A Lush Post-Apocalyptic World – How have machines dominated this world, and what is their purpose? What happened to the civilization here before? Scour every corner of a realm filled with ancient relics and mysterious buildings in order to uncover your past and unearth the many secrets of a forgotten land.
  • Nature and Machines Collide – Horizon Zero Dawn juxtaposes two contrasting elements, taking a vibrant world rich with beautiful nature and filling it with awe-inspiring highly advanced technology. This marriage creates a dynamic combination for both exploration and gameplay.
  • Defy Overwhelming Odds – The foundation of combat in Horizon Zero Dawn is built upon the speed and cunning of Aloy versus the raw strength and size of the machines. In order to overcome a much larger and technologically superior enemy, Aloy must use every ounce of her knowledge, intelligence, and agility to survive each encounter.
  • Cutting Edge Open World Tech – Stunningly detailed forests, imposing mountains, and atmospheric ruins of a bygone civilization meld together in a landscape that is alive with changing weather systems and a full day/night cycle.
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Horizon Zero Dawn - PlayStation 4", + Price = 69.90M, + OldPrice = 79.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(productHorizonZeroDown, "product_horizon.jpg"); + productHorizonZeroDown.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productHorizonZeroDown.ProductCategories.Add(new ProductCategory { Category = categories["Games"], DisplayOrder = 2 }); - productPrinceOfPersia.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-prince-of-persia.jpg"), "image/jpeg", GetSeName(productPrinceOfPersia.Name)), - DisplayOrder = 1 - }); + #endregion Horizon Zero Down - var productDriverSanFrancisco = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Ubi-driversanfrancisco", - Name = "Driver San Francisco", - ShortDescription = "Developed by Ubisoft Reflections, creators of the original DRIVER title, DRIVER SAN FRANCISCO is the return of the established action driving video game series that has sold 14 million copies worldwide. Players will race through the iconic streets of San Francisco and beyond in the largest open-world environment to date spanning over 200 square miles.", - FullDescription = "

With crime lord Charles Jericho now on the loose, San Francisco faces a terrible threat. Only one man can stand against him. He has driven the streets of a hundred cities and spent his whole life putting criminals behind bars. But to take Jericho down, there can be no turning back, and he knows that this may very well be his last ride. His name is John Tanner. He is the DRIVER.

An innovative gameplay feature enables players to seamlessly SHIFT between over 130 licensed muscle and super cars to keep them constantly in the heart of the action. With its timeless atmosphere, unique car handling and renewed playability, DRIVER SAN FRANCISCO revitalizes the classic free-roaming, cinematic car chase experience for the current generation of video game platforms.

THE TRUE CAR CHASE EXPERIENCE

Rediscover the cinematic driving sensations of DRIVER: loose suspension, long drifts, Hollywood-style crashes and high-speed pursuits in dense traffic. Drive over 130 fully destructible muscle and super cars with realistic handling and customization features that take fast-action driving to the next level.

RELENTLESS MANHUNT

Uncover a thrilling character-driven storyline in which personal revenge fuels Tanner’s relentless manhunt for Jericho. Follow Tanner's survival race across San Francisco and beyond to discover how this chase will bring him to a point of no return.

SHIFT

As Tanner recovers from a terrible crash, he realizes he has acquired a new ability, SHIFT, enabling him to instantly change vehicles and take control. Experience unprecedented intensity, diversity and freedom; SHIFT into a faster car, deploy civilian vehicles to destroy your enemies and even take control of your opponents’ car to force their demise. SHIFT also allows for thrilling new Multi-player modes within the game.

A CAR CHASE PLAYGROUND

Drive on more than 200 square miles of road network, over the Golden Gate Bridge, and through iconic locations of San Francisco. SHIFT from one car to the next and dip into the lives of different residents, a head-spinning array of characters, each with a unique perspective on a city under siege.

MULTIPLAYER MAYHEM

Experience 10 different addictive multi-player modes, including 6 on-line modes where the SHIFT feature allows players to be anywhere at any time. Ram, tail and overtake your friends in offline split-screen or online modes.

…AND MORE

Record your best stunts and chases with the Director replay mode to edit and share your movies. Test your driving skills with 20 challenging races and 80 “dares” spread all across the city. Listen to over 60 music tracks with songs from famous artists, not to mention the original memorable DRIVER theme.

", - ProductTemplateId = productTemplateSimple.Id, - AllowCustomerReviews = true, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "Driver San Francisco", - Price = 39.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + #region Fifa 17 - productDriverSanFrancisco.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - productDriverSanFrancisco.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 3 }); + var productFifa17 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + //Sku = "Ubi-princepersia", + Sku = "PD-Fifa17", + Name = "FIFA 17 - PlayStation 4", + ShortDescription = "Powered by Frostbite", + FullDescription = "
  • Powered by Frostbite: One of the industry’s leading game engines, Frostbite delivers authentic, true-to-life action, takes players to new football worlds, and introduces fans to characters full of depth and emotion in FIFA 17.
  • The Journey: For the first time ever in FIFA, live your story on and off the pitch as the Premier League’s next rising star, Alex Hunter. Play on any club in the premier league, for authentic managers and alongside some of the best players on the planet. Experience brand new worlds in FIFA 17, all while navigating your way through the emotional highs and lows of The Journey.
  • Own Every Moment: Complete innovation in the way players think and move, physically interact with opponents, and execute in attack puts you in complete control of every moment on the pitch.
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "FIFA 17 - PlayStation 4", + Price = 79.90M, + OldPrice = 89.90M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; - productDriverSanFrancisco.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-driver-san-francisco.jpg"), "image/jpeg", GetSeName(productDriverSanFrancisco.Name)), - DisplayOrder = 1 - }); + 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 }); - var productPs3OneGame = new Product() + #endregion Fifa 17 + + #region Lego Worlds + + var productLegoWorlds = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - Sku = "Sony-PS310111", - Name = "PlayStation 3 plus game cheaper", - ShortDescription = "Our special offer: PlayStation 3 plus one game of your choise cheaper.", - FullDescription = productPs3.FullDescription, - ProductTemplateId = productTemplateSimple.Id, + Sku = "Gaming-Lego-001", + Name = "LEGO Worlds - PlayStation 4", + ShortDescription = "Experience a galaxy of Worlds made entirely from LEGO bricks.", + FullDescription = "
  • Experience a galaxy of Worlds made entirely from LEGO bricks.
  • LEGO Worlds is an open environment of procedurally-generated Worlds made entirely of LEGO bricks which you can freely manipulate and dynamically populate with LEGO models.
  • Create anything you can imagine one brick at a time, or use large-scale landscaping tools to create vast mountain ranges and dot your world with tropical islands.
  • Explore using helicopters, dragons, motorbikes or even gorillas and unlock treasures that enhance your gameplay.
  • Watch your creations come to life through characters and creatures that interact with you and each other in unexpected ways.

", + ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - MetaTitle = "PlayStation 3 plus game cheaper", - Price = 160.00M, + MetaTitle = "LEGO Worlds - PlayStation 4", + Price = 29.90M, + OldPrice = 34.90M, ManageInventoryMethod = ManageInventoryMethod.DontManageStock, OrderMinimumQuantity = 1, - OrderMaximumQuantity = 3, + OrderMaximumQuantity = 10000, StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; - - productPs3OneGame.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs3OneGame.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 6 }); - - productPs3OneGame.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps3_plus_game.png"), "image/png", GetSeName(productPs3OneGame.Name)), - DisplayOrder = 1 - }); - - #endregion Ps3PlusOneGame - - #endregion gaming + DeliveryTimeId = firstDeliveryTime.Id + }; - var entities = new List - { - product5GiftCard, product25GiftCard, product50GiftCard, productBooksUberMan, productBooksGefangeneDesHimmels, - productBooksBestGrillingRecipes, productBooksCookingForTwo, productBooksAutosDerSuperlative, productBooksBildatlasMotorraeder, productBooksAutoBuch, productBooksFastCars, - productBooksMotorradAbenteuer, productComputerDellInspiron23, productComputerDellOptiplex3010,productSmartPhonesAppleIphone, - productInstantDownloadVivaldi, productComputerAcerAspireOne, productInstantDownloadBeethoven, productWatchesCertinaDSPodiumBigSize, - productPs3, productDualshock3Controller, productAssassinsCreed3, productBundlePs3AssassinCreed, + 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 }); + + #endregion Lego Worlds + + //#region XBox One S + + //var productXBoxOneS = new Product + //{ + // ProductType = ProductType.SimpleProduct, + // VisibleIndividually = true, + // 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 = "Xbox One S", + // Price = 279.99M, + // OldPrice = 279.99M, + // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + // OrderMinimumQuantity = 1, + // OrderMaximumQuantity = 10000, + // StockQuantity = 10000, + // NotifyAdminForQuantityBelow = 1, + // AllowBackInStockSubscriptions = false, + // IsShipEnabled = true, + // DeliveryTimeId = firstDeliveryTime.Id + //}; + + //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, + productIphoneplus,productWatchSeries2,product10GiftCard, product25GiftCard, product50GiftCard,product100GiftCard, productBooksUberMan, productBooksGefangeneDesHimmels, + productBooksBestGrillingRecipes, productBooksCookingForTwo, productBooksAutosDerSuperlative, productBooksBildatlasMotorraeder, + productBooksAutoBuch, productBooksFastCars, productBooksMotorradAbenteuer, + productInstantDownloadVivaldi, productInstantDownloadBeethoven, productWatchesCertinaDSPodiumBigSize, + productPs3, productMinecraft, productBundlePs3AssassinCreed, productPs4, productDualshock4Controller, productPs4Camera, productBundlePs4, - productGroupAccessories, - productWatchDogs, productPrinceOfPersia, productDriverSanFrancisco, productPs3OneGame - }; + productGroupAccessories, productPrinceOfPersia, productLegoWorlds, productHorizonZeroDown, productFifa17, productJackWolfsKinKanukaPoint + }; + + entities.AddRange(GetFashionProducts(specAttributes)); + entities.AddRange(GetFurnitureProducts(specAttributes)); this.Alter(entities); return entities; @@ -8664,105 +12971,214 @@ public IList Products() public IList ProductBundleItems() { - #region gaming + var utcNow = DateTime.UtcNow; + + #region apple bundles + var bundleAppleProHipster = _ctx.Set().First(x => x.Sku == "P-2005-Bundle"); + + var bundleItemIproductIphoneplus = new ProductBundleItem() + { + BundleProduct = bundleAppleProHipster, + Product = _ctx.Set().First(x => x.Sku == "P-2001"), + Quantity = 1, + Discount = 40.0M, + Visible = true, + Published = true, + DisplayOrder = 1 + }; + + var bundleItemProductWatchSeries2 = new ProductBundleItem() + { + BundleProduct = bundleAppleProHipster, + Product = _ctx.Set().First(x => x.Sku == "P-2002"), + Quantity = 2, + Discount = 30.0M, + Visible = true, + Published = true, + DisplayOrder = 2 + }; + + var bundleItemproductAirpods = new ProductBundleItem() + { + BundleProduct = bundleAppleProHipster, + Product = _ctx.Set().First(x => x.Sku == "P-2003"), + Quantity = 1, + Discount = 30.0M, + Visible = true, + Published = true, + DisplayOrder = 3 + }; - var utcNow = DateTime.UtcNow; - var bundlePs3AssassinCreed = _ctx.Set().First(x => x.Sku == "Sony-PS399105"); + var bundleItemproductIpad = new ProductBundleItem() + { + BundleProduct = bundleAppleProHipster, + Product = _ctx.Set().First(x => x.Sku == "P-2004"), + Quantity = 1, + Discount = 30.0M, + Visible = true, + Published = true, + DisplayOrder = 3 + }; + + #endregion apple bundles - var bundleItemPs3AssassinCreed1 = new ProductBundleItem() + #region gaming + + //var bundlePs3AssassinCreed = _ctx.Set().First(x => x.Sku == "Sony-PS399105"); + var bundlePs4Minecraft = _ctx.Set().First(x => x.Sku == "Sony-PS399105"); + + var bundleItemPs4Minecraft1 = new ProductBundleItem() { - BundleProduct = bundlePs3AssassinCreed, + BundleProduct = bundlePs4Minecraft, Product = _ctx.Set().First(x => x.Sku == "Sony-PS399000"), Quantity = 1, Discount = 20.0M, Visible = true, Published = true, - DisplayOrder = 1, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow + DisplayOrder = 1 }; - var bundleItemPs3AssassinCreed2 = new ProductBundleItem() + var bundleItemPs4Minecraft2 = new ProductBundleItem() { - BundleProduct = bundlePs3AssassinCreed, + BundleProduct = bundlePs4Minecraft, Product = _ctx.Set().First(x => x.Sku == "Sony-PS399004"), Quantity = 2, Discount = 30.0M, Visible = true, Published = true, - DisplayOrder = 2, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow + DisplayOrder = 2 }; - var bundleItemPs3AssassinCreed3 = new ProductBundleItem() + var bundleItemPs4Minecraft3 = new ProductBundleItem() { - BundleProduct = bundlePs3AssassinCreed, - Product = _ctx.Set().First(x => x.Sku == "Ubi-acreed3"), + BundleProduct = bundlePs4Minecraft, + Product = _ctx.Set().First(x => x.Sku == "PD-Minecraft4ps4"), Quantity = 1, Discount = 20.0M, Visible = true, Published = true, - DisplayOrder = 3, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow + DisplayOrder = 3 }; var bundlePs4 = _ctx.Set().First(x => x.Sku == "Sony-PS410099"); - var bundleItemPs41 = new ProductBundleItem() + var bundleItemPs41 = new ProductBundleItem { BundleProduct = bundlePs4, Product = _ctx.Set().First(x => x.Sku == "Sony-PS410034"), Quantity = 1, Visible = true, Published = true, - DisplayOrder = 1, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow + DisplayOrder = 1 }; - var bundleItemPs42 = new ProductBundleItem() + var bundleItemPs42 = new ProductBundleItem { BundleProduct = bundlePs4, - Product = _ctx.Set().First(x => x.Sku == "Sony-PS410037"), - Quantity = 1, + //Product = _ctx.Set().First(x => x.Sku == "Sony-PS410037"), + Product = _ctx.Set().First(x => x.Sku == "Sony-PS399004"), + Quantity = 1, Visible = true, Published = true, - DisplayOrder = 2, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow + DisplayOrder = 2 }; - var bundleItemPs43 = new ProductBundleItem() + var bundleItemPs43 = new ProductBundleItem { BundleProduct = bundlePs4, Product = _ctx.Set().First(x => x.Sku == "Sony-PS410040"), Quantity = 1, Visible = true, Published = true, - DisplayOrder = 3, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow + DisplayOrder = 3 }; #endregion gaming var entities = new List { - bundleItemPs3AssassinCreed1, bundleItemPs3AssassinCreed2, bundleItemPs3AssassinCreed3, - bundleItemPs41, bundleItemPs42, bundleItemPs43 - }; + bundleItemPs4Minecraft1, bundleItemPs4Minecraft2, bundleItemPs4Minecraft3, + bundleItemPs41, bundleItemPs42, bundleItemPs43,bundleItemIproductIphoneplus, bundleItemProductWatchSeries2,bundleItemproductAirpods,bundleItemproductIpad + }; this.Alter(entities); 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", "Ubi-acreed3", "Sony-PS410037", "Sony-PS410040" }; + var gamingAccessoriesSkus = new List() { "Sony-PS399004", "PD-Minecraft4ps4", "Sony-PS410037", "Sony-PS410040" }; savedProducts .Where(x => gamingAccessoriesSkus.Contains(x.Sku)) @@ -8771,23 +13187,20 @@ public void AssignGroupedProducts(IList savedProducts) { x.ParentGroupedProductId = productGamingAccessoriesId; - _ctx.Set().Attach(x); - _ctx.Entry(x).State = System.Data.Entity.EntityState.Modified; + //_ctx.Set().Attach(x); + //_ctx.Entry(x).State = System.Data.Entity.EntityState.Modified; }); - _ctx.SaveChanges(); + _ctx.SaveChanges(); } - #region ForumGroups public IList ForumGroups() { - var forumGroupGeneral = new ForumGroup() + var forumGroupGeneral = new ForumGroup { Name = "General", Description = "", - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, + DisplayOrder = 1 }; var entities = new List @@ -8798,36 +13211,32 @@ 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, NumPosts = 0, LastPostCustomerId = 0, LastPostTime = null, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, + DisplayOrder = 1 }; - var packagingShippingForum = new Forum() + var packagingShippingForum = new Forum { - ForumGroup = _ctx.Set().Where(c => c.DisplayOrder == 1).Single(), + ForumGroup = group, Name = "Packaging & Shipping", Description = "Discuss packaging & shipping", NumTopics = 0, NumPosts = 0, LastPostTime = null, - DisplayOrder = 20, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, + DisplayOrder = 20 }; @@ -8839,9 +13248,7 @@ public IList Forums() this.Alter(entities); return entities; } - #endregion Forums - #region Discounts public IList Discounts() { var sampleDiscountWithCouponCode = new Discount() @@ -8852,7 +13259,7 @@ public IList Discounts() UsePercentage = false, DiscountAmount = 10, RequiresCouponCode = true, - CouponCode = "123", + CouponCode = "123" }; var sampleDiscounTwentyPercentTotal = new Discount() { @@ -8864,7 +13271,7 @@ public IList Discounts() StartDateUtc = new DateTime(2013, 1, 1), EndDateUtc = new DateTime(2020, 1, 1), RequiresCouponCode = true, - CouponCode = "456", + CouponCode = "456" }; var entities = new List @@ -8875,9 +13282,7 @@ public IList Discounts() this.Alter(entities); return entities; } - #endregion Discounts - #region Deliverytimes public IList DeliveryTimes() { var entities = new List() @@ -8905,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(); @@ -8964,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), }; @@ -8976,9 +13384,7 @@ public IList BlogPosts() this.Alter(entities); return entities; } - #endregion BlogPost - #region NewsItems public IList NewsItems() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -8986,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 }; @@ -9013,9 +13419,7 @@ public IList NewsItems() this.Alter(entities); return entities; } - #endregion NewsItems - #region PollAnswer public IList PollAnswers() { var pollAnswer1 = new PollAnswer() @@ -9067,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 { @@ -9159,8 +13551,6 @@ public IList Polls() this.Alter(entities); return entities; } - #endregion Polls - #region Alterations @@ -9288,6 +13678,14 @@ protected virtual void Alter(IList entities) { } + protected virtual void Alter(IList entities) + { + } + + protected virtual void Alter(IList entities) + { + } + protected virtual void Alter(IList entities) { } @@ -9340,6 +13738,10 @@ protected virtual void Alter(IList entities) { } + protected virtual void Alter(UrlRecord entity) + { + } + #endregion Alterations #endregion Sample data creators @@ -9370,22 +13772,95 @@ protected string SampleDownloadsPath } } - protected Picture CreatePicture(byte[] pictureBinary, string mimeType, string seoFilename) + public virtual UrlRecord CreateUrlRecordFor(T entity) where T : BaseEntity, ISlugSupported, new() { - mimeType = mimeType.EmptyNull(); - mimeType = mimeType.Truncate(20); + 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); seoFilename = seoFilename.Truncate(100); var picture = _ctx.Set().Create(); - picture.PictureBinary = pictureBinary; picture.MimeType = mimeType; picture.SeoFilename = seoFilename; + picture.UpdatedOnUtc = DateTime.UtcNow; + + picture.MediaStorage = new MediaStorage + { + Data = pictureBinary + }; 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); } @@ -9399,16 +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.CreatedOnUtc = DateTime.UtcNow; - currency.UpdatedOnUtc = DateTime.UtcNow; + currency = new Currency + { + DisplayLocale = locale, + Name = info.CurrencyNativeName, + CurrencyCode = info.ISOCurrencySymbol, + Rate = rate, + CustomFormatting = formatting, + Published = published, + DisplayOrder = order + }; } } catch @@ -9419,6 +13894,35 @@ protected Currency CreateCurrency(string locale, decimal rate = 1M, string forma return currency; } + protected string FormatAttributeXml(int attributeId, int valueId, bool withRootTag = true) + { + var xml = $"{valueId}"; + + if (withRootTag) + { + return string.Concat("", xml, ""); + } + + return xml; + } + protected string FormatAttributeXml(int attributeId1, int valueId1, int attributeId2, int valueId2) + { + return string.Concat( + "", + FormatAttributeXml(attributeId1, valueId1, false), + FormatAttributeXml(attributeId2, valueId2, false), + ""); + } + protected string FormatAttributeXml(int attributeId1, int valueId1, int attributeId2, int valueId2, int attributeId3, int valueId3) + { + return string.Concat( + "", + FormatAttributeXml(attributeId1, valueId1, false), + FormatAttributeXml(attributeId2, valueId2, false), + FormatAttributeXml(attributeId3, valueId3, false), + ""); + } + #endregion } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/SeedEntityExtensions.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/SeedEntityExtensions.cs index 2c1c7a3738..229dd25f9b 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/SeedEntityExtensions.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/SeedEntityExtensions.cs @@ -8,10 +8,8 @@ namespace SmartStore.Data.Setup { - public static class SeedEntityExtensions { - #region fluent extensions public static SeedEntityAlterer WithKey(this IList list, Expression> expression) where T : BaseEntity @@ -104,5 +102,4 @@ public SeedEntityAlterer Remove(TKey key) #endregion } - } diff --git a/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs b/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs index 27e18dea4b..d18414d6e9 100644 --- a/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs +++ b/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs @@ -1,56 +1,65 @@ using System; -using System.Collections.Generic; +using System.Linq; using System.Data.Entity; using System.Data.Entity.Core.Common; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.Infrastructure.DependencyResolution; -using System.Linq; -using EFCache; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; -using SmartStore.Data.Setup; using SmartStore.Data.Caching; -using SmartStore.Core.Caching; +using System.Web.Hosting; +using System.Data.Entity.Infrastructure; +using System.Data.Entity.Infrastructure.DependencyResolution; +using SmartStore.Utilities; namespace SmartStore.Data { - 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 { provider = (new EfDataProviderFactory(DataSettings.Current).LoadDataProvider()) as IEfDataProvider; } - catch { /* SmartStore is not installed yet! */ } - + catch + { + /* SmartStore is not installed yet! */ + } + if (provider != null) { base.SetDefaultConnectionFactory(provider.GetConnectionFactory()); - // prepare EntityFramework 2nd level cache - ICache cache = null; - try - { - var innerCache = EngineContext.Current.Resolve(); - cache = new EfCacheImpl(innerCache); - } - catch + if (HostingEnvironment.IsHosted && DataSettings.DatabaseIsInstalled()) { - cache = new InMemoryCache(); - } - - var transactionHandler = new CacheTransactionHandler(cache); - AddInterceptor(transactionHandler); + Loaded += (sender, args) => + { + // prepare EntityFramework 2nd level cache + IDbCache cache = null; + try + { + cache = EngineContext.Current.Resolve(); + } + catch + { + cache = new NullDbCache(); + } - Loaded += - (sender, args) => args.ReplaceService( - (s, _) => new CachingProviderServices(s, transactionHandler, - new EfCachingPolicy())); + var cacheInterceptor = new CacheTransactionInterceptor(cache); + AddInterceptor(cacheInterceptor); + args.ReplaceService((s, o) => new CachingProviderServices(s, cacheInterceptor)); + }; + } } } } - } diff --git a/src/Libraries/SmartStore.Data/SmartObjectContext.cs b/src/Libraries/SmartStore.Data/SmartObjectContext.cs index bbe46338db..586a4f6f3f 100644 --- a/src/Libraries/SmartStore.Data/SmartObjectContext.cs +++ b/src/Libraries/SmartStore.Data/SmartObjectContext.cs @@ -1,25 +1,23 @@ 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 -{ - /// - /// Object context - /// - public class SmartObjectContext : ObjectContextBase +{ + public class SmartObjectContext : ObjectContextBase { static SmartObjectContext() { + if (!DataSettings.DatabaseIsInstalled()) + return; + var initializer = new MigrateDatabaseInitializer { TablesToCheck = new[] { "Customer", "Discount", "Order", "Product", "ShoppingCartItem" } @@ -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 f30a48b476..9f7c8ce217 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -10,7 +11,7 @@ Properties SmartStore.Data SmartStore.Data - v4.5.1 + v4.6.1 512 @@ -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,26 +64,23 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - True - - - ..\..\packages\EntityFramework.Cache.1.0.0\lib\net45\EFCache.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + True - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + True - False - ..\..\packages\EntityFramework.SqlServerCompact.6.1.3\lib\net45\EntityFramework.SqlServerCompact.dll + ..\..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll + True True @@ -93,9 +94,8 @@ True ..\..\packages\Microsoft.SqlServer.Scripting.11.0.2100.61\lib\Microsoft.SqlServer.Smo.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll @@ -105,6 +105,9 @@ ..\..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll + + ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + @@ -119,15 +122,38 @@ Properties\AssemblyVersionInfo.cs - - + + + + + + + + + + + + + + + + + + + + + + + + + 201403112331027_Initial.cs @@ -408,13 +434,265 @@ 201606241315549_AddressEnhancement.cs + + + 201607111649138_FixExportDeploymentIsPublic.cs + + + + 201608110948010_MediaStorageData.cs + + + + 201609201852449_log4net.cs + + + + 201609281549565_CategoryBadges.cs + + + + 201611261017137_Search.cs + + + + 201612061904083_ListPaging.cs + + + + 201701121659419_PictureSize.cs + + + + 201701251835419_SpecificationAttributeAlias.cs + + + + 201702061557351_DefaultDeliveryTime.cs + + + + 201702081502539_QuantityEnhancements.cs + + + + 201702091426405_ProductSellingAbroad.cs + + + + 201702141336312_VariantAttributeValuePictures.cs + + + + 201702202112266_VariantAttributeAllowFiltering.cs + + + + 201703021146527_ProductAttributeOptionsSet.cs + + + + 201703161013248_FacetTemplateHint.cs + + + + 201704031946479_SpecificationAttributeAllowFiltering.cs + + + + 201705032238367_RenameColorSquaresRgb.cs + + + + 201705102339006_V3Final.cs + + + + 201705281903241_MoreIndexes.cs + + + + 201706020759565_UpdateMediaPath.cs + + + + 201707190940318_V302Resources.cs + + + + 201707281452589_TierPriceCalcMethod.cs + + + + 201708251628482_SystemTopics.cs + + + + 201709141000226_V303Resources.cs + + + + 201709251538312_UpdateTrustedShopsTask.cs + + + + 201710102038287_CurrencyRounding.cs + + + + 201710252016556_IndexOptionNames.cs + + + + 201711112331162_ProductMainPictureId.cs + + + + 201711222311112_MoveFsMedia.cs + + + + 201711291017168_SyncStringResources.cs + + + + 201712081631552_Liquid.cs + + + + 201712290151517_AddressFormat.cs + + + + 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 + + - @@ -535,6 +813,8 @@ + + @@ -759,6 +1039,195 @@ 201606241315549_AddressEnhancement.cs + + 201607111649138_FixExportDeploymentIsPublic.cs + + + 201608110948010_MediaStorageData.cs + + + 201609201852449_log4net.cs + + + 201609281549565_CategoryBadges.cs + + + 201611261017137_Search.cs + + + 201612061904083_ListPaging.cs + + + 201701121659419_PictureSize.cs + + + 201701251835419_SpecificationAttributeAlias.cs + + + 201702061557351_DefaultDeliveryTime.cs + + + 201702081502539_QuantityEnhancements.cs + + + 201702091426405_ProductSellingAbroad.cs + + + 201702141336312_VariantAttributeValuePictures.cs + + + 201702202112266_VariantAttributeAllowFiltering.cs + + + 201703021146527_ProductAttributeOptionsSet.cs + + + 201703161013248_FacetTemplateHint.cs + + + 201704031946479_SpecificationAttributeAllowFiltering.cs + + + 201705032238367_RenameColorSquaresRgb.cs + + + 201705102339006_V3Final.cs + + + 201705281903241_MoreIndexes.cs + + + 201706020759565_UpdateMediaPath.cs + + + 201707190940318_V302Resources.cs + + + 201707281452589_TierPriceCalcMethod.cs + + + 201708251628482_SystemTopics.cs + + + 201709141000226_V303Resources.cs + + + 201709251538312_UpdateTrustedShopsTask.cs + + + 201710102038287_CurrencyRounding.cs + + + 201710252016556_IndexOptionNames.cs + + + 201711112331162_ProductMainPictureId.cs + + + 201711222311112_MoveFsMedia.cs + + + 201711291017168_SyncStringResources.cs + + + 201712081631552_Liquid.cs + + + 201712290151517_AddressFormat.cs + + + 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 + @@ -775,9 +1244,8 @@ - + - diff --git a/src/Libraries/SmartStore.Data/Sql/Indexes.Inverse.sql b/src/Libraries/SmartStore.Data/Sql/Indexes.Inverse.sql index c3a5465148..2d00394576 100644 --- a/src/Libraries/SmartStore.Data/Sql/Indexes.Inverse.sql +++ b/src/Libraries/SmartStore.Data/Sql/Indexes.Inverse.sql @@ -151,9 +151,6 @@ GO DROP INDEX [IX_Product_LimitedToStores] ON [Product] GO -DROP INDEX [IX_Log_ContentHash] ON [Log] -GO - DROP INDEX [IX_ProductVariantAttributeCombination_SKU] GO 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/Sql/Indexes.sql b/src/Libraries/SmartStore.Data/Sql/Indexes.sql index 014c560e85..855d1934c5 100644 --- a/src/Libraries/SmartStore.Data/Sql/Indexes.sql +++ b/src/Libraries/SmartStore.Data/Sql/Indexes.sql @@ -151,10 +151,6 @@ GO CREATE NONCLUSTERED INDEX [IX_Product_LimitedToStores] ON [Product] ([LimitedToStores] ASC) GO --- codehint: sm-add -CREATE NONCLUSTERED INDEX [IX_Log_ContentHash] ON [Log] ([ContentHash] ASC) -GO - CREATE NONCLUSTERED INDEX [IX_ProductVariantAttributeCombination_SKU] ON [ProductVariantAttributeCombination] ([SKU] ASC) GO diff --git a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql deleted file mode 100644 index 522b70dc27..0000000000 --- a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql +++ /dev/null @@ -1,688 +0,0 @@ -ALTER PROCEDURE [dbo].[ProductLoadAllPaged] -( - @CategoryIds nvarchar(MAX) = null, --a list of category IDs (comma-separated list). e.g. 1,2,3 - @ManufacturerId int = 0, - @StoreId int = 0, - @ParentGroupedProductId int = 0, - @ProductTypeId int = null, --product type identifier, null - load all products - @VisibleIndividuallyOnly bit = 0, --0 - load all products , 1 - "visible indivially" only - @ProductTagId int = 0, - @FeaturedProducts bit = null, --0 featured only , 1 not featured only, null - load all products - @PriceMin decimal(18, 4) = null, - @PriceMax decimal(18, 4) = null, - @Keywords nvarchar(4000) = null, - @SearchDescriptions bit = 0, --a value indicating whether to search by a specified "keyword" in product descriptions - @SearchSku bit = 0, --a value indicating whether to search by a specified "keyword" in product SKU - @SearchProductTags bit = 0, --a value indicating whether to search by a specified "keyword" in product tags - @UseFullTextSearch bit = 0, - @FullTextMode int = 0, --0 - using CONTAINS with , 5 - using CONTAINS and OR with , 10 - using CONTAINS and AND with - @FilteredSpecs nvarchar(MAX) = null, --filter by attributes (comma-separated list). e.g. 14,15,16 - @LanguageId int = 0, - @OrderBy int = 0, --0 - position, 5 - Name: A to Z, 6 - Name: Z to A, 10 - Price: Low to High, 11 - Price: High to Low, 15 - creation date - @AllowedCustomerRoleIds nvarchar(MAX) = null, --a list of customer role IDs (comma-separated list) for which a product should be shown (if a subjet to ACL) - @PageIndex int = 0, - @PageSize int = 2147483644, - @ShowHidden bit = 0, - @LoadFilterableSpecificationAttributeOptionIds bit = 0, --a value indicating whether we should load the specification attribute option identifiers applied to loaded products (all pages) - @WithoutCategories bit = null, - @WithoutManufacturers bit = null, - @IsPublished bit = null, - @HomePageProducts bit = null, - @IdMin int = 0, - @IdMax int = 0, - @AvailabilityMin int = null, - @AvailabilityMax int = null, - @CreatedFromUtc nvarchar(MAX) = null, - @CreatedToUtc nvarchar(MAX) = null, - @FilterableSpecificationAttributeOptionIds nvarchar(MAX) = null OUTPUT, --the specification attribute option identifiers applied to loaded products (all pages). returned as a comma separated list of identifiers - @TotalRecords int = null OUTPUT -) -AS -BEGIN - - /* Products that filtered by keywords */ - CREATE TABLE #KeywordProducts - ( - [ProductId] int NOT NULL - ) - - DECLARE - @SearchKeywords bit, - @sql nvarchar(max), - @sql_orderby nvarchar(max) - - SET NOCOUNT ON - - --filter by keywords - SET @Keywords = isnull(@Keywords, '') - SET @Keywords = rtrim(ltrim(@Keywords)) - IF ISNULL(@Keywords, '') != '' - BEGIN - SET @SearchKeywords = 1 - - IF @UseFullTextSearch = 1 - BEGIN - --remove wrong chars (' ") - SET @Keywords = REPLACE(@Keywords, '''', '') - SET @Keywords = REPLACE(@Keywords, '"', '') - - --full-text search - IF @FullTextMode = 0 - BEGIN - --0 - using CONTAINS with - SET @Keywords = ' "' + @Keywords + '*" ' - END - ELSE - BEGIN - --5 - using CONTAINS and OR with - --10 - using CONTAINS and AND with - - --clean multiple spaces - WHILE CHARINDEX(' ', @Keywords) > 0 - SET @Keywords = REPLACE(@Keywords, ' ', ' ') - - DECLARE @concat_term nvarchar(100) - IF @FullTextMode = 5 --5 - using CONTAINS and OR with - BEGIN - SET @concat_term = 'OR' - END - IF @FullTextMode = 10 --10 - using CONTAINS and AND with - BEGIN - SET @concat_term = 'AND' - END - - --now let's build search string - declare @fulltext_keywords nvarchar(4000) - set @fulltext_keywords = N'' - declare @index int - - set @index = CHARINDEX(' ', @Keywords, 0) - - -- if index = 0, then only one field was passed - IF(@index = 0) - set @fulltext_keywords = ' "' + @Keywords + '*" ' - ELSE - BEGIN - DECLARE @first BIT - SET @first = 1 - WHILE @index > 0 - BEGIN - IF (@first = 0) - SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' - ELSE - SET @first = 0 - - SET @fulltext_keywords = @fulltext_keywords + '"' + SUBSTRING(@Keywords, 1, @index - 1) + '*"' - SET @Keywords = SUBSTRING(@Keywords, @index + 1, LEN(@Keywords) - @index) - SET @index = CHARINDEX(' ', @Keywords, 0) - end - - -- add the last field - IF LEN(@fulltext_keywords) > 0 - SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' + '"' + SUBSTRING(@Keywords, 1, LEN(@Keywords)) + '*"' - END - SET @Keywords = @fulltext_keywords - END - END - ELSE - BEGIN - --usual search by PATINDEX - SET @Keywords = '%' + @Keywords + '%' - END - --PRINT @Keywords - - --product name - SET @sql = ' - INSERT INTO #KeywordProducts ([ProductId]) - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[Name], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[Name]) > 0 ' - - - --localized product name - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''Name''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - - - IF @SearchDescriptions = 1 - BEGIN - --product short description - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[ShortDescription], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[ShortDescription]) > 0 ' - - - --product full description - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[FullDescription], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[FullDescription]) > 0 ' - - - - --localized product short description - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''ShortDescription''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - - - --localized product full description - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''FullDescription''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - END - - --SKU - IF @SearchSku = 1 - BEGIN - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - LEFT OUTER JOIN ProductVariantAttributeCombination pvac with(NOLOCK) ON pvac.ProductId = p.Id - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + '(CONTAINS(pvac.[Sku], @Keywords) OR CONTAINS(p.[Sku], @Keywords)) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, pvac.[Sku]) > 0 OR PATINDEX(@Keywords, p.[Sku]) > 0 ' - END - - IF @SearchProductTags = 1 - BEGIN - --product tag - SET @sql = @sql + ' - UNION - SELECT pptm.Product_Id - FROM Product_ProductTag_Mapping pptm with(NOLOCK) INNER JOIN ProductTag pt with(NOLOCK) ON pt.Id = pptm.ProductTag_Id - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(pt.[Name], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, pt.[Name]) > 0 ' - - --localized product tag - SET @sql = @sql + ' - UNION - SELECT pptm.Product_Id - FROM LocalizedProperty lp with (NOLOCK) INNER JOIN Product_ProductTag_Mapping pptm with(NOLOCK) ON lp.EntityId = pptm.ProductTag_Id - WHERE - lp.LocaleKeyGroup = N''ProductTag'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''Name''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - END - - --PRINT (@sql) - EXEC sp_executesql @sql, N'@Keywords nvarchar(4000)', @Keywords - - END - ELSE - BEGIN - SET @SearchKeywords = 0 - END - - --filter by category IDs - SET @CategoryIds = isnull(@CategoryIds, '') - CREATE TABLE #FilteredCategoryIds - ( - CategoryId int not null - ) - INSERT INTO #FilteredCategoryIds (CategoryId) - SELECT CAST(data as int) FROM [sm_splitstring_to_table](@CategoryIds, ',') - DECLARE @CategoryIdsCount int - SET @CategoryIdsCount = (SELECT COUNT(1) FROM #FilteredCategoryIds) - - --filter by attributes - SET @FilteredSpecs = isnull(@FilteredSpecs, '') - CREATE TABLE #FilteredSpecs - ( - SpecificationAttributeOptionId int not null - ) - INSERT INTO #FilteredSpecs (SpecificationAttributeOptionId) - SELECT CAST(data as int) FROM [sm_splitstring_to_table](@FilteredSpecs, ',') - DECLARE @SpecAttributesCount int - SET @SpecAttributesCount = (SELECT COUNT(1) FROM #FilteredSpecs) - - --filter by customer role IDs (access control list) - SET @AllowedCustomerRoleIds = isnull(@AllowedCustomerRoleIds, '') - CREATE TABLE #FilteredCustomerRoleIds - ( - CustomerRoleId int not null - ) - INSERT INTO #FilteredCustomerRoleIds (CustomerRoleId) - SELECT CAST(data as int) FROM [sm_splitstring_to_table](@AllowedCustomerRoleIds, ',') - - --paging - DECLARE @PageLowerBound int - DECLARE @PageUpperBound int - DECLARE @RowsToReturn int - SET @RowsToReturn = @PageSize * (@PageIndex + 1) - SET @PageLowerBound = @PageSize * @PageIndex - SET @PageUpperBound = @PageLowerBound + @PageSize + 1 - - CREATE TABLE #DisplayOrderTmp - ( - [Id] int IDENTITY (1, 1) NOT NULL, - [ProductId] int NOT NULL - ) - - SET @sql = ' - INSERT INTO #DisplayOrderTmp ([ProductId]) - SELECT p.Id - FROM - Product p with (NOLOCK)' - - IF @CategoryIdsCount > 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_Category_Mapping pcm with (NOLOCK) - ON p.Id = pcm.ProductId' - END - - IF @ManufacturerId > 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_Manufacturer_Mapping pmm with (NOLOCK) - ON p.Id = pmm.ProductId' - END - - IF ISNULL(@ProductTagId, 0) != 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_ProductTag_Mapping pptm with (NOLOCK) - ON p.Id = pptm.Product_Id' - END - - --searching by keywords - IF @SearchKeywords = 1 - BEGIN - SET @sql = @sql + ' - JOIN #KeywordProducts kp - ON p.Id = kp.ProductId' - END - - SET @sql = @sql + ' - WHERE - p.Deleted = 0' - - --filter by category - IF @CategoryIdsCount > 0 - BEGIN - SET @sql = @sql + ' - AND pcm.CategoryId IN (SELECT CategoryId FROM #FilteredCategoryIds)' - - IF @FeaturedProducts IS NOT NULL - BEGIN - SET @sql = @sql + ' - AND pcm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) - END - END - - --filter by manufacturer - IF @ManufacturerId > 0 - BEGIN - SET @sql = @sql + ' - AND pmm.ManufacturerId = ' + CAST(@ManufacturerId AS nvarchar(max)) - - IF @FeaturedProducts IS NOT NULL - BEGIN - SET @sql = @sql + ' - AND pmm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) - END - END - - --filter by parent grouped product identifer - IF @ParentGroupedProductId > 0 - BEGIN - SET @sql = @sql + ' - AND p.ParentGroupedProductId = ' + CAST(@ParentGroupedProductId AS nvarchar(max)) - END - - --filter by product type - IF @ProductTypeId is not null - BEGIN - SET @sql = @sql + ' - AND p.ProductTypeId = ' + CAST(@ProductTypeId AS nvarchar(max)) - END - - --filter by visible individually - IF @VisibleIndividuallyOnly = 1 - BEGIN - SET @sql = @sql + ' - AND p.VisibleIndividually = 1' - END - - --filter by product tag - IF ISNULL(@ProductTagId, 0) != 0 - BEGIN - SET @sql = @sql + ' - AND pptm.ProductTag_Id = ' + CAST(@ProductTagId AS nvarchar(max)) - END - - --homepage products - IF (@HomePageProducts is not null) - BEGIN - SET @sql = @sql + ' - AND p.ShowOnHomePage = ' + CAST(@HomePageProducts AS nvarchar(max)) - END - - --is published - IF (@IsPublished is null) - BEGIN - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND p.Published = 1' - END - END - ELSE IF (@IsPublished = 1) - BEGIN - SET @sql = @sql + ' - AND p.Published = 1' - END - ELSE IF (@IsPublished = 0) - BEGIN - SET @sql = @sql + ' - AND p.Published = 0' - END - - --show hidden - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND p.Deleted = 0 - AND (getutcdate() BETWEEN ISNULL(p.AvailableStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.AvailableEndDateTimeUtc, ''1/1/2999''))' - END - - --min price - IF @PriceMin > 0 - BEGIN - SET @sql = @sql + ' - AND ( - ( - --special price (specified price and valid date range) - (p.SpecialPrice IS NOT NULL AND (getutcdate() BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.SpecialPrice >= ' + CAST(@PriceMin AS nvarchar(max)) + ') - ) - OR - ( - --regular price (price isnt specified or date range isnt valid) - (p.SpecialPrice IS NULL OR (getutcdate() NOT BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.Price >= ' + CAST(@PriceMin AS nvarchar(max)) + ') - ) - )' - END - - --max price - IF @PriceMax > 0 - BEGIN - SET @sql = @sql + ' - AND ( - ( - --special price (specified price and valid date range) - (p.SpecialPrice IS NOT NULL AND (getutcdate() BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.SpecialPrice <= ' + CAST(@PriceMax AS nvarchar(max)) + ') - ) - OR - ( - --regular price (price isnt specified or date range isnt valid) - (p.SpecialPrice IS NULL OR (getutcdate() NOT BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.Price <= ' + CAST(@PriceMax AS nvarchar(max)) + ') - ) - )' - END - - --show hidden and ACL - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND (p.SubjectToAcl = 0 OR EXISTS ( - SELECT 1 FROM #FilteredCustomerRoleIds [fcr] - WHERE - [fcr].CustomerRoleId IN ( - SELECT [acl].CustomerRoleId - FROM [AclRecord] acl with (NOLOCK) - WHERE [acl].EntityId = p.Id AND [acl].EntityName = ''Product'' - ) - ))' - END - - --show hidden and filter by store - IF @StoreId > 0 - BEGIN - SET @sql = @sql + ' - AND (p.LimitedToStores = 0 OR EXISTS ( - SELECT 1 FROM [StoreMapping] sm with (NOLOCK) - WHERE [sm].EntityId = p.Id AND [sm].EntityName = ''Product'' and [sm].StoreId=' + CAST(@StoreId AS nvarchar(max)) + ' - ))' - END - - --filter by product identifier - IF @IdMin != 0 - BEGIN - SET @sql = @sql + ' - AND p.Id >= ' + CAST(@IdMin AS nvarchar(max)) - END - - IF @IdMax != 0 - BEGIN - SET @sql = @sql + ' - AND p.Id <= ' + CAST(@IdMax AS nvarchar(max)) - END - - --filter by availability - IF @AvailabilityMin is not null - BEGIN - SET @sql = @sql + ' - AND p.StockQuantity >= ' + CAST(@AvailabilityMin AS nvarchar(max)) - END - - IF @AvailabilityMax is not null - BEGIN - SET @sql = @sql + ' - AND p.StockQuantity <= ' + CAST(@AvailabilityMax AS nvarchar(max)) - END - - --filter by creation date - IF @CreatedFromUtc is not null - BEGIN - SET @sql = @sql + ' - AND p.CreatedOnUtc >= ''' + CAST(@CreatedFromUtc AS nvarchar(max)) + '''' - END - - IF @CreatedToUtc is not null - BEGIN - SET @sql = @sql + ' - AND p.CreatedOnUtc <= ''' + CAST(@CreatedToUtc AS nvarchar(max)) + '''' - END - - --filter by specs - IF @SpecAttributesCount > 0 - BEGIN - SET @sql = @sql + ' - AND NOT EXISTS ( - SELECT 1 FROM #FilteredSpecs [fs] - WHERE - [fs].SpecificationAttributeOptionId NOT IN ( - SELECT psam.SpecificationAttributeOptionId - FROM Product_SpecificationAttribute_Mapping psam with (NOLOCK) - WHERE psam.AllowFiltering = 1 AND psam.ProductId = p.Id - ) - )' - END - - IF (@WithoutCategories is not null) - BEGIN - IF (@WithoutCategories = 1) - BEGIN - SET @sql = @sql + ' - AND NOT EXISTS (SELECT 1 FROM [Product_Category_Mapping] pcm with (NOLOCK) WHERE [pcm].[ProductId] = p.Id)' - END - ELSE IF (@WithoutCategories = 0) - BEGIN - SET @sql = @sql + ' - AND EXISTS (SELECT 1 FROM [Product_Category_Mapping] pcm with (NOLOCK) WHERE [pcm].[ProductId] = p.Id)' - END - END - - IF (@WithoutManufacturers is not null) - BEGIN - IF (@WithoutManufacturers = 1) - BEGIN - SET @sql = @sql + ' - AND NOT EXISTS (SELECT 1 FROM [Product_Manufacturer_Mapping] pmm with (NOLOCK) WHERE [pmm].[ProductId] = p.Id)' - END - ELSE IF (@WithoutManufacturers = 0) - BEGIN - SET @sql = @sql + ' - AND EXISTS (SELECT 1 FROM [Product_Manufacturer_Mapping] pmm with (NOLOCK) WHERE [pmm].[ProductId] = p.Id)' - END - END - - --sorting - SET @sql_orderby = '' - IF @OrderBy = 5 /* Name: A to Z */ - SET @sql_orderby = ' p.[Name] ASC' - ELSE IF @OrderBy = 6 /* Name: Z to A */ - SET @sql_orderby = ' p.[Name] DESC' - ELSE IF @OrderBy = 10 /* Price: Low to High */ - SET @sql_orderby = ' p.[Price] ASC' - ELSE IF @OrderBy = 11 /* Price: High to Low */ - SET @sql_orderby = ' p.[Price] DESC' - ELSE IF @OrderBy = 15 /* creation date */ - SET @sql_orderby = ' p.[CreatedOnUtc] DESC' - ELSE /* default sorting, 0 (position) */ - BEGIN - --category position (display order) - IF @CategoryIdsCount > 0 SET @sql_orderby = ' pcm.DisplayOrder ASC' - - --manufacturer position (display order) - IF @ManufacturerId > 0 - BEGIN - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' pmm.DisplayOrder ASC' - END - - --parent grouped product specified (sort associated products) - IF @ParentGroupedProductId > 0 - BEGIN - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' p.[DisplayOrder] ASC' - END - - --name - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' p.[Name] ASC' - END - - SET @sql = @sql + ' - ORDER BY' + @sql_orderby - - --PRINT (@sql) - EXEC sp_executesql @sql - - DROP TABLE #FilteredCategoryIds - DROP TABLE #FilteredSpecs - DROP TABLE #FilteredCustomerRoleIds - DROP TABLE #KeywordProducts - - CREATE TABLE #PageIndex - ( - [IndexId] int IDENTITY (1, 1) NOT NULL, - [ProductId] int NOT NULL - ) - INSERT INTO #PageIndex ([ProductId]) - SELECT ProductId - FROM #DisplayOrderTmp - GROUP BY ProductId - ORDER BY min([Id]) - - --total records - SET @TotalRecords = @@rowcount - - DROP TABLE #DisplayOrderTmp - - --prepare filterable specification attribute option identifier (if requested) - IF @LoadFilterableSpecificationAttributeOptionIds = 1 - BEGIN - CREATE TABLE #FilterableSpecs - ( - [SpecificationAttributeOptionId] int NOT NULL - ) - INSERT INTO #FilterableSpecs ([SpecificationAttributeOptionId]) - SELECT DISTINCT [psam].SpecificationAttributeOptionId - FROM [Product_SpecificationAttribute_Mapping] [psam] with (NOLOCK) - WHERE [psam].[AllowFiltering] = 1 - AND [psam].[ProductId] IN (SELECT [pi].ProductId FROM #PageIndex [pi]) - - --build comma separated list of filterable identifiers - SELECT @FilterableSpecificationAttributeOptionIds = COALESCE(@FilterableSpecificationAttributeOptionIds + ',' , '') + CAST(SpecificationAttributeOptionId as nvarchar(4000)) - FROM #FilterableSpecs - - DROP TABLE #FilterableSpecs - END - - --return products - SELECT TOP (@RowsToReturn) - p.* - FROM - #PageIndex [pi] - INNER JOIN Product p with (NOLOCK) on p.Id = [pi].[ProductId] - WHERE - [pi].IndexId > @PageLowerBound AND - [pi].IndexId < @PageUpperBound - ORDER BY - [pi].IndexId - - DROP TABLE #PageIndex -END diff --git a/src/Libraries/SmartStore.Data/Sql/StoredProcedures.Inverse.sql b/src/Libraries/SmartStore.Data/Sql/StoredProcedures.Inverse.sql index fb7180e930..904868e026 100644 --- a/src/Libraries/SmartStore.Data/Sql/StoredProcedures.Inverse.sql +++ b/src/Libraries/SmartStore.Data/Sql/StoredProcedures.Inverse.sql @@ -1,19 +1,7 @@ -DROP PROCEDURE [dbo].[ProductLoadAllPaged] -GO - + DROP PROCEDURE [dbo].[ProductTagCountLoadAll] GO -DROP PROCEDURE [dbo].[FullText_IsSupported] -GO - -DROP PROCEDURE [dbo].[FullText_Enable] -GO - -DROP PROCEDURE [dbo].[FullText_Disable] -GO - - DROP FUNCTION [dbo].[sm_getprimarykey_indexname] GO diff --git a/src/Libraries/SmartStore.Data/Sql/StoredProcedures.sql b/src/Libraries/SmartStore.Data/Sql/StoredProcedures.sql index 4f93be5a39..550b34cf7d 100644 --- a/src/Libraries/SmartStore.Data/Sql/StoredProcedures.sql +++ b/src/Libraries/SmartStore.Data/Sql/StoredProcedures.sql @@ -63,593 +63,6 @@ END GO -CREATE PROCEDURE [dbo].[ProductLoadAllPaged] -( - @CategoryIds nvarchar(MAX) = null, --a list of category IDs (comma-separated list). e.g. 1,2,3 - @ManufacturerId int = 0, - @StoreId int = 0, - @ParentGroupedProductId int = 0, - @ProductTypeId int = null, --product type identifier, null - load all products - @VisibleIndividuallyOnly bit = 0, --0 - load all products , 1 - "visible indivially" only - @ProductTagId int = 0, - @FeaturedProducts bit = null, --0 featured only , 1 not featured only, null - load all products - @PriceMin decimal(18, 4) = null, - @PriceMax decimal(18, 4) = null, - @Keywords nvarchar(4000) = null, - @SearchDescriptions bit = 0, --a value indicating whether to search by a specified "keyword" in product descriptions - @SearchSku bit = 0, --a value indicating whether to search by a specified "keyword" in product SKU - @SearchProductTags bit = 0, --a value indicating whether to search by a specified "keyword" in product tags - @UseFullTextSearch bit = 0, - @FullTextMode int = 0, --0 - using CONTAINS with , 5 - using CONTAINS and OR with , 10 - using CONTAINS and AND with - @FilteredSpecs nvarchar(MAX) = null, --filter by attributes (comma-separated list). e.g. 14,15,16 - @LanguageId int = 0, - @OrderBy int = 0, --0 - position, 5 - Name: A to Z, 6 - Name: Z to A, 10 - Price: Low to High, 11 - Price: High to Low, 15 - creation date - @AllowedCustomerRoleIds nvarchar(MAX) = null, --a list of customer role IDs (comma-separated list) for which a product should be shown (if a subjet to ACL) - @PageIndex int = 0, - @PageSize int = 2147483644, - @ShowHidden bit = 0, - @LoadFilterableSpecificationAttributeOptionIds bit = 0, --a value indicating whether we should load the specification attribute option identifiers applied to loaded products (all pages) - @FilterableSpecificationAttributeOptionIds nvarchar(MAX) = null OUTPUT, --the specification attribute option identifiers applied to loaded products (all pages). returned as a comma separated list of identifiers - @TotalRecords int = null OUTPUT -) -AS -BEGIN - - /* Products that filtered by keywords */ - CREATE TABLE #KeywordProducts - ( - [ProductId] int NOT NULL - ) - - DECLARE - @SearchKeywords bit, - @sql nvarchar(max), - @sql_orderby nvarchar(max) - - SET NOCOUNT ON - - --filter by keywords - SET @Keywords = isnull(@Keywords, '') - SET @Keywords = rtrim(ltrim(@Keywords)) - IF ISNULL(@Keywords, '') != '' - BEGIN - SET @SearchKeywords = 1 - - IF @UseFullTextSearch = 1 - BEGIN - --remove wrong chars (' ") - SET @Keywords = REPLACE(@Keywords, '''', '') - SET @Keywords = REPLACE(@Keywords, '"', '') - - --full-text search - IF @FullTextMode = 0 - BEGIN - --0 - using CONTAINS with - SET @Keywords = ' "' + @Keywords + '*" ' - END - ELSE - BEGIN - --5 - using CONTAINS and OR with - --10 - using CONTAINS and AND with - - --clean multiple spaces - WHILE CHARINDEX(' ', @Keywords) > 0 - SET @Keywords = REPLACE(@Keywords, ' ', ' ') - - DECLARE @concat_term nvarchar(100) - IF @FullTextMode = 5 --5 - using CONTAINS and OR with - BEGIN - SET @concat_term = 'OR' - END - IF @FullTextMode = 10 --10 - using CONTAINS and AND with - BEGIN - SET @concat_term = 'AND' - END - - --now let's build search string - declare @fulltext_keywords nvarchar(4000) - set @fulltext_keywords = N'' - declare @index int - - set @index = CHARINDEX(' ', @Keywords, 0) - - -- if index = 0, then only one field was passed - IF(@index = 0) - set @fulltext_keywords = ' "' + @Keywords + '*" ' - ELSE - BEGIN - DECLARE @first BIT - SET @first = 1 - WHILE @index > 0 - BEGIN - IF (@first = 0) - SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' - ELSE - SET @first = 0 - - SET @fulltext_keywords = @fulltext_keywords + '"' + SUBSTRING(@Keywords, 1, @index - 1) + '*"' - SET @Keywords = SUBSTRING(@Keywords, @index + 1, LEN(@Keywords) - @index) - SET @index = CHARINDEX(' ', @Keywords, 0) - end - - -- add the last field - IF LEN(@fulltext_keywords) > 0 - SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' + '"' + SUBSTRING(@Keywords, 1, LEN(@Keywords)) + '*"' - END - SET @Keywords = @fulltext_keywords - END - END - ELSE - BEGIN - --usual search by PATINDEX - SET @Keywords = '%' + @Keywords + '%' - END - --PRINT @Keywords - - --product name - SET @sql = ' - INSERT INTO #KeywordProducts ([ProductId]) - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[Name], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[Name]) > 0 ' - - - --localized product name - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''Name''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - - - IF @SearchDescriptions = 1 - BEGIN - --product short description - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[ShortDescription], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[ShortDescription]) > 0 ' - - - --product full description - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[FullDescription], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[FullDescription]) > 0 ' - - - - --localized product short description - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''ShortDescription''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - - - --localized product full description - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''FullDescription''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - END - - --SKU - IF @SearchSku = 1 - BEGIN - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - LEFT OUTER JOIN ProductVariantAttributeCombination pvac with(NOLOCK) ON pvac.ProductId = p.Id - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + '(CONTAINS(pvac.[Sku], @Keywords) OR CONTAINS(p.[Sku], @Keywords)) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, pvac.[Sku]) > 0 OR PATINDEX(@Keywords, p.[Sku]) > 0 ' - END - - IF @SearchProductTags = 1 - BEGIN - --product tag - SET @sql = @sql + ' - UNION - SELECT pptm.Product_Id - FROM Product_ProductTag_Mapping pptm with(NOLOCK) INNER JOIN ProductTag pt with(NOLOCK) ON pt.Id = pptm.ProductTag_Id - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(pt.[Name], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, pt.[Name]) > 0 ' - - --localized product tag - SET @sql = @sql + ' - UNION - SELECT pptm.Product_Id - FROM LocalizedProperty lp with (NOLOCK) INNER JOIN Product_ProductTag_Mapping pptm with(NOLOCK) ON lp.EntityId = pptm.ProductTag_Id - WHERE - lp.LocaleKeyGroup = N''ProductTag'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''Name''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - END - - --PRINT (@sql) - EXEC sp_executesql @sql, N'@Keywords nvarchar(4000)', @Keywords - - END - ELSE - BEGIN - SET @SearchKeywords = 0 - END - - --filter by category IDs - SET @CategoryIds = isnull(@CategoryIds, '') - CREATE TABLE #FilteredCategoryIds - ( - CategoryId int not null - ) - INSERT INTO #FilteredCategoryIds (CategoryId) - SELECT CAST(data as int) FROM [sm_splitstring_to_table](@CategoryIds, ',') - DECLARE @CategoryIdsCount int - SET @CategoryIdsCount = (SELECT COUNT(1) FROM #FilteredCategoryIds) - - --filter by attributes - SET @FilteredSpecs = isnull(@FilteredSpecs, '') - CREATE TABLE #FilteredSpecs - ( - SpecificationAttributeOptionId int not null - ) - INSERT INTO #FilteredSpecs (SpecificationAttributeOptionId) - SELECT CAST(data as int) FROM [sm_splitstring_to_table](@FilteredSpecs, ',') - DECLARE @SpecAttributesCount int - SET @SpecAttributesCount = (SELECT COUNT(1) FROM #FilteredSpecs) - - --filter by customer role IDs (access control list) - SET @AllowedCustomerRoleIds = isnull(@AllowedCustomerRoleIds, '') - CREATE TABLE #FilteredCustomerRoleIds - ( - CustomerRoleId int not null - ) - INSERT INTO #FilteredCustomerRoleIds (CustomerRoleId) - SELECT CAST(data as int) FROM [sm_splitstring_to_table](@AllowedCustomerRoleIds, ',') - - --paging - DECLARE @PageLowerBound int - DECLARE @PageUpperBound int - DECLARE @RowsToReturn int - SET @RowsToReturn = @PageSize * (@PageIndex + 1) - SET @PageLowerBound = @PageSize * @PageIndex - SET @PageUpperBound = @PageLowerBound + @PageSize + 1 - - CREATE TABLE #DisplayOrderTmp - ( - [Id] int IDENTITY (1, 1) NOT NULL, - [ProductId] int NOT NULL - ) - - SET @sql = ' - INSERT INTO #DisplayOrderTmp ([ProductId]) - SELECT p.Id - FROM - Product p with (NOLOCK)' - - IF @CategoryIdsCount > 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_Category_Mapping pcm with (NOLOCK) - ON p.Id = pcm.ProductId' - END - - IF @ManufacturerId > 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_Manufacturer_Mapping pmm with (NOLOCK) - ON p.Id = pmm.ProductId' - END - - IF ISNULL(@ProductTagId, 0) != 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_ProductTag_Mapping pptm with (NOLOCK) - ON p.Id = pptm.Product_Id' - END - - --searching by keywords - IF @SearchKeywords = 1 - BEGIN - SET @sql = @sql + ' - JOIN #KeywordProducts kp - ON p.Id = kp.ProductId' - END - - SET @sql = @sql + ' - WHERE - p.Deleted = 0' - - --filter by category - IF @CategoryIdsCount > 0 - BEGIN - SET @sql = @sql + ' - AND pcm.CategoryId IN (SELECT CategoryId FROM #FilteredCategoryIds)' - - IF @FeaturedProducts IS NOT NULL - BEGIN - SET @sql = @sql + ' - AND pcm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) - END - END - - --filter by manufacturer - IF @ManufacturerId > 0 - BEGIN - SET @sql = @sql + ' - AND pmm.ManufacturerId = ' + CAST(@ManufacturerId AS nvarchar(max)) - - IF @FeaturedProducts IS NOT NULL - BEGIN - SET @sql = @sql + ' - AND pmm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) - END - END - - --filter by parent grouped product identifer - IF @ParentGroupedProductId > 0 - BEGIN - SET @sql = @sql + ' - AND p.ParentGroupedProductId = ' + CAST(@ParentGroupedProductId AS nvarchar(max)) - END - - --filter by product type - IF @ProductTypeId is not null - BEGIN - SET @sql = @sql + ' - AND p.ProductTypeId = ' + CAST(@ProductTypeId AS nvarchar(max)) - END - - --filter by visible individually - IF @VisibleIndividuallyOnly = 1 - BEGIN - SET @sql = @sql + ' - AND p.VisibleIndividually = 1' - END - - --filter by product tag - IF ISNULL(@ProductTagId, 0) != 0 - BEGIN - SET @sql = @sql + ' - AND pptm.ProductTag_Id = ' + CAST(@ProductTagId AS nvarchar(max)) - END - - --show hidden - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND p.Published = 1 - AND p.Deleted = 0 - AND (getutcdate() BETWEEN ISNULL(p.AvailableStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.AvailableEndDateTimeUtc, ''1/1/2999''))' - END - - --min price - IF @PriceMin > 0 - BEGIN - SET @sql = @sql + ' - AND ( - ( - --special price (specified price and valid date range) - (p.SpecialPrice IS NOT NULL AND (getutcdate() BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.SpecialPrice >= ' + CAST(@PriceMin AS nvarchar(max)) + ') - ) - OR - ( - --regular price (price isnt specified or date range isnt valid) - (p.SpecialPrice IS NULL OR (getutcdate() NOT BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.Price >= ' + CAST(@PriceMin AS nvarchar(max)) + ') - ) - )' - END - - --max price - IF @PriceMax > 0 - BEGIN - SET @sql = @sql + ' - AND ( - ( - --special price (specified price and valid date range) - (p.SpecialPrice IS NOT NULL AND (getutcdate() BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.SpecialPrice <= ' + CAST(@PriceMax AS nvarchar(max)) + ') - ) - OR - ( - --regular price (price isnt specified or date range isnt valid) - (p.SpecialPrice IS NULL OR (getutcdate() NOT BETWEEN isnull(p.SpecialPriceStartDateTimeUtc, ''1/1/1900'') AND isnull(p.SpecialPriceEndDateTimeUtc, ''1/1/2999''))) - AND - (p.Price <= ' + CAST(@PriceMax AS nvarchar(max)) + ') - ) - )' - END - - --show hidden and ACL - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND (p.SubjectToAcl = 0 OR EXISTS ( - SELECT 1 FROM #FilteredCustomerRoleIds [fcr] - WHERE - [fcr].CustomerRoleId IN ( - SELECT [acl].CustomerRoleId - FROM [AclRecord] acl with (NOLOCK) - WHERE [acl].EntityId = p.Id AND [acl].EntityName = ''Product'' - ) - ))' - END - - --show hidden and filter by store - IF @StoreId > 0 - BEGIN - SET @sql = @sql + ' - AND (p.LimitedToStores = 0 OR EXISTS ( - SELECT 1 FROM [StoreMapping] sm with (NOLOCK) - WHERE [sm].EntityId = p.Id AND [sm].EntityName = ''Product'' and [sm].StoreId=' + CAST(@StoreId AS nvarchar(max)) + ' - ))' - END - - --filter by specs - IF @SpecAttributesCount > 0 - BEGIN - SET @sql = @sql + ' - AND NOT EXISTS ( - SELECT 1 FROM #FilteredSpecs [fs] - WHERE - [fs].SpecificationAttributeOptionId NOT IN ( - SELECT psam.SpecificationAttributeOptionId - FROM Product_SpecificationAttribute_Mapping psam with (NOLOCK) - WHERE psam.AllowFiltering = 1 AND psam.ProductId = p.Id - ) - )' - END - - --sorting - SET @sql_orderby = '' - IF @OrderBy = 5 /* Name: A to Z */ - SET @sql_orderby = ' p.[Name] ASC' - ELSE IF @OrderBy = 6 /* Name: Z to A */ - SET @sql_orderby = ' p.[Name] DESC' - ELSE IF @OrderBy = 10 /* Price: Low to High */ - SET @sql_orderby = ' p.[Price] ASC' - ELSE IF @OrderBy = 11 /* Price: High to Low */ - SET @sql_orderby = ' p.[Price] DESC' - ELSE IF @OrderBy = 15 /* creation date */ - SET @sql_orderby = ' p.[CreatedOnUtc] DESC' - ELSE /* default sorting, 0 (position) */ - BEGIN - --category position (display order) - IF @CategoryIdsCount > 0 SET @sql_orderby = ' pcm.DisplayOrder ASC' - - --manufacturer position (display order) - IF @ManufacturerId > 0 - BEGIN - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' pmm.DisplayOrder ASC' - END - - --parent grouped product specified (sort associated products) - IF @ParentGroupedProductId > 0 - BEGIN - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' p.[DisplayOrder] ASC' - END - - --name - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' p.[Name] ASC' - END - - SET @sql = @sql + ' - ORDER BY' + @sql_orderby - - --PRINT (@sql) - EXEC sp_executesql @sql - - DROP TABLE #FilteredCategoryIds - DROP TABLE #FilteredSpecs - DROP TABLE #FilteredCustomerRoleIds - DROP TABLE #KeywordProducts - - CREATE TABLE #PageIndex - ( - [IndexId] int IDENTITY (1, 1) NOT NULL, - [ProductId] int NOT NULL - ) - INSERT INTO #PageIndex ([ProductId]) - SELECT ProductId - FROM #DisplayOrderTmp - GROUP BY ProductId - ORDER BY min([Id]) - - --total records - SET @TotalRecords = @@rowcount - - DROP TABLE #DisplayOrderTmp - - --prepare filterable specification attribute option identifier (if requested) - IF @LoadFilterableSpecificationAttributeOptionIds = 1 - BEGIN - CREATE TABLE #FilterableSpecs - ( - [SpecificationAttributeOptionId] int NOT NULL - ) - INSERT INTO #FilterableSpecs ([SpecificationAttributeOptionId]) - SELECT DISTINCT [psam].SpecificationAttributeOptionId - FROM [Product_SpecificationAttribute_Mapping] [psam] with (NOLOCK) - WHERE [psam].[AllowFiltering] = 1 - AND [psam].[ProductId] IN (SELECT [pi].ProductId FROM #PageIndex [pi]) - - --build comma separated list of filterable identifiers - SELECT @FilterableSpecificationAttributeOptionIds = COALESCE(@FilterableSpecificationAttributeOptionIds + ',' , '') + CAST(SpecificationAttributeOptionId as nvarchar(4000)) - FROM #FilterableSpecs - - DROP TABLE #FilterableSpecs - END - - --return products - SELECT TOP (@RowsToReturn) - p.* - FROM - #PageIndex [pi] - INNER JOIN Product p with (NOLOCK) on p.Id = [pi].[ProductId] - WHERE - [pi].IndexId > @PageLowerBound AND - [pi].IndexId < @PageUpperBound - ORDER BY - [pi].IndexId - - DROP TABLE #PageIndex -END -GO - CREATE PROCEDURE [dbo].[ProductTagCountLoadAll] ( @@ -673,92 +86,4 @@ BEGIN GROUP BY pt.Id ORDER BY pt.Id END -GO - - -CREATE PROCEDURE [dbo].[FullText_IsSupported] -AS -BEGIN - EXEC(' - SELECT CASE SERVERPROPERTY(''IsFullTextInstalled'') - WHEN 1 THEN - CASE DatabaseProperty (DB_NAME(DB_ID()), ''IsFulltextEnabled'') - WHEN 1 THEN 1 - ELSE 0 - END - ELSE 0 - END') -END -GO - - - -CREATE PROCEDURE [dbo].[FullText_Enable] -AS -BEGIN - --create catalog - EXEC(' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = ''SmartStoreNETFullTextCatalog'') - CREATE FULLTEXT CATALOG [SmartStoreNETFullTextCatalog] AS DEFAULT') - - --create indexes - DECLARE @create_index_text nvarchar(4000) - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[Product]'')) - CREATE FULLTEXT INDEX ON [Product]([Name], [ShortDescription], [FullDescription], [Sku]) - KEY INDEX [' + dbo.[sm_getprimarykey_indexname] ('Product') + '] ON [SmartStoreNETFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) - - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductVariantAttributeCombination]'')) - CREATE FULLTEXT INDEX ON [ProductVariantAttributeCombination]([SKU]) - KEY INDEX [' + dbo.[sm_getprimarykey_indexname] ('ProductVariantAttributeCombination') + '] ON [SmartStoreNETFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) - - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[LocalizedProperty]'')) - CREATE FULLTEXT INDEX ON [LocalizedProperty]([LocaleValue]) - KEY INDEX [' + dbo.[sm_getprimarykey_indexname] ('LocalizedProperty') + '] ON [SmartStoreNETFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) - - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductTag]'')) - CREATE FULLTEXT INDEX ON [ProductTag]([Name]) - KEY INDEX [' + dbo.[sm_getprimarykey_indexname] ('ProductTag') + '] ON [SmartStoreNETFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) -END -GO - - - -CREATE PROCEDURE [dbo].[FullText_Disable] -AS -BEGIN - EXEC(' - --drop indexes - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[Product]'')) - DROP FULLTEXT INDEX ON [Product] - ') - - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductVariantAttributeCombination]'')) - DROP FULLTEXT INDEX ON [ProductVariantAttributeCombination] - ') - - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[LocalizedProperty]'')) - DROP FULLTEXT INDEX ON [LocalizedProperty] - ') - - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductTag]'')) - DROP FULLTEXT INDEX ON [ProductTag] - ') - - --drop catalog - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = ''SmartStoreNETFullTextCatalog'') - DROP FULLTEXT CATALOG [SmartStoreNETFullTextCatalog] - ') -END -GO +GO \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SqlCeDataProvider.cs b/src/Libraries/SmartStore.Data/SqlCeDataProvider.cs index 59e917410b..ba2681d732 100644 --- a/src/Libraries/SmartStore.Data/SqlCeDataProvider.cs +++ b/src/Libraries/SmartStore.Data/SqlCeDataProvider.cs @@ -1,36 +1,21 @@ using System.Data.Common; -using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.SqlClient; -using SmartStore.Core.Data; -using SmartStore.Data.Setup; -using SmartStore.Data.Migrations; namespace SmartStore.Data { public class SqlCeDataProvider : IEfDataProvider { - /// - /// Get connection factory - /// - /// Connection factory public virtual IDbConnectionFactory GetConnectionFactory() { - return new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); - } + return new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); + } - /// - /// A value indicating whether this data provider supports stored procedures - /// public bool StoredProceduresSupported { get { return false; } } - /// - /// Gets a support database parameter object (used by stored procedures) - /// - /// Parameter public DbParameter GetParameter() { return new SqlParameter(); diff --git a/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs b/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs new file mode 100644 index 0000000000..49c93d1b22 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs @@ -0,0 +1,827 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +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.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 EfState = System.Data.Entity.EntityState; + +namespace SmartStore.Data.Utilities +{ + public static class DataMigrator + { + #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)); + + // INFO: this method must be able to handle pre-save state also. + + 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)); + + entities = entities ?? product.ProductPictures; + if (entities == null) + return false; + + var transientEntities = entities.Where(x => x.Id == 0); + + var sortedEntities = entities + // Remove transient entities + .Except(transientEntities) + .OrderBy(x => x.DisplayOrder) + .ThenBy(x => x.Id) + .Select(x => ctx.Entry(x)) + // Remove deleted and detached entities + .Where(x => x.State != EfState.Deleted && x.State != EfState.Detached) + .Select(x => x.Entity) + // Added/transient entities must be appended + .Concat(transientEntities.OrderBy(x => x.DisplayOrder)); + + var newMainPictureId = sortedEntities.FirstOrDefault()?.PictureId; + + if (newMainPictureId != product.MainPictureId) + { + product.MainPictureId = newMainPictureId; + return true; + } + + return false; + } + + /// + /// Traverses all products and fixes 'MainPictureId' property values if it is out of sync. + /// + /// Database context (must be ) + /// Minimum modified or created date of products to process. Pass null to fix all products. + /// The total count of fixed and updated product entities + public static int FixProductMainPictureIds(IDbContext context, DateTime? ifModifiedSinceUtc = null) + { + return FixProductMainPictureIds(context, false, ifModifiedSinceUtc); + } + + /// + /// Called from migration seeder and only processes product entities without MainPictureId value. + /// + /// The total count of fixed and updated product entities + internal static int FixProductMainPictureIds(IDbContext context, bool initial, DateTime? ifModifiedSinceUtc = null) + { + 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)); + + var query = from p in ctx.Set().AsNoTracking() + where (!initial || p.MainPictureId == null) && (ifModifiedSinceUtc == null || p.UpdatedOnUtc >= ifModifiedSinceUtc.Value) + orderby p.Id + select new { p.Id, p.MainPictureId }; + + // Key = ProductId, Value = MainPictureId + var toUpate = new Dictionary(); + + // 1st pass + int pageIndex = -1; + while (true) + { + var products = PagedList.Create(query, ++pageIndex, 1000); + var map = GetPoductPictureMap(ctx, products.Select(x => x.Id).ToArray()); + + foreach (var p in products) + { + int? fixedPictureId = null; + if (map.ContainsKey(p.Id)) + { + // Product has still a pic. + fixedPictureId = map[p.Id]; + } + + // Update only if fixed PictureId differs from current + if (fixedPictureId != p.MainPictureId) + { + toUpate.Add(p.Id, fixedPictureId); + } + } + + if (!products.HasNextPage) + break; + } + + // 2nd pass + foreach (var chunk in toUpate.Slice(1000)) + { + using (var tx = ctx.Database.BeginTransaction()) + { + foreach (var kvp in chunk) + { + context.ExecuteSqlCommand("Update [Product] Set [MainPictureId] = {0} WHERE [Id] = {1}", false, null, kvp.Value, kvp.Key); + } + + context.SaveChanges(); + tx.Commit(); + } + } + + return toUpate.Count; + } + + private static IDictionary GetPoductPictureMap(SmartObjectContext context, IEnumerable productIds) + { + var map = new Dictionary(); + + var query = from pp in context.Set().AsNoTracking() + where productIds.Contains(pp.ProductId) + group pp by pp.ProductId into g + select new + { + ProductId = g.Key, + PictureIds = g.OrderBy(x => x.DisplayOrder).ThenBy(x => x.Id) + .Take(1) + .Select(x => x.PictureId) + }; + + map = query.ToList().ToDictionary(x => x.ProductId, x => x.PictureIds.First()); + + return map; + } + + #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) + { + 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)); + + var filePath = CommonHelper.MapPath("~/App_Data/AddressFormats.xml"); + + if (!File.Exists(filePath)) + { + return 0; + } + + var countries = ctx.Set() + .Where(x => x.AddressFormat == null) + .ToList() + .ToDictionarySafe(x => x.TwoLetterIsoCode, StringComparer.OrdinalIgnoreCase); + + var doc = XDocument.Load(filePath); + + foreach (var node in doc.Root.Nodes().OfType()) + { + var code = node.Attribute("code")?.Value?.Trim(); + var format = node.Value.Trim(); + + if (code.HasValue() && countries.TryGetValue(code, out var country)) + { + country.AddressFormat = format; + } + } + + return ctx.SaveChanges(); + } + + #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 new file mode 100644 index 0000000000..310aa4a220 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Utilities/MessageTemplateConverter.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Messages; +using SmartStore.Utilities; +using System.Xml; +using System.Xml.Linq; + +namespace SmartStore.Data.Utilities +{ + public sealed class MessageTemplateConverter + { + private readonly SmartObjectContext _ctx; + private readonly EmailAccount _defaultEmailAccount; + + public MessageTemplateConverter(IDbContext context) + { + _ctx = context as SmartObjectContext; + if (_ctx == null) + throw new ArgumentException("Passed context must be an instance of type '{0}'.".FormatInvariant(typeof(SmartObjectContext)), nameof(context)); + + _defaultEmailAccount = _ctx.Set().FirstOrDefault(x => x.Email != null); + } + + /// + /// Loads a single message template from file (~/App_Data/EmailTemplates/) + /// and deserializes its xml content. + /// + /// 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, string virtualRootPath = null) + { + Guard.NotEmpty(templateName, nameof(templateName)); + Guard.NotNull(language, nameof(language)); + + var dir = ResolveTemplateDirectory(language, virtualRootPath); + var fullPath = Path.Combine(dir.FullName, templateName + ".xml"); + + if (!File.Exists(fullPath)) + { + throw new FileNotFoundException($"File '{fullPath}' does not exist."); + } + + return DeserializeTemplate(fullPath); + } + + /// + /// 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, string virtualRootPath = null) + { + Guard.NotNull(language, nameof(language)); + + var dir = ResolveTemplateDirectory(language, virtualRootPath); + var files = dir.EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly); + + foreach (var file in files) + { + var template = DeserializeTemplate(file.FullName); + template.Name = Path.GetFileNameWithoutExtension(file.Name); + yield return template; + } + } + + public MessageTemplate Deserialize(string xml, string templateName) + { + Guard.NotEmpty(xml, nameof(xml)); + Guard.NotEmpty(templateName, nameof(templateName)); + + var template = DeserializeDocument(XDocument.Parse(xml)); + template.Name = templateName; + return template; + } + + public XmlDocument Save(MessageTemplate template, Language language) + { + Guard.NotNull(template, nameof(template)); + Guard.NotNull(language, nameof(language)); + + var doc = new XmlDocument(); + doc.LoadXml(""); + + var root = doc.DocumentElement; + root.AppendChild(doc.CreateElement("To")).InnerText = template.To; + if (template.ReplyTo.HasValue()) + root.AppendChild(doc.CreateElement("ReplyTo")).InnerText = template.ReplyTo; + root.AppendChild(doc.CreateElement("Subject")).InnerText = template.Subject; + root.AppendChild(doc.CreateElement("ModelTypes")).InnerText = template.ModelTypes; + root.AppendChild(doc.CreateElement("Body")).AppendChild(doc.CreateCDataSection(template.Body)); + + var path = Path.Combine(CommonHelper.MapPath("~/App_Data/EmailTemplates"), language.GetTwoLetterISOLanguageName()); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + // File path + path = Path.Combine(path, template.Name + ".xml"); + + var xml = Prettifier.PrettifyXML(doc.OuterXml); + File.WriteAllText(path, xml); + + return doc; + } + + /// + /// Imports all template xml files to MessageTemplate table + /// + /// 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, virtualRootPath); + var dbTemplatesMap = table + .ToList() + .ToMultimap(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase); + + foreach (var source in sourceTemplates) + { + if (dbTemplatesMap.ContainsKey(source.Name)) + { + 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 + { + var template = new MessageTemplate + { + Name = source.Name, + To = source.To, + ReplyTo = source.ReplyTo, + Subject = source.Subject, + ModelTypes = source.ModelTypes, + Body = source.Body, + IsActive = true, + EmailAccountId = (_defaultEmailAccount?.Id).GetValueOrDefault(), + }; + + table.Add(template); + } + } + + _ctx.SaveChanges(); + } + + private DirectoryInfo ResolveTemplateDirectory(Language language, string virtualRootPath = null) + { + var rootPath = CommonHelper.MapPath(virtualRootPath.NullEmpty() ?? "~/App_Data/EmailTemplates/"); + var testPaths = new[] + { + language.LanguageCulture, + language.GetTwoLetterISOLanguageName(), + "en" + }; + + foreach (var path in testPaths.Select(x => Path.Combine(rootPath, x))) + { + if (Directory.Exists(path)) + { + 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."); + } + + private MessageTemplate DeserializeTemplate(string fullPath) + { + return DeserializeDocument(XDocument.Load(fullPath)); + } + + 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(); + + switch (node.Name.LocalName) + { + case "To": + result.To = value; + break; + case "ReplyTo": + result.ReplyTo = value; + break; + case "Subject": + result.Subject = value; + break; + case "ModelTypes": + result.ModelTypes = value; + break; + case "Body": + result.Body = value; + break; + } + } + + return result; + } + } +} diff --git a/src/Libraries/SmartStore.Data/app.config b/src/Libraries/SmartStore.Data/app.config index 284b7ee573..63141836b9 100644 --- a/src/Libraries/SmartStore.Data/app.config +++ b/src/Libraries/SmartStore.Data/app.config @@ -4,12 +4,20 @@ - + - + + + + + + + + + - + diff --git a/src/Libraries/SmartStore.Data/packages.config b/src/Libraries/SmartStore.Data/packages.config index b1c169951c..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/Affiliates/AffiliateService.cs b/src/Libraries/SmartStore.Services/Affiliates/AffiliateService.cs index 37e67c5239..68475199cf 100644 --- a/src/Libraries/SmartStore.Services/Affiliates/AffiliateService.cs +++ b/src/Libraries/SmartStore.Services/Affiliates/AffiliateService.cs @@ -89,9 +89,6 @@ public virtual void InsertAffiliate(Affiliate affiliate) throw new ArgumentNullException("affiliate"); _affiliateRepository.Insert(affiliate); - - //event notification - _eventPublisher.EntityInserted(affiliate); } /// @@ -104,9 +101,6 @@ public virtual void UpdateAffiliate(Affiliate affiliate) throw new ArgumentNullException("affiliate"); _affiliateRepository.Update(affiliate); - - //event notification - _eventPublisher.EntityUpdated(affiliate); } #endregion diff --git a/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs b/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs index 93202955fb..7eab4cd87a 100644 --- a/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs +++ b/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs @@ -1,5 +1,3 @@ -//Contributor: Nicholas Mayne - using System; using SmartStore.Core; using SmartStore.Core.Domain.Customers; @@ -28,34 +26,38 @@ public partial class ExternalAuthorizer : IExternalAuthorizer private readonly CustomerSettings _customerSettings; private readonly ExternalAuthenticationSettings _externalAuthenticationSettings; private readonly IShoppingCartService _shoppingCartService; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly LocalizationSettings _localizationSettings; + private readonly IMessageFactory _messageFactory; + private readonly LocalizationSettings _localizationSettings; #endregion #region Ctor - public ExternalAuthorizer(IAuthenticationService authenticationService, + public ExternalAuthorizer( + IAuthenticationService authenticationService, IOpenAuthenticationService openAuthenticationService, IGenericAttributeService genericAttributeService, ICustomerRegistrationService customerRegistrationService, - ICustomerActivityService customerActivityService, ILocalizationService localizationService, - IWorkContext workContext, CustomerSettings customerSettings, + ICustomerActivityService customerActivityService, + ILocalizationService localizationService, + IWorkContext workContext, + CustomerSettings customerSettings, ExternalAuthenticationSettings externalAuthenticationSettings, IShoppingCartService shoppingCartService, - IWorkflowMessageService workflowMessageService, LocalizationSettings localizationSettings) + IMessageFactory messageFactory, + LocalizationSettings localizationSettings) { - this._authenticationService = authenticationService; - this._openAuthenticationService = openAuthenticationService; - this._genericAttributeService = genericAttributeService; - this._customerRegistrationService = customerRegistrationService; - this._customerActivityService = customerActivityService; - this._localizationService = localizationService; - this._workContext = workContext; - this._customerSettings = customerSettings; - this._externalAuthenticationSettings = externalAuthenticationSettings; - this._shoppingCartService = shoppingCartService; - this._workflowMessageService = workflowMessageService; - this._localizationSettings = localizationSettings; + _authenticationService = authenticationService; + _openAuthenticationService = openAuthenticationService; + _genericAttributeService = genericAttributeService; + _customerRegistrationService = customerRegistrationService; + _customerActivityService = customerActivityService; + _localizationService = localizationService; + _workContext = workContext; + _customerSettings = customerSettings; + _externalAuthenticationSettings = externalAuthenticationSettings; + _shoppingCartService = shoppingCartService; + _messageFactory = messageFactory; + _localizationSettings = localizationSettings; } #endregion @@ -109,6 +111,7 @@ public virtual AuthorizationResult Authorize(OpenAuthenticationParameters parame result.AddError("Account is already assigned"); return result; } + if (AccountDoesNotExistAndUserIsNotLoggedOn(userFound, userLoggedIn)) { ExternalAuthorizerHelper.StoreParametersForRoundTrip(parameters); @@ -121,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(); @@ -144,35 +145,32 @@ public virtual AuthorizationResult Authorize(OpenAuthenticationParameters parame //authenticate if (isApproved) _authenticationService.SignIn(userFound ?? userLoggedIn, false); - + //notifications if (_customerSettings.NotifyNewCustomerRegistration) - _workflowMessageService.SendCustomerRegisteredNotificationMessage(currentCustomer, _localizationSettings.DefaultAdminLanguageId); + _messageFactory.SendCustomerRegisteredNotificationMessage(currentCustomer, _localizationSettings.DefaultAdminLanguageId); switch (_customerSettings.UserRegistrationType) { case UserRegistrationType.EmailValidation: - { - //email validation message - _genericAttributeService.SaveAttribute(currentCustomer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); - _workflowMessageService.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 - _workflowMessageService.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; } @@ -207,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 b948930425..3e5a33fa85 100644 --- a/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs +++ b/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs @@ -7,9 +7,6 @@ namespace SmartStore.Services.Authentication { - /// - /// Authentication service - /// public partial class FormsAuthenticationService : IAuthenticationService { private readonly HttpContextBase _httpContext; @@ -19,44 +16,43 @@ public partial class FormsAuthenticationService : IAuthenticationService private Customer _cachedCustomer; - /// - /// Ctor - /// - /// HTTP context - /// Customer service - /// Customer settings 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; @@ -74,23 +70,22 @@ public virtual void SignOut() public virtual Customer GetAuthenticatedCustomer() { - if (_cachedCustomer != null) - return _cachedCustomer; + if (_cachedCustomer != null) + return _cachedCustomer; - if (_httpContext == null || _httpContext.Request == null || !_httpContext.Request.IsAuthenticated || _httpContext.User == null) - return null; + if (_httpContext?.Request == null || !_httpContext.Request.IsAuthenticated || _httpContext.User == null) + 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()) @@ -98,22 +93,32 @@ public virtual Customer GetAuthenticatedCustomer() _cachedCustomer = customer; } - return _cachedCustomer; - } + return _cachedCustomer; + } 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/BlogMessageFactoryExtensions.cs b/src/Libraries/SmartStore.Services/Blogs/BlogMessageFactoryExtensions.cs new file mode 100644 index 0000000000..7ef5c96706 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Blogs/BlogMessageFactoryExtensions.cs @@ -0,0 +1,19 @@ +using System; +using SmartStore.Core.Domain.Blogs; +using SmartStore.Core.Domain.Messages; +using SmartStore.Services.Messages; + +namespace SmartStore.Services.Blogs +{ + public static class BlogMessageFactoryExtensions + { + /// + /// Sends a blog comment notification message to a store owner + /// + public static CreateMessageResult SendBlogCommentNotificationMessage(this IMessageFactory factory, BlogComment blogComment, int languageId = 0) + { + Guard.NotNull(blogComment, nameof(blogComment)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.BlogCommentStoreOwner, languageId, customer: blogComment.Customer), true, blogComment); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs index f3bb8103bb..7cdfafa8b7 100644 --- a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs +++ b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs @@ -7,6 +7,8 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.Localization; +using SmartStore.Core.Domain.Seo; +using SmartStore.Utilities; namespace SmartStore.Services.Blogs { @@ -21,8 +23,8 @@ public partial class BlogService : IBlogService private readonly IRepository _storeMappingRepository; private readonly ICommonServices _services; private readonly ILanguageService _languageService; - - private readonly BlogSettings _blogSettings; + private readonly SeoSettings _seoSettings; + private readonly BlogSettings _blogSettings; #endregion @@ -32,15 +34,17 @@ public BlogService(IRepository blogPostRepository, IRepository storeMappingRepository, ICommonServices services, ILanguageService languageService, - BlogSettings blogSettings) + SeoSettings seoSettings, + BlogSettings blogSettings) { _blogPostRepository = blogPostRepository; _storeMappingRepository = storeMappingRepository; _services = services; _languageService = languageService; _blogSettings = blogSettings; + _seoSettings = seoSettings; - this.QuerySettings = DbQuerySettings.Default; + this.QuerySettings = DbQuerySettings.Default; } public DbQuerySettings QuerySettings { get; set; } @@ -59,9 +63,6 @@ public virtual void DeleteBlogPost(BlogPost blogPost) throw new ArgumentNullException("blogPost"); _blogPostRepository.Delete(blogPost); - - //event notification - _services.EventPublisher.EntityDeleted(blogPost); } /// @@ -146,17 +147,28 @@ orderby bpGroup.Key /// Page size /// A value indicating whether to show hidden records /// Blog posts - public virtual IPagedList GetAllBlogPostsByTag(int storeId, int languageId, string tag, - int pageIndex, int pageSize, bool showHidden = false) + public virtual IPagedList GetAllBlogPostsByTag( + int storeId, + int languageId, + string tag, + int pageIndex, + int pageSize, + bool showHidden = false) { tag = tag.Trim(); //we laod all records and only then filter them by tag var blogPostsAll = GetAllBlogPosts(storeId, languageId, null, null, 0, int.MaxValue, showHidden); var taggedBlogPosts = new List(); + foreach (var blogPost in blogPostsAll) { - var tags = blogPost.ParseTags(); + 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)))) taggedBlogPosts.Add(blogPost); } @@ -211,9 +223,6 @@ public virtual void InsertBlogPost(BlogPost blogPost) throw new ArgumentNullException("blogPost"); _blogPostRepository.Insert(blogPost); - - //event notification - _services.EventPublisher.EntityInserted(blogPost); } /// @@ -226,9 +235,6 @@ public virtual void UpdateBlogPost(BlogPost blogPost) throw new ArgumentNullException("blogPost"); _blogPostRepository.Update(blogPost); - - //event notification - _services.EventPublisher.EntityUpdated(blogPost); } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs b/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs index c81a8e13cd..38a945b869 100644 --- a/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs @@ -18,7 +18,7 @@ public partial class BackInStockSubscriptionService : IBackInStockSubscriptionSe #region Fields private readonly IRepository _backInStockSubscriptionRepository; - private readonly IWorkflowMessageService _workflowMessageService; + private readonly IMessageFactory _messageFactory; private readonly IWorkContext _workContext; private readonly IEventPublisher _eventPublisher; @@ -26,22 +26,16 @@ public partial class BackInStockSubscriptionService : IBackInStockSubscriptionSe #region Ctor - /// - /// Ctor - /// - /// Back in stock subscription repository - /// Workflow message service - /// Work context - /// Event publisher - public BackInStockSubscriptionService(IRepository backInStockSubscriptionRepository, - IWorkflowMessageService workflowMessageService, + public BackInStockSubscriptionService( + IRepository backInStockSubscriptionRepository, + IMessageFactory messageFactory, IWorkContext workContext, IEventPublisher eventPublisher) { - this._backInStockSubscriptionRepository = backInStockSubscriptionRepository; - this._workflowMessageService = workflowMessageService; - this._workContext = workContext; - this._eventPublisher = eventPublisher; + _backInStockSubscriptionRepository = backInStockSubscriptionRepository; + _messageFactory = messageFactory; + _workContext = workContext; + _eventPublisher = eventPublisher; } #endregion @@ -58,9 +52,6 @@ public virtual void DeleteSubscription(BackInStockSubscription subscription) throw new ArgumentNullException("subscription"); _backInStockSubscriptionRepository.Delete(subscription); - - //event notification - _eventPublisher.EntityDeleted(subscription); } /// @@ -152,9 +143,6 @@ public virtual void InsertSubscription(BackInStockSubscription subscription) throw new ArgumentNullException("subscription"); _backInStockSubscriptionRepository.Insert(subscription); - - //event notification - _eventPublisher.EntityInserted(subscription); } /// @@ -167,9 +155,6 @@ public virtual void UpdateSubscription(BackInStockSubscription subscription) throw new ArgumentNullException("subscription"); _backInStockSubscriptionRepository.Update(subscription); - - //event notification - _eventPublisher.EntityUpdated(subscription); } /// @@ -186,12 +171,10 @@ public virtual int SendNotificationsToSubscribers(Product product) var subscriptions = GetAllSubscriptionsByProductId(product.Id, 0, 0, int.MaxValue); foreach (var subscription in subscriptions) { - //ensure that customer is registered (simple and fast way) + // Ensure that customer is registered (simple and fast way) if (subscription.Customer.Email.IsEmail()) { - var customer = subscription.Customer; - var customerLanguageId = customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, subscription.StoreId); - _workflowMessageService.SendBackInStockNotification(subscription, customerLanguageId); + _messageFactory.SendBackInStockNotification(subscription); result++; } } diff --git a/src/Libraries/SmartStore.Services/Catalog/CatalogMessageFactoryExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/CatalogMessageFactoryExtensions.cs new file mode 100644 index 0000000000..41361d97fa --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/CatalogMessageFactoryExtensions.cs @@ -0,0 +1,79 @@ +using System; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Messages; +using SmartStore.Services.Messages; +using SmartStore.Services.Common; + +namespace SmartStore.Services.Catalog +{ + public static class CatalogMessageFactoryExtensions + { + /// + /// Sends "email a friend" message + /// + public static CreateMessageResult SendShareProductMessage(this IMessageFactory factory, Customer customer, Product product, + string fromEmail, string toEmail, string personalMessage, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(product, nameof(product)); + + var model = new NamedModelPart("Message") + { + ["Body"] = personalMessage.NullEmpty(), + ["From"] = fromEmail.NullEmpty(), + ["To"] = toEmail.NullEmpty() + }; + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ShareProduct, languageId, customer: customer), true, product, model); + } + + public static CreateMessageResult SendProductQuestionMessage(this IMessageFactory factory, Customer customer, Product product, + string senderEmail, string senderName, string senderPhone, string question, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(product, nameof(product)); + + var model = new NamedModelPart("Message") + { + ["Message"] = question.NullEmpty(), + ["SenderEmail"] = senderEmail.NullEmpty(), + ["SenderName"] = senderName.NullEmpty(), + ["SenderPhone"] = senderPhone.NullEmpty() + }; + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ProductQuestion, languageId, customer: customer), true, product, model); + } + + /// + /// Sends a product review notification message to a store owner + /// + public static CreateMessageResult SendProductReviewNotificationMessage(this IMessageFactory factory, ProductReview productReview, int languageId = 0) + { + Guard.NotNull(productReview, nameof(productReview)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ProductReviewStoreOwner, languageId, customer: productReview.Customer), true, productReview, productReview.Product); + } + + /// + /// Sends a "quantity below" notification to a store owner + /// + public static CreateMessageResult SendQuantityBelowStoreOwnerNotification(this IMessageFactory factory, Product product, int languageId = 0) + { + Guard.NotNull(product, nameof(product)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.QuantityBelowStoreOwner, languageId), true, product); + } + + /// + /// Sends a 'Back in stock' notification message to a customer + /// + public static CreateMessageResult SendBackInStockNotification(this IMessageFactory factory, BackInStockSubscription subscription) + { + Guard.NotNull(subscription, nameof(subscription)); + + var customer = subscription.Customer; + var languageId = customer.GetAttribute(SystemCustomerAttributeNames.LanguageId); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.BackInStockCustomer, languageId, subscription.StoreId, customer), true, subscription.Product); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs index 39825192a6..f5ce3b7ece 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Localization; namespace SmartStore.Services.Catalog { @@ -17,26 +20,33 @@ public static class CategoryExtensions /// Parent category identifier /// A value indicating whether categories without parent category in provided category list (source) should be ignored /// Sorted categories - public static IList SortCategoriesForTree(this IList source, int parentId = 0, bool ignoreCategoriesWithoutExistingParent = false) + public static IList SortCategoryNodesForTree(this IEnumerable source, int parentId = 0, bool ignoreCategoriesWithoutExistingParent = false) + where T : ICategoryNode { - if (source == null) - throw new ArgumentNullException("source"); + Guard.NotNull(source, nameof(source)); - var result = new List(); + var result = new List(); + var sourceCount = source.Count(); - var categories = source.ToList().FindAll(c => c.ParentCategoryId == parentId); - foreach (var cat in categories) + var childNodes = source.Where(c => c.ParentCategoryId == parentId).ToArray(); + foreach (var node in childNodes) { - result.Add(cat); - result.AddRange(SortCategoriesForTree(source, cat.Id, true)); + result.Add(node); + result.AddRange(SortCategoryNodesForTree(source, node.Id, true)); } - if (!ignoreCategoriesWithoutExistingParent && result.Count != source.Count) + + if (!ignoreCategoriesWithoutExistingParent && result.Count != sourceCount) { - // find categories without parent in provided category source and insert them into result + // Find categories without parent in provided category source and insert them into result foreach (var cat in source) - if (result.Where(x => x.Id == cat.Id).FirstOrDefault() == null) - result.Add(cat); + { + if (result.Where(x => x.Id == cat.Id).FirstOrDefault() == null) + { + result.Add(cat); + } + } } + return result; } @@ -54,77 +64,65 @@ public static ProductCategory FindProductCategory(this IList so if (productCategory.ProductId == productId && productCategory.CategoryId == categoryId) return productCategory; } + return null; } - public static string GetCategoryNameWithAlias(this Category category) + public static string GetCategoryNameIndented(this TreeNode treeNode, + string indentWith = "--", + int? languageId = null, + bool withAlias = true) { - if (category != null) + Guard.NotNull(treeNode, nameof(treeNode)); + + var sb = new StringBuilder(); + var indentSize = treeNode.Depth - 1; + for (int i = 0; i < indentSize; i++) { - if (category.Alias.HasValue()) - return "{0} ({1})".FormatWith(category.Name, category.Alias); - else - return category.Name; + sb.Append(indentWith); } - return null; - } - public static string GetCategoryNameWithPrefix(this Category category, ICategoryService categoryService, IDictionary mappedCategories = null) - { - string result = string.Empty; + var cat = treeNode.Value; - while (category != null) - { - if (String.IsNullOrEmpty(result)) - { - result = category.GetCategoryNameWithAlias(); - } - else - { - result = "--" + result; - } - - int parentId = category.ParentCategoryId; - if (mappedCategories == null) - { - category = categoryService.GetCategoryById(parentId); - } - else - { - category = mappedCategories.ContainsKey(parentId) ? mappedCategories[parentId] : categoryService.GetCategoryById(parentId); - } - } - return result; - } + var name = languageId.HasValue + ? cat.GetLocalized(n => n.Name, languageId.Value) + : cat.Name; - public static string GetCategoryBreadCrumb(this Category category, ICategoryService categoryService, IDictionary mappedCategories = null) - { - string result = string.Empty; + sb.Append(name); - while (category != null && !category.Deleted) - { - if (String.IsNullOrEmpty(result)) - { - result = category.GetCategoryNameWithAlias(); - } - else - { - result = category.GetCategoryNameWithAlias() + " >> " + result; - } - - int parentId = category.ParentCategoryId; - if (mappedCategories == null) - { - category = categoryService.GetCategoryById(parentId); - } - else - { - category = mappedCategories.ContainsKey(parentId) ? mappedCategories[parentId] : categoryService.GetCategoryById(parentId); - } - } + if (withAlias && cat.Alias.HasValue()) + { + sb.Append(" ("); + sb.Append(cat.Alias); + sb.Append(")"); + } - return result; - } + return sb.ToString(); + } - } + /// + /// Builds a category breadcrumb (path) for a particular category node + /// + /// The category node + /// The id of language. Pass null to skip localization. + /// true appends the category alias - if specified - to the name + /// The separator string + /// Category breadcrumb path + public static string GetCategoryPath(this ICategoryNode categoryNode, + ICategoryService categoryService, + int? languageId = null, + string aliasPattern = null, + string separator = " ") + { + Guard.NotNull(categoryNode, nameof(categoryNode)); + + var treeNode = categoryService.GetCategoryTree(categoryNode.Id, true); + if (treeNode != null) + { + 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 c307d76397..f0c104405b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Data.Entity; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; @@ -9,30 +11,28 @@ using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; +using SmartStore.Data.Caching; using SmartStore.Services.Customers; using SmartStore.Services.Localization; +using SmartStore.Services.Search; using SmartStore.Services.Security; using SmartStore.Services.Stores; namespace SmartStore.Services.Catalog { - /// - /// Category service - /// - public partial class CategoryService : ICategoryService + public partial class CategoryService : ICategoryService { - #region Constants + internal static TimeSpan CategoryTreeCacheDuration = TimeSpan.FromHours(6); - private const string CATEGORIES_BY_PARENT_CATEGORY_ID_KEY = "SmartStore.category.byparent-{0}-{1}-{2}-{3}"; - private const string PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY = "SmartStore.productcategory.allbycategoryid-{0}-{1}-{2}-{3}-{4}-{5}"; - private const string PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY = "SmartStore.productcategory.allbyproductid-{0}-{1}-{2}-{3}"; - private const string CATEGORIES_PATTERN_KEY = "SmartStore.category."; - private const string PRODUCTCATEGORIES_PATTERN_KEY = "SmartStore.productcategory."; - private const string CATEGORIES_BY_ID_KEY = "SmartStore.category.id-{0}"; + // {0} = IncludeHidden, {1} = CustomerRoleIds, {2} = StoreId + internal const string CATEGORY_TREE_KEY = "category:tree-{0}-{1}-{2}"; + internal const string CATEGORY_TREE_PATTERN_KEY = "category:tree-*"; - #endregion - - #region Fields + 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; @@ -43,31 +43,16 @@ public partial class CategoryService : ICategoryService private readonly IStoreContext _storeContext; private readonly IEventPublisher _eventPublisher; private readonly IRequestCache _requestCache; + private readonly ICacheManager _cache; private readonly IStoreMappingService _storeMappingService; private readonly IAclService _aclService; - private readonly Lazy> _navigationFilters; private readonly ICustomerService _customerService; - private readonly IProductService _productService; private readonly IStoreService _storeService; + private readonly ICatalogSearchService _catalogSearchService; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Category repository - /// ProductCategory repository - /// Product repository - /// ACL record repository - /// Store mapping repository - /// Work context - /// Store context - /// Event publisher public CategoryService(IRequestCache requestCache, - IRepository categoryRepository, + ICacheManager cache, + IRepository categoryRepository, IRepository productCategoryRepository, IRepository productRepository, IRepository aclRepository, @@ -77,44 +62,43 @@ public CategoryService(IRequestCache requestCache, IEventPublisher eventPublisher, IStoreMappingService storeMappingService, IAclService aclService, - Lazy> navigationFilters, ICustomerService customerService, - IProductService productService, - IStoreService storeService) + IStoreService storeService, + ICatalogSearchService catalogSearchService) { - this._requestCache = requestCache; - this._categoryRepository = categoryRepository; - this._productCategoryRepository = productCategoryRepository; - this._productRepository = productRepository; - this._aclRepository = aclRepository; - this._storeMappingRepository = storeMappingRepository; - this._workContext = workContext; - this._storeContext = storeContext; - this._eventPublisher = eventPublisher; - this._storeMappingService = storeMappingService; - this._aclService = aclService; - this._navigationFilters = navigationFilters; - this._customerService = customerService; - this._productService = productService; - this._storeService = storeService; - - this.QuerySettings = DbQuerySettings.Default; + _requestCache = requestCache; + _cache = cache; + _categoryRepository = categoryRepository; + _productCategoryRepository = productCategoryRepository; + _productRepository = productRepository; + _aclRepository = aclRepository; + _storeMappingRepository = storeMappingRepository; + _workContext = workContext; + _storeContext = storeContext; + _eventPublisher = eventPublisher; + _storeMappingService = storeMappingService; + _aclService = aclService; + _customerService = customerService; + _storeService = storeService; + _catalogSearchService = catalogSearchService; + + QuerySettings = DbQuerySettings.Default; } public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Utilities - private void DeleteAllCategories(IList categories, bool delete) { foreach (var category in categories) - { + { if (delete) + { category.Deleted = true; + } else + { category.ParentCategoryId = 0; + } UpdateCategory(category); @@ -123,26 +107,27 @@ private void DeleteAllCategories(IList categories, bool delete) } } - #endregion - - #region Methods - - public virtual void InheritAclIntoChildren(int categoryId, + public virtual void InheritAclIntoChildren( + int categoryId, bool touchProductsWithMultipleCategories = false, bool touchExistingAcls = false, bool categoriesOnly = false) { - var category = GetCategoryById(categoryId); var subcategories = GetAllCategoriesByParentCategoryId(categoryId, true); - var context = new ProductSearchContext { PageSize = int.MaxValue, ShowHidden = true }; - context.CategoryIds.AddRange(subcategories.Select(x => x.Id)); - context.CategoryIds.Add(categoryId); - var products = _productService.SearchProducts(context); - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - var categoryCustomerRoles = _aclService.GetCustomerRoleIdsWithAccess(category); - - using (var scope = new DbContextScope(ctx: _aclRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var categoryCustomerRoles = _aclService.GetCustomerRoleIdsWithAccessTo(category); + + var categoryIds = new HashSet(subcategories.Select(x => x.Id)); + categoryIds.Add(categoryId); + + var searchQuery = new CatalogSearchQuery() + .WithCategoryIds(null, categoryIds.ToArray()); + + var query = _catalogSearchService.PrepareQuery(searchQuery); + var products = query.OrderBy(p => p.Id).ToList(); + + using (var scope = new DbContextScope(ctx: _aclRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { _aclRepository.AutoCommitEnabled = false; @@ -167,15 +152,14 @@ public virtual void InheritAclIntoChildren(int categoryId, } else { - AclRecord aclRecordToDelete; - if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete)) + if (existingAclRecords.TryGetValue(customerRole.Id, out var aclRecordToDelete)) { _aclRepository.Delete(aclRecordToDelete); } } } } - + _aclRepository.Context.SaveChanges(); foreach (var product in products) @@ -199,12 +183,11 @@ public virtual void InheritAclIntoChildren(int categoryId, } else { - AclRecord aclRecordToDelete; - if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete)) - { - _aclRepository.Delete(aclRecordToDelete); - } - } + if (existingAclRecords.TryGetValue(customerRole.Id, out var aclRecordToDelete)) + { + _aclRepository.Delete(aclRecordToDelete); + } + } } } @@ -212,21 +195,25 @@ public virtual void InheritAclIntoChildren(int categoryId, } } - public virtual void InheritStoresIntoChildren(int categoryId, + public virtual void InheritStoresIntoChildren( + int categoryId, bool touchProductsWithMultipleCategories = false, bool touchExistingAcls = false, bool categoriesOnly = false) { - var category = GetCategoryById(categoryId); var subcategories = GetAllCategoriesByParentCategoryId(categoryId, true); - var context = new ProductSearchContext { PageSize = int.MaxValue , ShowHidden = true }; - context.CategoryIds.AddRange(subcategories.Select(x => x.Id)); - context.CategoryIds.Add(categoryId); - var products = _productService.SearchProducts(context); + var allStores = _storeService.GetAllStores(); + var categoryStoreMappings = _storeMappingService.GetStoresIdsWithAccess(category); + + var categoryIds = new HashSet(subcategories.Select(x => x.Id)); + categoryIds.Add(categoryId); + + var searchQuery = new CatalogSearchQuery() + .WithCategoryIds(null, categoryIds.ToArray()); - var allStores = _storeService.GetAllStores(); - var categoryStoreMappings = _storeMappingService.GetStoresIdsWithAccess(category); + var query = _catalogSearchService.PrepareQuery(searchQuery); + var products = query.OrderBy(p => p.Id).ToList(); using (var scope = new DbContextScope(ctx: _storeMappingRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { @@ -253,12 +240,11 @@ public virtual void InheritStoresIntoChildren(int categoryId, } else { - StoreMapping storeMappingToDelete; - if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete)) - { - _storeMappingRepository.Delete(storeMappingToDelete); - } - } + if (existingStoreMappingsRecords.TryGetValue(store.Id, out var storeMappingToDelete)) + { + _storeMappingRepository.Delete(storeMappingToDelete); + } + } } } @@ -285,12 +271,11 @@ public virtual void InheritStoresIntoChildren(int categoryId, } else { - StoreMapping storeMappingToDelete; - if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete)) - { - _storeMappingRepository.Delete(storeMappingToDelete); - } - } + if (existingStoreMappingsRecords.TryGetValue(store.Id, out var storeMappingToDelete)) + { + _storeMappingRepository.Delete(storeMappingToDelete); + } + } } } @@ -298,28 +283,21 @@ public virtual void InheritStoresIntoChildren(int categoryId, } } - /// - /// Delete category - /// - /// Category - /// Whether to delete child categories or to set them to no parent. public virtual void DeleteCategory(Category category, bool deleteChilds = false) { - if (category == null) - throw new ArgumentNullException("category"); + Guard.NotNull(category, nameof(category)); - category.Deleted = true; + category.Deleted = true; UpdateCategory(category); var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true); DeleteAllCategories(childCategories, deleteChilds); } - public virtual IQueryable GetCategories( + public virtual IQueryable BuildCategoriesQuery( string categoryName = "", bool showHidden = false, string alias = null, - bool applyNavigationFilters = true, int storeId = 0) { var query = _categoryRepository.Table; @@ -333,49 +311,23 @@ public virtual IQueryable GetCategories( 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, applyNavigationFilters, _storeContext.CurrentStore.Id); - } + query = ApplyHiddenCategoriesFilter(query, storeId, showHidden); query = query.Where(c => !c.Deleted); return query; } - - /// - /// Gets all categories - /// - /// Category name - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Alias to be filtered - /// Whether to apply instances to the actual categories query. Never applied when is true - /// A value indicating whether categories without parent category in provided category list (source) should be ignored - /// Store identifier; 0 to load all records - /// Categories - public virtual IPagedList GetAllCategories(string categoryName = "", int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string alias = null, - bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0) + + public virtual IPagedList GetAllCategories( + string categoryName = "", + int pageIndex = 0, + int pageSize = int.MaxValue, + bool showHidden = false, + string alias = null, + bool ignoreCategoriesWithoutExistingParent = true, + int storeId = 0) { - var query = GetCategories(categoryName, showHidden, alias, applyNavigationFilters, storeId); + var query = BuildCategoriesQuery(categoryName, showHidden, alias, storeId); query = query .OrderBy(x => x.ParentCategoryId) @@ -384,19 +336,13 @@ public virtual IPagedList GetAllCategories(string categoryName = "", i var unsortedCategories = query.ToList(); - // sort categories - var sortedCategories = unsortedCategories.SortCategoriesForTree(ignoreCategoriesWithoutExistingParent: ignoreCategoriesWithoutExistingParent); + // Sort categories + var sortedCategories = unsortedCategories.SortCategoryNodesForTree(ignoreCategoriesWithoutExistingParent: ignoreCategoriesWithoutExistingParent); - // paging + // Paging return new PagedList(sortedCategories, pageIndex, pageSize); } - /// - /// Gets all categories filtered by parent category identifier - /// - /// Parent category identifier - /// A value indicating whether to show hidden records - /// Category collection public IList GetAllCategoriesByParentCategoryId(int parentCategoryId, bool showHidden = false) { int storeId = _storeContext.CurrentStore.Id; @@ -404,131 +350,116 @@ public IList GetAllCategoriesByParentCategoryId(int parentCategoryId, return _requestCache.Get(key, () => { var query = _categoryRepository.Table; + 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); - if (!showHidden) - { - query = ApplyHiddenCategoriesFilter(query, false, storeId); - query = query.OrderBy(c => c.DisplayOrder); - } + query = query.Where(c => c.ParentCategoryId == parentCategoryId && !c.Deleted); - var categories = query.ToList(); + query = ApplyHiddenCategoriesFilter(query, storeId, showHidden); + + var categories = query.OrderBy(x => x.DisplayOrder).ToList(); return categories; }); - } - protected virtual IQueryable ApplyHiddenCategoriesFilter(IQueryable query, bool applyNavigationFilters, 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; - } - // only distinct categories (group by ID) - query = from c in query - group c by c.Id into cGroup - orderby cGroup.Key - select cGroup.FirstOrDefault(); + 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(); + } - if (applyNavigationFilters) - { - var filters = _navigationFilters.Value; - if (filters.Any()) - { - filters.Each(x => { - query = x.Apply(query); - }); - } - } return query; } - - /// - /// Gets all categories displayed on the home page - /// - /// Categories + public virtual IList GetAllCategoriesDisplayedOnHomePage() { var query = from c in _categoryRepository.Table orderby c.DisplayOrder where c.Published && - !c.Deleted && + !c.Deleted && c.ShowOnHomePage select c; var categories = query.ToList(); return categories; } - - /// - /// Gets a category - /// - /// Category identifier - /// Category + public virtual Category GetCategoryById(int categoryId) { if (categoryId == 0) return null; - string key = string.Format(CATEGORIES_BY_ID_KEY, categoryId); - return _requestCache.Get(key, () => + return _categoryRepository.GetByIdCached(categoryId, "db.category.id-" + categoryId); + } + + public virtual IList GetCategoriesByIds(int[] categoryIds) + { + if (categoryIds == null || !categoryIds.Any()) { - return _categoryRepository.GetById(categoryId); - }); + 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(); } - /// - /// Inserts category - /// - /// Category public virtual void InsertCategory(Category category) { - if (category == null) - throw new ArgumentNullException("category"); + Guard.NotNull(category, nameof(category)); - _categoryRepository.Insert(category); + _categoryRepository.Insert(category); - //cache _requestCache.RemoveByPattern(CATEGORIES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(category); } - /// - /// Updates the category - /// - /// Category public virtual void UpdateCategory(Category category) { - if (category == null) - throw new ArgumentNullException("category"); + Guard.NotNull(category, nameof(category)); //validate category hierarchy var parentCategory = GetCategoryById(category.ParentCategoryId); @@ -544,54 +475,29 @@ public virtual void UpdateCategory(Category category) _categoryRepository.Update(category); - //cache _requestCache.RemoveByPattern(CATEGORIES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(category); } - - /// - /// Update HasDiscountsApplied property (used for performance optimization) - /// - /// Category + public virtual void UpdateHasDiscountsApplied(Category category) { - if (category == null) - throw new ArgumentNullException("category"); + Guard.NotNull(category, nameof(category)); - category.HasDiscountsApplied = category.AppliedDiscounts.Count > 0; + category.HasDiscountsApplied = category.AppliedDiscounts.Count > 0; UpdateCategory(category); } - /// - /// Deletes a product category mapping - /// - /// Product category public virtual void DeleteProductCategory(ProductCategory productCategory) { - if (productCategory == null) - throw new ArgumentNullException("productCategory"); + Guard.NotNull(productCategory, nameof(productCategory)); - _productCategoryRepository.Delete(productCategory); + _productCategoryRepository.Delete(productCategory); //cache _requestCache.RemoveByPattern(CATEGORIES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(productCategory); } - /// - /// Gets product category mapping collection - /// - /// Category identifier - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Product a category mapping collection public virtual IPagedList GetProductCategoriesByCategoryId(int categoryId, int pageIndex, int pageSize, bool showHidden = false) { if (categoryId == 0) @@ -622,12 +528,6 @@ join p in _productRepository.Table on pc.ProductId equals p.Id }); } - /// - /// Gets a product category mapping collection - /// - /// Product identifier - /// A value indicating whether to show hidden records - /// Product category mapping collection public virtual IList GetProductCategoriesByProductId(int productId, bool showHidden = false) { if (productId == 0) @@ -636,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 && @@ -644,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) @@ -667,15 +569,22 @@ orderby pc.DisplayOrder public virtual Multimap GetProductCategoriesByProductIds(int[] productIds, bool? hasDiscountsApplied = null, bool showHidden = false) { - Guard.ArgumentNotNull(() => productIds); + Guard.NotNull(productIds, nameof(productIds)); - var query = - from pc in _productCategoryRepository.TableUntracked.Expand(x => x.Category).Expand(x => x.Category.Picture) + if (!productIds.Any()) + { + return new Multimap(); + } + + var query = + 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); @@ -689,13 +598,13 @@ orderby pc.DisplayOrder } var map = list.ToMultimap(x => x.ProductId, x => x); - + return map; } public virtual Multimap GetProductCategoriesByCategoryIds(int[] categoryIds) { - Guard.ArgumentNotNull(() => categoryIds); + Guard.NotNull(categoryIds, nameof(categoryIds)); var query = _productCategoryRepository.TableUntracked .Where(x => categoryIds.Contains(x.CategoryId)) @@ -752,11 +661,6 @@ orderby pcGroup.Key return query; } - /// - /// Gets a product category mapping - /// - /// Product category mapping identifier - /// Product category mapping public virtual ProductCategory GetProductCategoryById(int productCategoryId) { if (productCategoryId == 0) @@ -765,29 +669,17 @@ public virtual ProductCategory GetProductCategoryById(int productCategoryId) return _productCategoryRepository.GetById(productCategoryId); } - /// - /// Inserts a product category mapping - /// - /// >Product category mapping public virtual void InsertProductCategory(ProductCategory productCategory) { if (productCategory == null) throw new ArgumentNullException("productCategory"); - + _productCategoryRepository.Insert(productCategory); - //cache _requestCache.RemoveByPattern(CATEGORIES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(productCategory); } - /// - /// Updates the product category mapping - /// - /// >Product category mapping public virtual void UpdateProductCategory(ProductCategory productCategory) { if (productCategory == null) @@ -795,59 +687,168 @@ public virtual void UpdateProductCategory(ProductCategory productCategory) _productCategoryRepository.Update(productCategory); - //cache _requestCache.RemoveByPattern(CATEGORIES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productCategory); } - public virtual string GetCategoryPath(Product product, int? languageId, Func pathLookup, Action addPathToCache, Func categoryLookup, - ProductCategory prodCategory = null) + public virtual IEnumerable GetCategoryTrail(ICategoryNode node) { - if (product == null) - return string.Empty; + Guard.NotNull(node, nameof(node)); - pathLookup = pathLookup ?? ((i) => { return string.Empty; }); - categoryLookup = categoryLookup ?? ((i) => { return GetCategoryById(i); }); - addPathToCache = addPathToCache ?? ((i, val) => { }); + var treeNode = GetCategoryTree(node.Id, true); - var alreadyProcessedCategoryIds = new List(); - var path = new List(); + if (treeNode == null) + { + return Enumerable.Empty(); + } - var productCategory = prodCategory ?? GetProductCategoriesByProductId(product.Id).FirstOrDefault(); + return treeNode.Trail + .Where(x => !x.IsRoot) + //.TakeWhile(x => x.Value.Published) // TBD: (mc) do we need this? + .Select(x => x.Value); + } - if (productCategory != null && productCategory.Category != null) + public virtual string GetCategoryPath( + TreeNode treeNode, + int? languageId = null, + string aliasPattern = null, + string separator = " ") + { + Guard.NotNull(treeNode, nameof(treeNode)); + + var lookupKey = "Path.{0}.{1}.{2}".FormatInvariant(separator, languageId ?? 0, aliasPattern.HasValue()); + var cachedPath = treeNode.GetMetadata(lookupKey, false); + + if (cachedPath != null) { - string cached = pathLookup(productCategory.CategoryId); - if (cached.HasValue()) + return cachedPath; + } + + var trail = treeNode.Trail; + var sb = new StringBuilder(string.Empty, (trail.Count()) * 16); + + foreach (var node in trail) + { + if (!node.IsRoot) { - return cached; - } + var cat = node.Value; - var category = productCategory.Category; + var name = languageId.HasValue + ? cat.GetLocalized(n => n.Name, languageId.Value) + : cat.Name; - path.Add(languageId.HasValue ? category.GetLocalized(x => x.Name, languageId.Value) : category.Name); - alreadyProcessedCategoryIds.Add(category.Id); + sb.Append(name); - category = categoryLookup(category.ParentCategoryId); - while (category != null && !category.Deleted && category.Published && !alreadyProcessedCategoryIds.Contains(category.Id)) - { - path.Add(languageId.HasValue ? category.GetLocalized(x => x.Name, languageId.Value) : category.Name); - alreadyProcessedCategoryIds.Add(category.Id); - category = categoryLookup(category.ParentCategoryId); + if (aliasPattern.HasValue() && cat.Alias.HasValue()) + { + sb.Append(" "); + sb.Append(string.Format(aliasPattern, cat.Alias)); } - path.Reverse(); - string result = String.Join(" > ", path); - addPathToCache(productCategory.CategoryId, result); - return result; + if (node != treeNode) + { + // Is not self (trail end) + sb.Append(separator); + } + } } - return string.Empty; + var path = sb.ToString(); + treeNode.SetThreadMetadata(lookupKey, path); + return path; } - #endregion - } + public TreeNode GetCategoryTree(int rootCategoryId = 0, bool includeHidden = false, int storeId = 0) + { + var storeToken = QuerySettings.IgnoreMultiStore ? "0" : storeId.ToString(); + var rolesToken = QuerySettings.IgnoreAcl || includeHidden ? "0" : _workContext.CurrentCustomer.GetRolesIdent(); + var cacheKey = CATEGORY_TREE_KEY.FormatInvariant(includeHidden.ToString().ToLower(), rolesToken, storeToken); + + var root = _cache.Get(cacheKey, () => + { + // (Perf) don't fetch every field from db + var query = from x in BuildCategoriesQuery(showHidden: includeHidden, storeId: storeId) + orderby x.ParentCategoryId, x.DisplayOrder, x.Name + select new + { + x.Id, + x.ParentCategoryId, + x.Name, + x.ExternalLink, + x.Alias, + x.PictureId, + x.Published, + x.DisplayOrder, + x.UpdatedOnUtc, + x.BadgeText, + x.BadgeStyle, + x.LimitedToStores, + x.SubjectToAcl + }; + + var unsortedNodes = query.ToList().Select(x => new CategoryNode + { + Id = x.Id, + ParentCategoryId = x.ParentCategoryId, + Name = x.Name, + ExternalLink = x.ExternalLink, + Alias = x.Alias, + PictureId = x.PictureId, + Published = x.Published, + DisplayOrder = x.DisplayOrder, + UpdatedOnUtc = x.UpdatedOnUtc, + BadgeText = x.BadgeText, + BadgeStyle = x.BadgeStyle, + LimitedToStores = x.LimitedToStores, + SubjectToAcl = x.SubjectToAcl + }); + + var nodes = unsortedNodes.SortCategoryNodesForTree(0, true); + var curParent = new TreeNode(new CategoryNode { Name = "Home" }); + CategoryNode prevNode = null; + + foreach (var node in nodes) + { + // Determine parent + if (prevNode != null) + { + if (node.ParentCategoryId != curParent.Value.Id) + { + if (node.ParentCategoryId == prevNode.Id) + { + // level +1 + curParent = curParent.LastChild; + } + else + { + // level -x + while (!curParent.IsRoot) + { + if (curParent.Value.Id == node.ParentCategoryId) + { + break; + } + curParent = curParent.Parent; + } + } + } + } + + // add to parent + curParent.Append(node, node.Id); + + prevNode = node; + } + + return curParent.Root; + }, CategoryTreeCacheDuration); + + if (rootCategoryId > 0) + { + root = root.SelectNodeById(rootCategoryId); + } + + return root; + } + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryTemplateService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryTemplateService.cs index 9dc27bb2ee..c455ddc0c6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryTemplateService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryTemplateService.cs @@ -1,66 +1,31 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Events; namespace SmartStore.Services.Catalog { - /// - /// Category template service - /// public partial class CategoryTemplateService : ICategoryTemplateService { - - #region Fields - private readonly IRepository _categoryTemplateRepository; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - - #endregion - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Category template repository - /// Event published - public CategoryTemplateService(IRequestCache requestCache, - IRepository categoryTemplateRepository, IEventPublisher eventPublisher) + public CategoryTemplateService(IRepository categoryTemplateRepository, IEventPublisher eventPublisher) { - _requestCache = requestCache; _categoryTemplateRepository = categoryTemplateRepository; _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - /// - /// Delete category template - /// - /// Category template public virtual void DeleteCategoryTemplate(CategoryTemplate categoryTemplate) { if (categoryTemplate == null) throw new ArgumentNullException("categoryTemplate"); _categoryTemplateRepository.Delete(categoryTemplate); - - //event notification - _eventPublisher.EntityDeleted(categoryTemplate); } - /// - /// Gets all category templates - /// - /// Category templates public virtual IList GetAllCategoryTemplates() { var query = from pt in _categoryTemplateRepository.Table @@ -71,11 +36,6 @@ orderby pt.DisplayOrder return templates; } - /// - /// Gets a category template - /// - /// Category template identifier - /// Category template public virtual CategoryTemplate GetCategoryTemplateById(int categoryTemplateId) { if (categoryTemplateId == 0) @@ -84,36 +44,20 @@ public virtual CategoryTemplate GetCategoryTemplateById(int categoryTemplateId) return _categoryTemplateRepository.GetById(categoryTemplateId); } - /// - /// Inserts category template - /// - /// Category template public virtual void InsertCategoryTemplate(CategoryTemplate categoryTemplate) { if (categoryTemplate == null) throw new ArgumentNullException("categoryTemplate"); _categoryTemplateRepository.Insert(categoryTemplate); - - //event notification - _eventPublisher.EntityInserted(categoryTemplate); } - /// - /// Updates the category template - /// - /// Category template public virtual void UpdateCategoryTemplate(CategoryTemplate categoryTemplate) { if (categoryTemplate == null) throw new ArgumentNullException("categoryTemplate"); _categoryTemplateRepository.Update(categoryTemplate); - - //event notification - _eventPublisher.EntityUpdated(categoryTemplate); } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs new file mode 100644 index 0000000000..804584a665 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Collections; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Configuration; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Events; +using SmartStore.Core.Search; + +namespace SmartStore.Services.Catalog +{ + public enum CategoryTreeChangeReason + { + ElementCounts, + Data, + Localization, + StoreMapping, + Acl, + Hierarchy + } + + public class CategoryTreeChangedEvent + { + public CategoryTreeChangedEvent(CategoryTreeChangeReason reason) + { + Reason = reason; + } + + public CategoryTreeChangeReason Reason { get; private set; } + } + + public class CategoryTreeChangeHook : IDbSaveHook, IConsumer + { + private readonly ICommonServices _services; + private readonly ICategoryService _categoryService; + + private readonly bool[] _handledReasons = new bool[(int)CategoryTreeChangeReason.Hierarchy + 1]; + private bool _invalidated; + + // 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", "ExternalLink", "PictureId", "BadgeText", "BadgeStyle" }; + + private static readonly HashSet _candidateTypes = new HashSet + { + typeof(Product), + typeof(Category), + typeof(ProductCategory), + typeof(Setting), + typeof(Language), + typeof(LocalizedProperty), + typeof(StoreMapping), + typeof(AclRecord) + }; + + public CategoryTreeChangeHook(ICommonServices services, ICategoryService categoryService) + { + _services = services; + _categoryService = categoryService; + } + + public void OnBeforeSave(IHookedEntity entry) + { + if (_invalidated) + return; + + if (entry.InitialState != EntityState.Modified) + throw new NotSupportedException(); + + var cache = _services.Cache; + var entity = entry.Entity; + + if (entity is Product) + { + var modProps = _services.DbContext.GetModifiedProperties(entity); + var toxicPropNames = Product.GetVisibilityAffectingPropertyNames(); + if (modProps.Keys.Any(x => toxicPropNames.Contains(x))) + { + // No eviction, just notification + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + } + else if (entity is ProductCategory) + { + var modProps = _services.DbContext.GetModifiedProperties(entity); + if (modProps.ContainsKey("CategoryId")) + { + // No eviction, just notification + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + } + else if (entity is Category category) + { + var modProps = _services.DbContext.GetModifiedProperties(entity); + + if (modProps.Keys.Any(x => _h.Contains(x))) + { + // Hierarchy affecting properties has changed. Nuke each tree. + cache.RemoveByPattern(CategoryService.CATEGORY_TREE_PATTERN_KEY); + PublishEvent(CategoryTreeChangeReason.Hierarchy); + _invalidated = true; + } + else if (modProps.Keys.Any(x => _a.Contains(x))) + { + if (modProps.ContainsKey("LimitedToStores")) + { + // Don't nuke store agnostic trees + cache.RemoveByPattern(BuildCacheKeyPattern("*", "*", "[^0]*")); + PublishEvent(CategoryTreeChangeReason.StoreMapping); + } + if (modProps.ContainsKey("SubjectToAcl")) + { + // Don't nuke ACL agnostic trees + cache.RemoveByPattern(BuildCacheKeyPattern("*", "[^0]*", "*")); + PublishEvent(CategoryTreeChangeReason.Acl); + } + } + else if (modProps.Keys.Any(x => _d.Contains(x))) + { + // Only data has changed. Don't nuke trees, update corresponding cache entries instead. + var keys = cache.Keys(CategoryService.CATEGORY_TREE_PATTERN_KEY).ToArray(); + foreach (var key in keys) + { + var tree = cache.Get>(key); + if (tree != null) + { + var node = tree.SelectNodeById(entity.Id); + if (node != null) + { + var value = node.Value as CategoryNode; + if (value == null) + { + // Cannot update. Nuke tree. + cache.Remove(key); + } + else + { + value.Name = category.Name; + value.ExternalLink = category.ExternalLink; + value.Alias = category.Alias; + value.PictureId = category.PictureId; + value.BadgeText = category.BadgeText; + value.BadgeStyle = category.BadgeStyle; + + // Persist to cache store + cache.Put(key, tree, CategoryService.CategoryTreeCacheDuration); + } + } + } + } + + // Publish event only once + PublishEvent(CategoryTreeChangeReason.Data); + } + } + else + { + throw new NotSupportedException(); + } + } + + public void OnAfterSave(IHookedEntity entry) + { + if (_invalidated) + return; + + if (!_candidateTypes.Contains(entry.EntityType)) + throw new NotSupportedException(); + + // INFO: Acl & StoreMapping affect element counts + + var cache = _services.Cache; + var isNewOrDeleted = entry.InitialState == EntityState.Added || entry.InitialState == EntityState.Deleted; + var entity = entry.Entity; + + if (entity is Product) + { + // INFO: 'Modified' case already handled in 'OnBeforeSave()' + if (entry.InitialState == EntityState.Deleted || (entry.InitialState == EntityState.Added && ((Product)entity).Published)) + { + // No eviction, just notification, but for PUBLISHED products only + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + } + else if (entity is ProductCategory && isNewOrDeleted) + { + // INFO: 'Modified' case already handled in 'OnBeforeSave()' + // New or deleted product category mappings affect counts + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + else if (entity is Category && isNewOrDeleted) + { + // 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) + { + var name = (entity as Setting).Name.ToLowerInvariant(); + if (name == "catalogsettings.showcategoryproductnumber" || name == "catalogsettings.showcategoryproductnumberincludingsubcategories") + { + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + } + else if (entity is Language && entry.InitialState == EntityState.Deleted) + { + PublishEvent(CategoryTreeChangeReason.Localization); + } + else if (entity is LocalizedProperty) + { + var lp = entity as LocalizedProperty; + var key = lp.LocaleKey; + if (lp.LocaleKeyGroup == "Category" && (key == "Name" || key == "BadgeText")) + { + PublishEvent(CategoryTreeChangeReason.Localization); + } + } + else if (entity is StoreMapping) + { + var stm = entity as StoreMapping; + if (stm.EntityName == "Product") + { + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + else if (stm.EntityName == "Category") + { + // Don't nuke store agnostic trees + cache.RemoveByPattern(BuildCacheKeyPattern("*", "*", "[^0]*")); + PublishEvent(CategoryTreeChangeReason.StoreMapping); + } + } + else if (entity is AclRecord) + { + var acl = entity as AclRecord; + if (!acl.IsIdle) + { + if (acl.EntityName == "Product") + { + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + else if (acl.EntityName == "Category") + { + // Don't nuke ACL agnostic trees + cache.RemoveByPattern(BuildCacheKeyPattern("*", "[^0]*", "*")); + PublishEvent(CategoryTreeChangeReason.Acl); + } + } + } + } + + public void HandleEvent(IndexingCompletedEvent message) + { + if (message.IndexInfo.IsModified) + { + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + } + + private void PublishEvent(CategoryTreeChangeReason reason) + { + if (_handledReasons[(int)reason] == false) + { + _services.EventPublisher.Publish(new CategoryTreeChangedEvent(reason)); + _handledReasons[(int)reason] = true; + } + } + + private string BuildCacheKeyPattern(string includeHiddenToken = "*", string rolesToken = "*", string storeToken = "*") + { + return CategoryService.CATEGORY_TREE_KEY.FormatInvariant(includeHiddenToken, rolesToken, storeToken); + } + + public void OnBeforeSaveCompleted() + { + } + + public void OnAfterSaveCompleted() + { + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs b/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs index 8f4085d2c9..6368d7dae5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs @@ -1,8 +1,9 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Web; using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Search; namespace SmartStore.Services.Catalog { @@ -11,57 +12,44 @@ namespace SmartStore.Services.Catalog /// public partial class CompareProductsService : ICompareProductsService { - #region Constants - private const string COMPARE_PRODUCTS_COOKIE_NAME = "sm.CompareProducts"; - #endregion - - #region Fields - private readonly HttpContextBase _httpContext; private readonly IProductService _productService; + private readonly ICatalogSearchService _catalogSearchService; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// HTTP context - /// Product service - public CompareProductsService(HttpContextBase httpContext, IProductService productService) + public CompareProductsService( + HttpContextBase httpContext, + IProductService productService, + ICatalogSearchService catalogSearchService) { - this._httpContext = httpContext; - this._productService = productService; + _httpContext = httpContext; + _productService = productService; + _catalogSearchService = catalogSearchService; } - #endregion - #region Utilities /// /// 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 @@ -88,30 +76,26 @@ 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() { - var searchContext = new ProductSearchContext() - { - ProductIds = GetComparedProductIds() - }; - - if (searchContext.ProductIds.Count <= 0) + var productIds = GetComparedProductIds(); + if (productIds.Count == 0) return 0; - var query = _productService.PrepareProductSearchQuery(searchContext); + var searchQuery = new CatalogSearchQuery() + .VisibleOnly() + .WithProductIds(productIds.ToArray()) + .BuildHits(false); - return query.Count(); + var result = _catalogSearchService.Search(searchQuery); + return result.TotalHitsCount; } diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index f786bf9c78..15088a1c1f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -1,422 +1,630 @@ using System; using System.Collections.Generic; +using System.Data.Entity; +using System.Globalization; +using System.Linq; +using SmartStore.Core.Logging; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Localization; using SmartStore.Services.Localization; using SmartStore.Services.Media; +using SmartStore.Services.Search; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Core.Domain.Seo; namespace SmartStore.Services.Catalog { - /// - /// Copy Product service - /// public partial class CopyProductService : ICopyProductService { - #region Fields - - private readonly IProductService _productService; + private readonly IRepository _productRepository; + private readonly IRepository _relatedProductRepository; + private readonly IRepository _crossSellProductRepository; + private readonly ICommonServices _services; + private readonly IProductService _productService; private readonly IProductAttributeService _productAttributeService; private readonly ILanguageService _languageService; private readonly ILocalizedEntityService _localizedEntityService; private readonly IPictureService _pictureService; - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly ISpecificationAttributeService _specificationAttributeService; private readonly IDownloadService _downloadService; private readonly IProductAttributeParser _productAttributeParser; private readonly IUrlRecordService _urlRecordService; private readonly IStoreMappingService _storeMappingService; - private readonly ILocalizationService _localizationService; - - #endregion - - #region Ctor - - public CopyProductService(IProductService productService, - IProductAttributeService productAttributeService, ILanguageService languageService, - ILocalizedEntityService localizedEntityService, IPictureService pictureService, - ICategoryService categoryService, IManufacturerService manufacturerService, - ISpecificationAttributeService specificationAttributeService, IDownloadService downloadService, - IProductAttributeParser productAttributeParser, IUrlRecordService urlRecordService, + private readonly ICatalogSearchService _catalogSearchService; + private readonly SeoSettings _seoSettings; + + public CopyProductService( + IRepository productRepository, + IRepository relatedProductRepository, + IRepository crossSellProductRepository, + ICommonServices services, + IProductService productService, + IProductAttributeService productAttributeService, + ILanguageService languageService, + ILocalizedEntityService localizedEntityService, + IPictureService pictureService, + IDownloadService downloadService, + IProductAttributeParser productAttributeParser, + IUrlRecordService urlRecordService, IStoreMappingService storeMappingService, - ILocalizationService localizationService) + ICatalogSearchService catalogSearchService, + SeoSettings seoSettings) { - this._productService = productService; - this._productAttributeService = productAttributeService; - this._languageService = languageService; - this._localizedEntityService = localizedEntityService; - this._pictureService = pictureService; - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._specificationAttributeService = specificationAttributeService; - this._downloadService = downloadService; - this._productAttributeParser = productAttributeParser; - this._urlRecordService = urlRecordService; - this._storeMappingService = storeMappingService; - this._localizationService = localizationService; + _productRepository = productRepository; + _relatedProductRepository = relatedProductRepository; + _crossSellProductRepository = crossSellProductRepository; + _services = services; + _productService = productService; + _productAttributeService = productAttributeService; + _languageService = languageService; + _localizedEntityService = localizedEntityService; + _pictureService = pictureService; + _downloadService = downloadService; + _productAttributeParser = productAttributeParser; + _urlRecordService = urlRecordService; + _storeMappingService = storeMappingService; + _catalogSearchService = catalogSearchService; + _seoSettings = seoSettings; + + T = NullLocalizer.Instance; } - #endregion - - #region Methods + public Localizer T { get; set; } - /// - /// Create a copy of product with all depended data - /// - /// The product - /// The name of product duplicate - /// A value indicating whether the product duplicate should be published - /// A value indicating whether the product images should be copied - /// A value indicating whether the copy associated products - /// Product entity - public virtual Product CopyProduct(Product product, string newName, bool isPublished, bool copyImages, bool copyAssociatedProducts = true) + public virtual Product CopyProduct( + Product product, + string newName, + bool isPublished, + bool copyImages, + bool copyAssociatedProducts = true) { - if (product == null) - throw new ArgumentNullException("product"); - - if (String.IsNullOrEmpty(newName)) - throw new ArgumentException("Product name is required"); + Guard.NotNull(product, nameof(product)); + Guard.NotEmpty(newName, nameof(newName)); - Product productCopy = null; - var utcNow = DateTime.UtcNow; - - // product download & sample download - int downloadId = 0; - int? sampleDownloadId = null; - - if (product.IsDownload) + using (_services.Chronometer.Step("Copy product " + product.Id)) { - var download = _downloadService.GetDownloadById(product.DownloadId); - if (download != null) + Product clone = null; + var utcNow = DateTime.UtcNow; + var languages = _languageService.GetAllLanguages(true); + + // Media stuff + int? sampleDownloadId = null; + var clonedPictures = new Dictionary(); // Key = former ID, Value = cloned picture + + using (var scope = new DbContextScope(ctx: _productRepository.Context, + autoCommit: false, + autoDetectChanges: false, + proxyCreation: true, + validateOnSave: false, + forceNoTracking: true, + hooksEnabled: false)) { - var downloadCopy = new Download + if (product.HasSampleDownload) + { + var sampleDownload = _downloadService.GetDownloadById((int)product.SampleDownloadId); + var sampleDownloadClone = CopyDownload(sampleDownload); + sampleDownloadId = sampleDownloadClone.Id; + } + + if (copyImages) { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = download.UseDownloadUrl, - DownloadUrl = download.DownloadUrl, - DownloadBinary = download.DownloadBinary, - ContentType = download.ContentType, - Filename = download.Filename, - Extension = download.Extension, - IsNew = download.IsNew, + clonedPictures = CopyPictures(product, newName); + } + + // Product + clone = new Product + { + ProductTypeId = product.ProductTypeId, + ParentGroupedProductId = product.ParentGroupedProductId, + VisibleIndividually = product.VisibleIndividually, + Name = newName, + ShortDescription = product.ShortDescription, + FullDescription = product.FullDescription, + ProductTemplateId = product.ProductTemplateId, + AdminComment = product.AdminComment, + ShowOnHomePage = product.ShowOnHomePage, + HomePageDisplayOrder = product.HomePageDisplayOrder, + MetaKeywords = product.MetaKeywords, + MetaDescription = product.MetaDescription, + MetaTitle = product.MetaTitle, + AllowCustomerReviews = product.AllowCustomerReviews, + LimitedToStores = product.LimitedToStores, + Sku = product.Sku, + ManufacturerPartNumber = product.ManufacturerPartNumber, + Gtin = product.Gtin, + IsGiftCard = product.IsGiftCard, + GiftCardType = product.GiftCardType, + RequireOtherProducts = product.RequireOtherProducts, + RequiredProductIds = product.RequiredProductIds, + AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts, + IsDownload = product.IsDownload, + UnlimitedDownloads = product.UnlimitedDownloads, + MaxNumberOfDownloads = product.MaxNumberOfDownloads, + DownloadExpirationDays = product.DownloadExpirationDays, + DownloadActivationType = product.DownloadActivationType, + HasSampleDownload = product.HasSampleDownload, + SampleDownloadId = sampleDownloadId, + HasUserAgreement = product.HasUserAgreement, + UserAgreementText = product.UserAgreementText, + IsRecurring = product.IsRecurring, + RecurringCycleLength = product.RecurringCycleLength, + RecurringCyclePeriod = product.RecurringCyclePeriod, + RecurringTotalCycles = product.RecurringTotalCycles, + IsShipEnabled = product.IsShipEnabled, + IsFreeShipping = product.IsFreeShipping, + AdditionalShippingCharge = product.AdditionalShippingCharge, + IsEsd = product.IsEsd, + IsTaxExempt = product.IsTaxExempt, + TaxCategoryId = product.TaxCategoryId, + ManageInventoryMethod = product.ManageInventoryMethod, + StockQuantity = product.StockQuantity, + DisplayStockAvailability = product.DisplayStockAvailability, + DisplayStockQuantity = product.DisplayStockQuantity, + MinStockQuantity = product.MinStockQuantity, + LowStockActivityId = product.LowStockActivityId, + NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow, + BackorderMode = product.BackorderMode, + AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions, + OrderMinimumQuantity = product.OrderMinimumQuantity, + OrderMaximumQuantity = product.OrderMaximumQuantity, + QuantityStep = product.QuantityStep, + QuantiyControlType = product.QuantiyControlType, + HideQuantityControl = product.HideQuantityControl, + AllowedQuantities = product.AllowedQuantities, + DisableBuyButton = product.DisableBuyButton, + DisableWishlistButton = product.DisableWishlistButton, + AvailableForPreOrder = product.AvailableForPreOrder, + CallForPrice = product.CallForPrice, + Price = product.Price, + OldPrice = product.OldPrice, + ProductCost = product.ProductCost, + SpecialPrice = product.SpecialPrice, + SpecialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc, + SpecialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc, + CustomerEntersPrice = product.CustomerEntersPrice, + MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice, + MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice, + LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice, + Weight = product.Weight, + Length = product.Length, + Width = product.Width, + Height = product.Height, + AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc, + AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc, + DisplayOrder = product.DisplayOrder, + Published = isPublished, + Deleted = product.Deleted, + IsSystemProduct = product.IsSystemProduct, + DeliveryTimeId = product.DeliveryTimeId, + QuantityUnitId = product.QuantityUnitId, + BasePriceEnabled = product.BasePriceEnabled, + BasePriceMeasureUnit = product.BasePriceMeasureUnit, + BasePriceAmount = product.BasePriceAmount, + BasePriceBaseAmount = product.BasePriceBaseAmount, + BundleTitleText = product.BundleTitleText, + BundlePerItemShipping = product.BundlePerItemShipping, + BundlePerItemPricing = product.BundlePerItemPricing, + BundlePerItemShoppingCart = product.BundlePerItemShoppingCart, + CustomsTariffNumber = product.CustomsTariffNumber, + CountryOfOriginId = product.CountryOfOriginId, + CreatedOnUtc = utcNow, + UpdatedOnUtc = utcNow }; - _downloadService.InsertDownload(downloadCopy); - downloadId = downloadCopy.Id; - } + // Category mappings + foreach (var pc in product.ProductCategories) + { + clone.ProductCategories.Add(new ProductCategory + { + CategoryId = pc.CategoryId, + IsFeaturedProduct = pc.IsFeaturedProduct, + DisplayOrder = pc.DisplayOrder + }); + } - if (product.HasSampleDownload) - { - var sampleDownload = _downloadService.GetDownloadById(product.SampleDownloadId.GetValueOrDefault()); - if (sampleDownload != null) + // Manufacturer mappings + foreach (var pm in product.ProductManufacturers) { - var sampleDownloadCopy = new Download + clone.ProductManufacturers.Add(new ProductManufacturer { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = sampleDownload.UseDownloadUrl, - DownloadUrl = sampleDownload.DownloadUrl, - DownloadBinary = sampleDownload.DownloadBinary, - ContentType = sampleDownload.ContentType, - Filename = sampleDownload.Filename, - Extension = sampleDownload.Extension, - IsNew = sampleDownload.IsNew - }; - - _downloadService.InsertDownload(sampleDownloadCopy); - sampleDownloadId = sampleDownloadCopy.Id; + ManufacturerId = pm.ManufacturerId, + IsFeaturedProduct = pm.IsFeaturedProduct, + DisplayOrder = pm.DisplayOrder + }); } - } - } - // product - productCopy = new Product - { - ProductTypeId = product.ProductTypeId, - ParentGroupedProductId = product.ParentGroupedProductId, - VisibleIndividually = product.VisibleIndividually, - Name = newName, - ShortDescription = product.ShortDescription, - FullDescription = product.FullDescription, - ProductTemplateId = product.ProductTemplateId, - AdminComment = product.AdminComment, - ShowOnHomePage = product.ShowOnHomePage, - HomePageDisplayOrder = product.HomePageDisplayOrder, - MetaKeywords = product.MetaKeywords, - MetaDescription = product.MetaDescription, - MetaTitle = product.MetaTitle, - AllowCustomerReviews = product.AllowCustomerReviews, - LimitedToStores = product.LimitedToStores, - Sku = product.Sku, - ManufacturerPartNumber = product.ManufacturerPartNumber, - Gtin = product.Gtin, - IsGiftCard = product.IsGiftCard, - GiftCardType = product.GiftCardType, - RequireOtherProducts = product.RequireOtherProducts, - RequiredProductIds = product.RequiredProductIds, - AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts, - IsDownload = product.IsDownload, - DownloadId = downloadId, - UnlimitedDownloads = product.UnlimitedDownloads, - MaxNumberOfDownloads = product.MaxNumberOfDownloads, - DownloadExpirationDays = product.DownloadExpirationDays, - DownloadActivationType = product.DownloadActivationType, - HasSampleDownload = product.HasSampleDownload, - SampleDownloadId = sampleDownloadId, - HasUserAgreement = product.HasUserAgreement, - UserAgreementText = product.UserAgreementText, - IsRecurring = product.IsRecurring, - RecurringCycleLength = product.RecurringCycleLength, - RecurringCyclePeriod = product.RecurringCyclePeriod, - RecurringTotalCycles = product.RecurringTotalCycles, - IsShipEnabled = product.IsShipEnabled, - IsFreeShipping = product.IsFreeShipping, - AdditionalShippingCharge = product.AdditionalShippingCharge, - IsEsd = product.IsEsd, - IsTaxExempt = product.IsTaxExempt, - TaxCategoryId = product.TaxCategoryId, - ManageInventoryMethod = product.ManageInventoryMethod, - StockQuantity = product.StockQuantity, - DisplayStockAvailability = product.DisplayStockAvailability, - DisplayStockQuantity = product.DisplayStockQuantity, - MinStockQuantity = product.MinStockQuantity, - LowStockActivityId = product.LowStockActivityId, - NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow, - BackorderMode = product.BackorderMode, - AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions, - OrderMinimumQuantity = product.OrderMinimumQuantity, - OrderMaximumQuantity = product.OrderMaximumQuantity, - AllowedQuantities = product.AllowedQuantities, - DisableBuyButton = product.DisableBuyButton, - DisableWishlistButton = product.DisableWishlistButton, - AvailableForPreOrder = product.AvailableForPreOrder, - CallForPrice = product.CallForPrice, - Price = product.Price, - OldPrice = product.OldPrice, - ProductCost = product.ProductCost, - SpecialPrice = product.SpecialPrice, - SpecialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc, - SpecialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc, - CustomerEntersPrice = product.CustomerEntersPrice, - MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice, - MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice, - LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice, - Weight = product.Weight, - Length = product.Length, - Width = product.Width, - Height = product.Height, - AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc, - AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc, - DisplayOrder = product.DisplayOrder, - Published = isPublished, - Deleted = product.Deleted, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow, - DeliveryTimeId = product.DeliveryTimeId, - QuantityUnitId = product.QuantityUnitId, - BasePriceEnabled = product.BasePriceEnabled, - BasePriceMeasureUnit = product.BasePriceMeasureUnit, - BasePriceAmount = product.BasePriceAmount, - BasePriceBaseAmount = product.BasePriceBaseAmount, - BundleTitleText = product.BundleTitleText, - BundlePerItemShipping = product.BundlePerItemShipping, - BundlePerItemPricing = product.BundlePerItemPricing, - BundlePerItemShoppingCart = product.BundlePerItemShoppingCart - }; + // Picture mappings + if (copyImages) + { + foreach (var pp in product.ProductPictures) + { + var pictureClone = clonedPictures.Get(pp.PictureId); + if (pictureClone != null) + { + clone.ProductPictures.Add(new ProductPicture + { + PictureId = pictureClone.Id, + DisplayOrder = pp.DisplayOrder + }); + } + } + } + + // Product specifications + foreach (var psa in product.ProductSpecificationAttributes) + { + clone.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + SpecificationAttributeOptionId = psa.SpecificationAttributeOptionId, + AllowFiltering = psa.AllowFiltering, + ShowOnProductPage = psa.ShowOnProductPage, + DisplayOrder = psa.DisplayOrder + }); + } + + // Tier prices + foreach (var tp in product.TierPrices) + { + clone.TierPrices.Add(new TierPrice + { + StoreId = tp.StoreId, + CustomerRoleId = tp.CustomerRoleId, + Quantity = tp.Quantity, + Price = tp.Price, + CalculationMethod = tp.CalculationMethod + }); + } - _productService.InsertProduct(productCopy); + // Discount mapping + foreach (var discount in product.AppliedDiscounts) + { + clone.AppliedDiscounts.Add(discount); + } - //search engine name - _urlRecordService.SaveSlug(productCopy, productCopy.ValidateSeName("", productCopy.Name, true), 0); + // Tags + foreach (var tag in product.ProductTags) + { + clone.ProductTags.Add(tag); + } - var languages = _languageService.GetAllLanguages(true); + // >>>>>>> Put clone to db (from here on we need the product clone's ID) + _productRepository.Insert(clone); + Commit(); - //localization - foreach (var lang in languages) - { - var name = product.GetLocalized(x => x.Name, lang.Id, false, false); - if (!String.IsNullOrEmpty(name)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.Name, name, lang.Id); + // Related products mapping + foreach (var rp in _productService.GetRelatedProductsByProductId1(product.Id, true)) + { + _relatedProductRepository.Insert(new RelatedProduct + { + ProductId1 = clone.Id, + ProductId2 = rp.ProductId2, + DisplayOrder = rp.DisplayOrder + }); + } - var shortDescription = product.GetLocalized(x => x.ShortDescription, lang.Id, false, false); - if (!String.IsNullOrEmpty(shortDescription)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.ShortDescription, shortDescription, lang.Id); + // CrossSell products mapping + foreach (var csp in _productService.GetCrossSellProductsByProductId1(product.Id, true)) + { + _crossSellProductRepository.Insert(new CrossSellProduct + { + ProductId1 = clone.Id, + ProductId2 = csp.ProductId2 + }); + } + + // Store mapping + var selectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(product); + foreach (var id in selectedStoreIds) + { + _storeMappingService.InsertStoreMapping(clone, id); + } - var fullDescription = product.GetLocalized(x => x.FullDescription, lang.Id, false, false); - if (!String.IsNullOrEmpty(fullDescription)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.FullDescription, fullDescription, lang.Id); + // SEO + ProcessSlug(clone); - var metaKeywords = product.GetLocalized(x => x.MetaKeywords, lang.Id, false, false); - if (!String.IsNullOrEmpty(metaKeywords)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaKeywords, metaKeywords, lang.Id); + // Localization + ProcessLocalization(product, clone, languages); - var metaDescription = product.GetLocalized(x => x.MetaDescription, lang.Id, false, false); - if (!String.IsNullOrEmpty(metaDescription)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaDescription, metaDescription, lang.Id); + // Attr stuff ... + ProcessAttributes(product, clone, newName, copyImages, clonedPictures, languages); - var metaTitle = product.GetLocalized(x => x.MetaTitle, lang.Id, false, false); - if (!String.IsNullOrEmpty(metaTitle)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaTitle, metaTitle, lang.Id); + // update computed properties + clone.HasTierPrices = clone.TierPrices.Count > 0; + clone.HasDiscountsApplied = clone.AppliedDiscounts.Count > 0; + clone.LowestAttributeCombinationPrice = _productAttributeService.GetLowestCombinationPrice(clone.Id); + clone.MainPictureId = clone.ProductPictures.OrderBy(x => x.DisplayOrder).Select(x => x.PictureId).FirstOrDefault(); - var bundleTitleText = product.GetLocalized(x => x.BundleTitleText, lang.Id, false, false); - if (!String.IsNullOrEmpty(bundleTitleText)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.BundleTitleText, bundleTitleText, lang.Id); + // Associated products + if (copyAssociatedProducts && product.ProductType != ProductType.BundledProduct) + { + ProcessAssociatedProducts(product, clone, isPublished, copyImages); + } + + // Bundle items + ProcessBundleItems(product, clone); - //search engine name - _urlRecordService.SaveSlug(productCopy, productCopy.ValidateSeName("", name, false), lang.Id); + // Downloads + CopyDownloads(product.Id, clone.Id, "Product"); + + // >>>>>>> Our final commit + Commit(); + } - } + return clone; + } + } - // product pictures - if (copyImages) + private void Commit() + { + _services.DbContext.SaveChanges(); + } + + private Dictionary CopyDownloads(int productId, int cloneId, string entityName = "") + { + var downloads = _downloadService.GetDownloadsFor(productId, entityName); + var clonedDownloads = new Dictionary(); + + if (!downloads.Any()) + { + return null; + } + + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) { - foreach (var productPicture in product.ProductPictures) + foreach (var download in downloads) { - var picture = productPicture.Picture; - var pictureCopy = _pictureService.InsertPicture( - _pictureService.LoadPictureBinary(picture), - picture.MimeType, - _pictureService.GetPictureSeName(newName), - true, - false, - false); - - _productService.InsertProductPicture(new ProductPicture - { - ProductId = productCopy.Id, - PictureId = pictureCopy.Id, - DisplayOrder = productPicture.DisplayOrder - }); + download.EntityId = cloneId; + var clone = CopyDownload(download); + clonedDownloads[clone.Id] = clone; } - } + } + + return clonedDownloads; + } - // product <-> categories mappings - foreach (var productCategory in product.ProductCategories) + private Download CopyDownload(Download download) + { + var clone = new Download { - var productCategoryCopy = new ProductCategory - { - ProductId = productCopy.Id, - CategoryId = productCategory.CategoryId, - IsFeaturedProduct = productCategory.IsFeaturedProduct, - DisplayOrder = productCategory.DisplayOrder - }; + 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 + }; - _categoryService.InsertProductCategory(productCategoryCopy); - } + _downloadService.InsertDownload(clone, download.MediaStorage?.Data); - // product <-> manufacturers mappings - foreach (var productManufacturers in product.ProductManufacturers) - { - var productManufacturerCopy = new ProductManufacturer - { - ProductId = productCopy.Id, - ManufacturerId = productManufacturers.ManufacturerId, - IsFeaturedProduct = productManufacturers.IsFeaturedProduct, - DisplayOrder = productManufacturers.DisplayOrder - }; + return clone; + } - _manufacturerService.InsertProductManufacturer(productManufacturerCopy); - } + private Dictionary CopyPictures(Product product, string newProductName) + { + var clonedPictures = new Dictionary(); + var seoFilename = _pictureService.GetPictureSeName(newProductName); - // product <-> releated products mappings - foreach (var relatedProduct in _productService.GetRelatedProductsByProductId1(product.Id, true)) - { - _productService.InsertRelatedProduct(new RelatedProduct - { - ProductId1 = productCopy.Id, - ProductId2 = relatedProduct.ProductId2, - DisplayOrder = relatedProduct.DisplayOrder - }); - } - - // product <-> cross sells mappings - foreach (var csProduct in _productService.GetCrossSellProductsByProductId1(product.Id, true)) - { - _productService.InsertCrossSellProduct(new CrossSellProduct - { - ProductId1 = productCopy.Id, - ProductId2 = csProduct.ProductId2, - }); - } + foreach (var pp in product.ProductPictures) + { + var clone = CopyPicture(pp.Picture, seoFilename); + clonedPictures[pp.PictureId] = clone; + } - // product specifications - foreach (var productSpecificationAttribute in product.ProductSpecificationAttributes) - { - var psaCopy = new ProductSpecificationAttribute - { - ProductId = productCopy.Id, - SpecificationAttributeOptionId = productSpecificationAttribute.SpecificationAttributeOptionId, - AllowFiltering = productSpecificationAttribute.AllowFiltering, - ShowOnProductPage = productSpecificationAttribute.ShowOnProductPage, - DisplayOrder = productSpecificationAttribute.DisplayOrder - }; - - _specificationAttributeService.InsertProductSpecificationAttribute(psaCopy); - } - - //store mapping - var selectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(product); - foreach (var id in selectedStoreIds) + return clonedPictures; + } + + private Picture CopyPicture(Picture picture, string seoFilename) + { + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) + { + var buffer = _pictureService.LoadPictureBinary(picture); + + var clone = _pictureService.InsertPicture( + buffer, + picture.MimeType, + seoFilename, + true, + picture.Width ?? 0, + picture.Height ?? 0, + false); + + return clone; + } + } + + private void ProcessSlug(Product clone) + { + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) { - _storeMappingService.InsertStoreMapping(productCopy, id); + var slug = clone.ValidateSeName("", clone.Name, true, _urlRecordService, _seoSettings); + _urlRecordService.SaveSlug(clone, slug, 0); } + } - // product <-> attributes mappings - var associatedAttributes = new Dictionary(); - var associatedAttributeValues = new Dictionary(); + private void ProcessLocalization(Product product, Product clone, IEnumerable languages) + { + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) + { + foreach (var lang in languages) + { + 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, false, false); + if (!String.IsNullOrEmpty(shortDescription)) + _localizedEntityService.SaveLocalizedValue(clone, x => x.ShortDescription, shortDescription, lang.Id); + + 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, false, false); + if (!String.IsNullOrEmpty(metaKeywords)) + _localizedEntityService.SaveLocalizedValue(clone, x => x.MetaKeywords, metaKeywords, lang.Id); + + 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, false, false); + if (!String.IsNullOrEmpty(metaTitle)) + _localizedEntityService.SaveLocalizedValue(clone, x => x.MetaTitle, metaTitle, lang.Id); + + 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. + var slug = clone.ValidateSeName("", name, false, _urlRecordService, _seoSettings, lang.Id); + _urlRecordService.SaveSlug(clone, slug, lang.Id); + } + } + } - foreach (var productVariantAttribute in _productAttributeService.GetProductVariantAttributesByProductId(product.Id)) + private void ProcessAssociatedProducts(Product product, Product clone, bool isPublished, bool copyImages) + { + var searchQuery = new CatalogSearchQuery().HasParentGroupedProduct(product.Id); + + var query = _catalogSearchService.PrepareQuery(searchQuery); + var associatedProducts = query.OrderBy(p => p.DisplayOrder).ToList(); + + foreach (var associatedProduct in associatedProducts) + { + var associatedProductCopy = CopyProduct(associatedProduct, T("Admin.Common.CopyOf", associatedProduct.Name), isPublished, copyImages, false); + associatedProductCopy.ParentGroupedProductId = clone.Id; + } + } + + private void ProcessBundleItems(Product product, Product clone) + { + var bundledItems = _productService.GetBundleItems(product.Id, true); + + foreach (var bundleItem in bundledItems) { - var productVariantAttributeCopy = new ProductVariantAttribute + var newBundleItem = bundleItem.Item.Clone(); + newBundleItem.BundleProductId = clone.Id; + + _productService.InsertBundleItem(newBundleItem); + + foreach (var itemFilter in bundleItem.Item.AttributeFilters) { - ProductId = productCopy.Id, - ProductAttributeId = productVariantAttribute.ProductAttributeId, - TextPrompt = productVariantAttribute.TextPrompt, - IsRequired = productVariantAttribute.IsRequired, - AttributeControlTypeId = productVariantAttribute.AttributeControlTypeId, - DisplayOrder = productVariantAttribute.DisplayOrder + var newItemFilter = itemFilter.Clone(); + newItemFilter.BundleItemId = newBundleItem.Id; + + _productAttributeService.InsertProductBundleItemAttributeFilter(newItemFilter); + } + } + } + + private void ProcessAttributes( + Product product, + Product clone, + string newName, + bool copyImages, + Dictionary clonedPictures, + IEnumerable languages) + { + // Former attribute id > clone + var pvaMap = new Dictionary(); + + // Former attribute value id > clone + var pvavMap = new Dictionary(); + + var pictureSeName = _pictureService.GetPictureSeName(newName); + + foreach (var pva in product.ProductVariantAttributes) + { + var pvaClone = new ProductVariantAttribute + { + ProductAttributeId = pva.ProductAttributeId, + TextPrompt = pva.TextPrompt, + IsRequired = pva.IsRequired, + AttributeControlTypeId = pva.AttributeControlTypeId, + DisplayOrder = pva.DisplayOrder }; - _productAttributeService.InsertProductVariantAttribute(productVariantAttributeCopy); - //save associated value (used for combinations copying) - associatedAttributes.Add(productVariantAttribute.Id, productVariantAttributeCopy.Id); + clone.ProductVariantAttributes.Add(pvaClone); - // product variant attribute values - var productVariantAttributeValues = _productAttributeService.GetProductVariantAttributeValues(productVariantAttribute.Id); + // Save associated value (used for combinations copying) + pvaMap[pva.Id] = pvaClone; - foreach (var productVariantAttributeValue in productVariantAttributeValues) + // Product variant attribute values + foreach (var pvav in pva.ProductVariantAttributeValues) { - var pvavCopy = new ProductVariantAttributeValue + var pvavClone = new ProductVariantAttributeValue { - ProductVariantAttributeId = productVariantAttributeCopy.Id, - Name = productVariantAttributeValue.Name, - ColorSquaresRgb = productVariantAttributeValue.ColorSquaresRgb, - PriceAdjustment = productVariantAttributeValue.PriceAdjustment, - WeightAdjustment = productVariantAttributeValue.WeightAdjustment, - IsPreSelected = productVariantAttributeValue.IsPreSelected, - DisplayOrder = productVariantAttributeValue.DisplayOrder, - ValueTypeId = productVariantAttributeValue.ValueTypeId, - LinkedProductId = productVariantAttributeValue.LinkedProductId, - Quantity = productVariantAttributeValue.Quantity, + Name = pvav.Name, + Color = pvav.Color, + PriceAdjustment = pvav.PriceAdjustment, + WeightAdjustment = pvav.WeightAdjustment, + IsPreSelected = pvav.IsPreSelected, + DisplayOrder = pvav.DisplayOrder, + ValueTypeId = pvav.ValueTypeId, + LinkedProductId = pvav.LinkedProductId, + Quantity = pvav.Quantity, + PictureId = copyImages ? pvav.PictureId : 0 // we'll clone this later }; - _productAttributeService.InsertProductVariantAttributeValue(pvavCopy); + pvaClone.ProductVariantAttributeValues.Add(pvavClone); + + // Save associated value (used for combinations copying) + pvavMap.Add(pvav.Id, pvavClone); + } + } + + // >>>>>> Commit + Commit(); + + // Attribute value localization + foreach (var pvav in product.ProductVariantAttributes.SelectMany(x => x.ProductVariantAttributeValues).ToArray()) + { + foreach (var lang in languages) + { + var name = pvav.GetLocalized(x => x.Name, lang, false, false); + if (!String.IsNullOrEmpty(name)) + { + var pvavClone = pvavMap.Get(pvav.Id); + if (pvavClone != null) + { + _localizedEntityService.SaveLocalizedValue(pvavClone, x => x.Name, name, lang.Id); + } + } + } + } - //save associated value (used for combinations copying) - associatedAttributeValues.Add(productVariantAttributeValue.Id, pvavCopy.Id); + // Clone attribute value images + if (copyImages) + { + // Reduce value set to those with assigned pictures + var allValueClonesWithPictures = pvavMap.Values.Where(x => x.PictureId > 0).ToArray(); + // Get those pictures for cloning + var allPictures = _pictureService.GetPicturesByIds(allValueClonesWithPictures.Select(x => x.PictureId).ToArray(), true); - //localization - foreach (var lang in languages) + foreach (var pvavClone in allValueClonesWithPictures) + { + var picture = allPictures.FirstOrDefault(x => x.Id == pvavClone.PictureId); + if (picture != null) { - var name = productVariantAttributeValue.GetLocalized(x => x.Name, lang.Id, false, false); - if (!String.IsNullOrEmpty(name)) - _localizedEntityService.SaveLocalizedValue(pvavCopy, x => x.Name, name, lang.Id); + var pictureClone = CopyPicture(picture, pictureSeName); + clonedPictures[pvavClone.PictureId] = pictureClone; + pvavClone.PictureId = pictureClone.Id; } } } + // >>>>>> Commit attributes & values + Commit(); + // attribute combinations using (var scope = new DbContextScope(lazyLoading: false, forceNoTracking: false)) { @@ -425,56 +633,63 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli foreach (var combination in product.ProductVariantAttributeCombinations) { - //generate new AttributesXml according to new value IDs + // Generate new AttributesXml according to new value IDs string newAttributesXml = ""; var parsedProductVariantAttributes = _productAttributeParser.ParseProductVariantAttributes(combination.AttributesXml); foreach (var oldPva in parsedProductVariantAttributes) { - if (associatedAttributes.ContainsKey(oldPva.Id)) + if (!pvaMap.ContainsKey(oldPva.Id)) + continue; + + var newPva = pvaMap.Get(oldPva.Id); + + if (newPva == null) + continue; + + var oldPvaValuesStr = _productAttributeParser.ParseValues(combination.AttributesXml, oldPva.Id); + foreach (var oldPvaValueStr in oldPvaValuesStr) { - int newPvaId = associatedAttributes[oldPva.Id]; - var newPva = _productAttributeService.GetProductVariantAttributeById(newPvaId); - if (newPva != null) + if (newPva.ShouldHaveValues()) { - var oldPvaValuesStr = _productAttributeParser.ParseValues(combination.AttributesXml, oldPva.Id); - foreach (var oldPvaValueStr in oldPvaValuesStr) + // attribute values + int oldPvaValue = oldPvaValueStr.Convert(); + if (pvavMap.ContainsKey(oldPvaValue)) { - if (newPva.ShouldHaveValues()) + var newPvav = pvavMap.Get(oldPvaValue); + if (newPvav != null) { - //attribute values - int oldPvaValue = int.Parse(oldPvaValueStr); - if (associatedAttributeValues.ContainsKey(oldPvaValue)) - { - int newPvavId = associatedAttributeValues[oldPvaValue]; - var newPvav = _productAttributeService.GetProductVariantAttributeValueById(newPvavId); - if (newPvav != null) - { - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, newPvav.Id.ToString()); - } - } - } - else - { - //just a text - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, oldPvaValueStr); + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, newPvav.Id.ToString()); } } } + else + { + // just a text + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, oldPvaValueStr); + } } } - var combinationCopy = new ProductVariantAttributeCombination + + var newAssignedPictureIds = new HashSet(); + foreach (var strPicId in combination.AssignedPictureIds.EmptyNull().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + var newPic = clonedPictures.Get(strPicId.Convert()); + if (newPic != null) + { + newAssignedPictureIds.Add(newPic.Id.ToString(CultureInfo.InvariantCulture)); + } + } + + var combinationClone = new ProductVariantAttributeCombination { - ProductId = productCopy.Id, AttributesXml = newAttributesXml, StockQuantity = combination.StockQuantity, AllowOutOfStockOrders = combination.AllowOutOfStockOrders, - - // SmartStore extension Sku = combination.Sku, Gtin = combination.Gtin, ManufacturerPartNumber = combination.ManufacturerPartNumber, Price = combination.Price, - AssignedPictureIds = copyImages ? combination.AssignedPictureIds : null, + AssignedPictureIds = copyImages ? String.Join(",", newAssignedPictureIds) : null, Length = combination.Length, Width = combination.Width, Height = combination.Height, @@ -485,81 +700,12 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli IsActive = combination.IsActive //IsDefaultCombination = combination.IsDefaultCombination }; - _productAttributeService.InsertProductVariantAttributeCombination(combinationCopy); - } - - // tier prices - foreach (var tierPrice in product.TierPrices) - { - _productService.InsertTierPrice(new TierPrice - { - ProductId = productCopy.Id, - StoreId = tierPrice.StoreId, - CustomerRoleId = tierPrice.CustomerRoleId, - Quantity = tierPrice.Quantity, - Price = tierPrice.Price - }); - } - - // product <-> discounts mapping - foreach (var discount in product.AppliedDiscounts) - { - productCopy.AppliedDiscounts.Add(discount); - _productService.UpdateProduct(productCopy); - } - - // update "HasTierPrices" and "HasDiscountsApplied" properties - _productService.UpdateHasTierPricesProperty(productCopy); - _productService.UpdateLowestAttributeCombinationPriceProperty(productCopy); - _productService.UpdateHasDiscountsApplied(productCopy); - - // associated products - if (copyAssociatedProducts && product.ProductType != ProductType.BundledProduct) - { - var searchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.Position, - ParentGroupedProductId = product.Id, - PageSize = int.MaxValue, - ShowHidden = true - }; - - string copyOf = _localizationService.GetResource("Admin.Common.CopyOf"); - var associatedProducts = _productService.SearchProducts(searchContext); - foreach (var associatedProduct in associatedProducts) - { - var associatedProductCopy = CopyProduct(associatedProduct, string.Format("{0} {1}", copyOf, associatedProduct.Name), isPublished, copyImages, false); - associatedProductCopy.ParentGroupedProductId = productCopy.Id; - - _productService.UpdateProduct(productCopy); - } - } - - // bundled products - var bundledItems = _productService.GetBundleItems(product.Id, true); - - foreach (var bundleItem in bundledItems) - { - var newBundleItem = bundleItem.Item.Clone(); - newBundleItem.BundleProductId = productCopy.Id; - newBundleItem.CreatedOnUtc = utcNow; - newBundleItem.UpdatedOnUtc = utcNow; - - _productService.InsertBundleItem(newBundleItem); - - foreach (var itemFilter in bundleItem.Item.AttributeFilters) - { - var newItemFilter = itemFilter.Clone(); - newItemFilter.BundleItemId = newBundleItem.Id; - - _productAttributeService.InsertProductBundleItemAttributeFilter(newItemFilter); - } + clone.ProductVariantAttributeCombinations.Add(combinationClone); } - return productCopy; - } - - #endregion - } + // >>>>>> Commit combinations + Commit(); + } + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductUrlHelper.cs b/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductUrlHelper.cs new file mode 100644 index 0000000000..ce2b90d9d2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductUrlHelper.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services.Catalog.Modelling; +using SmartStore.Services.Localization; +using SmartStore.Services.Search.Modelling; + +namespace SmartStore.Services.Catalog.Extensions +{ + public class ProductUrlHelper + { + private readonly HttpRequestBase _httpRequest; + private readonly ICommonServices _services; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductAttributeService _productAttributeService; + private readonly Lazy _languageService; + private readonly Lazy _catalogSearchQueryAliasMapper; + private readonly Lazy _localizationSettings; + + private readonly int _languageId; + + public ProductUrlHelper( + HttpRequestBase httpRequest, + ICommonServices services, + IProductAttributeParser productAttributeParser, + IProductAttributeService productAttributeService, + Lazy languageService, + Lazy catalogSearchQueryAliasMapper, + Lazy localizationSettings) + { + _httpRequest = httpRequest; + _services = services; + _productAttributeParser = productAttributeParser; + _productAttributeService = productAttributeService; + _languageService = languageService; + _catalogSearchQueryAliasMapper = catalogSearchQueryAliasMapper; + _localizationSettings = localizationSettings; + + _languageId = _services.WorkContext.WorkingLanguage.Id; + } + + /// + /// URL of the product page used to create the new product URL. Created from route if null. + /// + public string Url { get; set; } + + /// + /// Initial query string used to create the new query string. Usually null. + /// + public QueryString InitialQuery { get; set; } + + /// + /// Converts a query object into a URL query string + /// + /// Product variant query + /// URL query string + public virtual string ToQueryString(ProductVariantQuery query) + { + var qs = InitialQuery != null + ? new QueryString(InitialQuery) + : new QueryString(); + + // Checkout Attributes + foreach (var item in query.CheckoutAttributes) + { + if (item.Date.HasValue) + { + qs.Add(item.ToString(), string.Join("-", item.Date.Value.Year, item.Date.Value.Month, item.Date.Value.Day)); + } + else + { + qs.Add(item.ToString(), item.Value); + } + } + + // Gift cards + foreach (var item in query.GiftCards) + { + qs.Add(item.ToString(), item.Value); + } + + // Variants + foreach (var item in query.Variants) + { + if (item.Alias.IsEmpty()) + { + item.Alias = _catalogSearchQueryAliasMapper.Value.GetVariantAliasById(item.AttributeId, _languageId); + } + + if (item.Date.HasValue) + { + qs.Add(item.ToString(), string.Join("-", item.Date.Value.Year, item.Date.Value.Month, item.Date.Value.Day)); + } + else if (item.IsFile || item.IsText) + { + qs.Add(item.ToString(), item.Value); + } + else + { + if (item.ValueAlias.IsEmpty()) + { + item.ValueAlias = _catalogSearchQueryAliasMapper.Value.GetVariantOptionAliasById(item.Value.ToInt(), _languageId); + } + + var value = item.ValueAlias.HasValue() + ? $"{item.ValueAlias}-{item.Value}" + : item.Value; + + qs.Add(item.ToString(), value); + } + } + + return qs.ToString(false); + } + + /// + /// Deserializes attributes XML into a product variant query + /// + /// Product variant query + /// Product identifier + /// XML formatted attributes + /// Bundle item identifier + /// Product variant attributes + public virtual void DeserializeQuery( + ProductVariantQuery query, + int productId, + string attributesXml, + int bundleItemId = 0, + ICollection attributes = null) + { + Guard.NotNull(query, nameof(query)); + + if (productId == 0 || attributesXml.IsEmpty()) + return; + + var attributeMap = _productAttributeParser.DeserializeProductVariantAttributes(attributesXml); + + if (attributes == null) + { + attributes = _productAttributeService.GetProductVariantAttributesByIds(attributeMap.Keys); + } + + foreach (var attribute in attributes) + { + if (attributeMap.ContainsKey(attribute.Id)) + { + foreach (var originalValue in attributeMap[attribute.Id]) + { + var value = originalValue; + DateTime? date = null; + + if (attribute.AttributeControlType == AttributeControlType.Datepicker) + { + date = originalValue.ToDateTime(new string[] { "D" }, CultureInfo.CurrentCulture, DateTimeStyles.None, null); + if (date == null) + continue; + + value = string.Join("-", date.Value.Year, date.Value.Month, date.Value.Day); + } + + var queryItem = new ProductVariantQueryItem(value); + queryItem.ProductId = productId; + queryItem.BundleItemId = bundleItemId; + queryItem.AttributeId = attribute.ProductAttributeId; + queryItem.VariantAttributeId = attribute.Id; + queryItem.Alias = _catalogSearchQueryAliasMapper.Value.GetVariantAliasById(attribute.ProductAttributeId, _languageId); + queryItem.Date = date; + queryItem.IsFile = attribute.AttributeControlType == AttributeControlType.FileUpload; + queryItem.IsText = attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox; + + if (attribute.ShouldHaveValues()) + { + queryItem.ValueAlias = _catalogSearchQueryAliasMapper.Value.GetVariantOptionAliasById(value.ToInt(), _languageId); + } + + query.AddVariant(queryItem); + } + } + } + } + + /// + /// Creates a product URL including variant query string. + /// + /// Product variant query + /// Product SEO name + /// Product URL + public virtual string GetProductUrl(ProductVariantQuery query, string productSeName) + { + if (productSeName.IsEmpty()) + return null; + + var url = Url ?? UrlHelper.GenerateUrl( + "Product", + null, + null, + new RouteValueDictionary(new { SeName = productSeName }), + RouteTable.Routes, + _httpRequest.RequestContext, + false); + + return url + ToQueryString(query); + } + + /// + /// Creates a product URL including variant query string. + /// + /// Product identifier + /// Product SEO name + /// XML formatted attributes + /// Product URL + public virtual string GetProductUrl(int productId, string productSeName, string attributesXml) + { + var query = new ProductVariantQuery(); + DeserializeQuery(query, productId, attributesXml); + + return GetProductUrl(query, productSeName); + } + + /// + /// Creates an absolute product URL. + /// + /// Product identifier + /// Product SEO name + /// XML formatted attributes + /// Store entity + /// Language entity + /// Absolute product URL + public virtual string GetAbsoluteProductUrl( + int productId, + string productSeName, + string attributesXml, + Store store = null, + Language language = null) + { + if (_httpRequest == null || productSeName.IsEmpty()) + return null; + + var url = Url; + + if (url.IsEmpty()) + { + // No given URL. Create SEO friendly URL. + var urlHelper = new LocalizedUrlHelper(_httpRequest.ApplicationPath, productSeName, false); + + store = store ?? _services.StoreContext.CurrentStore; + language = language ?? _services.WorkContext.WorkingLanguage; + + if (_localizationSettings.Value.SeoFriendlyUrlsForLanguagesEnabled) + { + var defaultSeoCode = _languageService.Value.GetDefaultLanguageSeoCode(store.Id); + + if (language.UniqueSeoCode == defaultSeoCode && _localizationSettings.Value.DefaultLanguageRedirectBehaviour > 0) + { + urlHelper.StripSeoCode(); + } + else + { + urlHelper.PrependSeoCode(language.UniqueSeoCode, true); + } + } + + var storeUrl = store.Url.TrimEnd('/'); + + // Prevent duplicate occurrence of application path. + if (urlHelper.ApplicationPath.HasValue() && storeUrl.EndsWith(urlHelper.ApplicationPath, StringComparison.OrdinalIgnoreCase)) + { + storeUrl = storeUrl.Substring(0, storeUrl.Length - urlHelper.ApplicationPath.Length).TrimEnd('/'); + } + + url = storeUrl + urlHelper.GetAbsolutePath(); + } + + if (attributesXml.HasValue()) + { + var query = new ProductVariantQuery(); + DeserializeQuery(query, productId, attributesXml); + + url = url + ToQueryString(query); + } + + return url; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductUrlHelperExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductUrlHelperExtensions.cs new file mode 100644 index 0000000000..d4501c5a6d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductUrlHelperExtensions.cs @@ -0,0 +1,129 @@ +using System.Linq; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Orders; +using SmartStore.Services.Catalog.Modelling; +using SmartStore.Services.Orders; +using SmartStore.Services.Seo; + +namespace SmartStore.Services.Catalog.Extensions +{ + public static class ProductUrlHelperExtensions + { + /// + /// Creates a product URL including variant query string. + /// + /// Product URL helper + /// Product identifier + /// Product SEO name + /// Bundle item identifier. Use 0 if it's not a bundle item. + /// Variant values + /// Product URL + public static string GetProductUrl( + this ProductUrlHelper helper, + int productId, + string productSeName, + int bundleItemId, + params ProductVariantAttributeValue[] variantValues) + { + Guard.NotZero(productId, nameof(productId)); + + var query = new ProductVariantQuery(); + + foreach (var value in variantValues) + { + var attribute = value.ProductVariantAttribute; + + query.AddVariant(new ProductVariantQueryItem(value.Id.ToString()) + { + ProductId = productId, + BundleItemId = bundleItemId, + AttributeId = attribute.ProductAttributeId, + VariantAttributeId = attribute.Id, + Alias = attribute.ProductAttribute.Alias, + ValueAlias = value.Alias + }); + } + + return helper.GetProductUrl(query, productSeName); + } + + /// + /// Creates a product URL including variant query string. + /// + /// Product URL helper + /// Product entity + /// Variant values + /// Product URL + public static string GetProductUrl( + this ProductUrlHelper helper, + Product product, + params ProductVariantAttributeValue[] variantValues) + { + Guard.NotNull(product, nameof(product)); + + return helper.GetProductUrl(product.Id, product.GetSeName(), 0, variantValues); + } + + /// + /// Creates a product URL including variant query string. + /// + /// Product URL helper + /// Product SEO name + /// Organized shopping cart item + /// Product URL + public static string GetProductUrl( + this ProductUrlHelper helper, + string productSeName, + OrganizedShoppingCartItem cartItem) + { + Guard.NotNull(cartItem, nameof(cartItem)); + + var query = new ProductVariantQuery(); + var product = cartItem.Item.Product; + + if (product.ProductType != ProductType.BundledProduct) + { + helper.DeserializeQuery(query, product.Id, cartItem.Item.AttributesXml); + } + else if (cartItem.ChildItems != null && product.BundlePerItemPricing) + { + foreach (var childItem in cartItem.ChildItems.Where(x => x.Item.Id != cartItem.Item.Id)) + { + helper.DeserializeQuery(query, childItem.Item.ProductId, childItem.Item.AttributesXml, childItem.BundleItemData.Item.Id); + } + } + + return helper.GetProductUrl(query, productSeName); + } + + /// + /// Creates a product URL including variant query string. + /// + /// Product URL helper + /// Product SEO name + /// Order item + /// Product URL + public static string GetProductUrl( + this ProductUrlHelper helper, + string productSeName, + OrderItem orderItem) + { + Guard.NotNull(orderItem, nameof(orderItem)); + + var query = new ProductVariantQuery(); + + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + helper.DeserializeQuery(query, orderItem.ProductId, orderItem.AttributesXml); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); + + bundleData.ForEach(x => helper.DeserializeQuery(query, x.ProductId, x.AttributesXml, x.BundleItemId)); + } + + return helper.GetProductUrl(query, productSeName); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductVariantQueryExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductVariantQueryExtensions.cs new file mode 100644 index 0000000000..42b07515b7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Extensions/ProductVariantQueryExtensions.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Media; +using SmartStore.Services.Catalog.Modelling; +using SmartStore.Services.Localization; +using SmartStore.Services.Media; + +namespace SmartStore.Services.Catalog.Extensions +{ + public static class ProductVariantQueryExtensions + { + public static string CreateSelectedAttributesXml( + this ProductVariantQuery query, + int productId, + int bundleItemId, + IEnumerable variantAttributes, + IProductAttributeParser productAttributeParser, + ILocalizationService localization, + IDownloadService downloadService, + CatalogSettings catalogSettings, + HttpRequestBase request, + List warnings) + { + var result = string.Empty; + + foreach (var pva in variantAttributes) + { + var selectedItems = query.Variants.Where(x => + x.ProductId == productId && + x.BundleItemId == bundleItemId && + x.AttributeId == pva.ProductAttributeId && + x.VariantAttributeId == pva.Id); + + switch (pva.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + case AttributeControlType.Boxes: + { + var firstItemValue = selectedItems.FirstOrDefault()?.Value; + if (firstItemValue.HasValue()) + { + var selectedAttributeId = firstItemValue.SplitSafe(",").SafeGet(0).ToInt(); + if (selectedAttributeId > 0) + { + result = productAttributeParser.AddProductAttribute(result, pva, selectedAttributeId.ToString()); + } + } + } + break; + + case AttributeControlType.Checkboxes: + foreach (var item in selectedItems) + { + var selectedAttributeId = item.Value.SplitSafe(",").SafeGet(0).ToInt(); + if (selectedAttributeId > 0) + { + result = productAttributeParser.AddProductAttribute(result, pva, selectedAttributeId.ToString()); + } + } + break; + + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + { + var selectedValue = string.Join(",", selectedItems.Select(x => x.Value)); + if (selectedValue.HasValue()) + { + result = productAttributeParser.AddProductAttribute(result, pva, selectedValue); + } + } + break; + + case AttributeControlType.Datepicker: + { + var firstItemDate = selectedItems.FirstOrDefault()?.Date; + if (firstItemDate.HasValue) + { + result = productAttributeParser.AddProductAttribute(result, pva, firstItemDate.Value.ToString("D")); + } + } + break; + + case AttributeControlType.FileUpload: + if (request == null) + { + var firstItemValue = selectedItems.FirstOrDefault()?.Value; + Guid downloadGuid; + Guid.TryParse(firstItemValue, out downloadGuid); + var download = downloadService.GetDownloadByGuid(downloadGuid); + if (download != null) + { + download.IsTransient = false; + downloadService.UpdateDownload(download); + + result = productAttributeParser.AddProductAttribute(result, pva, download.DownloadGuid.ToString()); + } + } + else + { + var postedFile = request.Files[ProductVariantQueryItem.CreateKey(productId, bundleItemId, pva.ProductAttributeId, pva.Id)]; + if (postedFile != null && postedFile.FileName.HasValue()) + { + if (postedFile.ContentLength > catalogSettings.FileUploadMaximumSizeBytes) + { + warnings.Add(localization.GetResource("ShoppingCart.MaximumUploadedFileSize").FormatInvariant((int)(catalogSettings.FileUploadMaximumSizeBytes / 1024))); + } + else + { + var download = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = false, + DownloadUrl = "", + ContentType = postedFile.ContentType, + Filename = System.IO.Path.GetFileNameWithoutExtension(postedFile.FileName), + Extension = System.IO.Path.GetExtension(postedFile.FileName), + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + + downloadService.InsertDownload(download, postedFile.InputStream != null ? postedFile.InputStream.ToByteArray() : null); + + result = productAttributeParser.AddProductAttribute(result, pva, download.DownloadGuid.ToString()); + } + } + } + break; + } + } + + return result; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/ICategoryNavigationFilter.cs b/src/Libraries/SmartStore.Services/Catalog/ICategoryNavigationFilter.cs deleted file mode 100644 index af6324c462..0000000000 --- a/src/Libraries/SmartStore.Services/Catalog/ICategoryNavigationFilter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Core.Domain.Catalog; - -namespace SmartStore.Services.Catalog -{ - /// - /// Implementors can use this interface to narrow down the category result set - /// in the public store. Useful in scenarios where merchant specific requirements demand - /// categories to be managed in backend, but be hidden in the frontend. - /// - public interface ICategoryNavigationFilter - { - /// - /// Applies a store specific filter to the actual category query. - /// - /// The original query - /// The modified/extended query - IQueryable Apply(IQueryable query); - } -} diff --git a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs index c19242429d..b52589cb5e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs @@ -4,6 +4,7 @@ using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Infrastructure; namespace SmartStore.Services.Catalog { @@ -12,7 +13,6 @@ namespace SmartStore.Services.Catalog /// public partial interface ICategoryService { - /// /// Assign acl to sub-categories and products /// @@ -45,35 +45,39 @@ void InheritStoresIntoChildren(int categoryId, void DeleteCategory(Category category, bool deleteChilds = false); /// - /// Gets categories + /// Builds LINQ query for categories /// - /// Category name + /// Category name filter /// A value indicating whether to show hidden records - /// Alias to be filtered - /// Whether to apply instances to the actual categories query. Never applied when is true + /// Alias filter /// Store identifier; 0 to load all records /// Category query - IQueryable GetCategories( + IQueryable BuildCategoriesQuery( string categoryName = "", bool showHidden = false, string alias = null, - bool applyNavigationFilters = true, int storeId = 0); - /// - /// Gets all categories - /// - /// Category name - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Alias to be filtered - /// Whether to apply instances to the actual categories query. Never applied when is true + /// + /// Gets all categories + /// + /// Category name filter + /// Page index + /// Page size + /// A value indicating whether to show hidden records + /// Alias filter + /// (Obsolete) Whether to apply instances to the actual categories query. Never applied when is true /// A value indicating whether categories without parent category in provided category list (source) should be ignored /// Store identifier; 0 to load all records - /// Categories - IPagedList GetAllCategories(string categoryName = "", int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string alias = null, - bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0); + /// Categories + IPagedList GetAllCategories( + string categoryName = "", + int pageIndex = 0, + int pageSize = int.MaxValue, + bool showHidden = false, + string alias = null, + bool ignoreCategoriesWithoutExistingParent = true, + int storeId = 0); /// /// Gets all categories filtered by parent category identifier @@ -96,6 +100,13 @@ IPagedList GetAllCategories(string categoryName = "", int pageIndex = /// Category Category GetCategoryById(int categoryId); + /// + /// Gets categories by Ids + /// + /// Array of category identifiers + /// List of Categories + IList GetCategoriesByIds(int[] categoryIds); + /// /// Inserts category /// @@ -128,8 +139,11 @@ IPagedList GetAllCategories(string categoryName = "", int pageIndex = /// Page size /// A value indicating whether to show hidden records /// Product a category mapping collection - IPagedList GetProductCategoriesByCategoryId(int categoryId, - int pageIndex, int pageSize, bool showHidden = false); + IPagedList GetProductCategoriesByCategoryId( + int categoryId, + int pageIndex, + int pageSize, + bool showHidden = false); /// /// Gets a product category mapping collection @@ -175,18 +189,45 @@ IPagedList GetProductCategoriesByCategoryId(int categoryId, void UpdateProductCategory(ProductCategory productCategory); /// - /// Builds a category breadcrumb (path) for a particular product + /// Gets the category trail /// - /// The product - /// The id of language - /// A delegate for fast (cached) path lookup - /// A callback that saves the resolved path to a cache (when pathLookup returned null) - /// A delegate for fast (cached) category lookup - /// First product category of product - /// Category breadcrumb for product - string GetCategoryPath(Product product, int? languageId, Func pathLookup, Action addPathToCache, Func categoryLookup, - ProductCategory prodCategory = null); - } + /// The category node + /// Trail + IEnumerable GetCategoryTrail(ICategoryNode node); + + + /// + /// Builds a category breadcrumb (path) for a particular category node + /// + /// The category node + /// The id of language. Pass null to skip localization. + /// 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, + string aliasPattern = null, + string separator = " "); + + /// + /// Gets the tree representation of categories + /// + /// Specifies which node to return as root + /// false excludes unpublished and ACL-inaccessible categories + /// > 0 = apply store mapping, 0 to bypass store mapping + /// The category tree representation + /// + /// This method puts the tree result into application cache, so subsequent calls are very fast. + /// Localization is up to the caller because the nodes only contain unlocalized data. + /// Subscribe to the CategoryTreeChanged event if you need to evict cache entries which depend + /// on this method's result. + /// + TreeNode GetCategoryTree( + int rootCategoryId = 0, + bool includeHidden = false, + int storeId = 0); + } public static class ICategoryServiceExtensions { @@ -194,10 +235,31 @@ public static class ICategoryServiceExtensions /// Builds a category breadcrumb for a particular product /// /// The product + /// The id of language. Pass null to skip localization. + /// The id of store. Pass null to skip store filtering. + /// The separator string /// Category breadcrumb for product - public static string GetCategoryBreadCrumb(this ICategoryService categoryService, Product product) + public static string GetCategoryPath(this ICategoryService categoryService, + Product product, + int? languageId = null, + int? storeId = null, + string separator = " ") { - return categoryService.GetCategoryPath(product, null, null, null, null); + Guard.NotNull(product, nameof(product)); + + string result = string.Empty; + + var pc = categoryService.GetProductCategoriesByProductId(product.Id).FirstOrDefault(); + if (pc != null) + { + var node = categoryService.GetCategoryTree(pc.CategoryId, false, storeId ?? 0); + if (node != null) + { + result = categoryService.GetCategoryPath(node, languageId, null, separator); + } + } + + return result; } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ICopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ICopyProductService.cs index 1fffa09df2..287d69836b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ICopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ICopyProductService.cs @@ -9,14 +9,19 @@ namespace SmartStore.Services.Catalog public partial interface ICopyProductService { /// - /// Create a copy of product with all depended data + /// Creates a copy of a product with all it's dependencies /// /// The product to copy - /// The name of product duplicate + /// The name of the product duplicate /// A value indicating whether the product duplicate should be published /// A value indicating whether the product images should be copied /// A value indicating whether the copy associated products /// Product copy - Product CopyProduct(Product product, string newName, bool isPublished, bool copyImages, bool copyAssociatedProducts = true); + Product CopyProduct( + Product product, + string newName, + bool isPublished, + bool copyImages, + bool copyAssociatedProducts = true); } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs index 24917da095..f8127a3d2c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs @@ -6,10 +6,10 @@ namespace SmartStore.Services.Catalog { - /// - /// Manufacturer service - /// - public partial interface IManufacturerService + /// + /// Manufacturer service + /// + public partial interface IManufacturerService { /// /// Deletes a manufacturer @@ -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 /// @@ -72,11 +79,17 @@ IPagedList GetAllManufacturers(string manufacturerName, /// Manufacturer void UpdateManufacturer(Manufacturer manufacturer); - /// - /// Deletes a product manufacturer mapping - /// - /// Product manufacturer mapping - void DeleteProductManufacturer(ProductManufacturer productManufacturer); + /// + /// Update HasDiscountsApplied property (used for performance optimization) + /// + /// Manufacturer + void UpdateHasDiscountsApplied(Manufacturer manufacturer); + + /// + /// Deletes a product manufacturer mapping + /// + /// Product manufacturer mapping + void DeleteProductManufacturer(ProductManufacturer productManufacturer); /// /// Gets product manufacturer collection diff --git a/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs index e3c7b9d5a6..c0a78cad3c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs @@ -3,20 +3,23 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Catalog { - /// - /// Price calculation service - /// - public partial interface IPriceCalculationService + /// + /// Price calculation service + /// + public partial interface IPriceCalculationService { /// /// Creates a price calculation context /// /// Products. null to lazy load data if required. + /// Customer, null to use current customer. + /// Store identifier, null to use current store. /// Price calculation context - PriceCalculationContext CreatePriceCalculationContext(IEnumerable products = null); + PriceCalculationContext CreatePriceCalculationContext(IEnumerable products = null, Customer customer = null, int? storeId = null); /// /// Get product special price (is valid) @@ -74,8 +77,8 @@ decimal GetFinalPrice(Product product, bool includeDiscounts, int quantity, ProductBundleItemData bundleItem = null, - PriceCalculationContext context = null); - + PriceCalculationContext context = null, + bool isTierPrice = false); /// /// Gets the final price including bundle per-item pricing /// @@ -101,28 +104,32 @@ decimal GetFinalPrice(Product product, /// Get the lowest possible price for a product. /// /// Product + /// The customer /// Object with cargo data for better performance /// Whether to display the from message. /// The lowest price. - decimal GetLowestPrice(Product product, PriceCalculationContext context, out bool displayFromMessage); + decimal GetLowestPrice(Product product, Customer customer, PriceCalculationContext context, out bool displayFromMessage); /// /// Get the lowest price of a grouped product. /// /// Grouped product + /// The customer /// Object with cargo data for better performance /// Products associated to product /// The associated product with the lowest price /// The lowest price. - decimal? GetLowestPrice(Product product, PriceCalculationContext context, IEnumerable associatedProducts, out Product lowestPriceProduct); + decimal? GetLowestPrice(Product product, Customer customer, PriceCalculationContext context, IEnumerable associatedProducts, out Product lowestPriceProduct); /// /// Get the initial price including preselected attributes /// /// Product + /// The customer + /// The currency /// Object with cargo data for better performance /// Preselected price - decimal GetPreselectedPrice(Product product, PriceCalculationContext context); + decimal GetPreselectedPrice(Product product, Customer customer, Currency currency, PriceCalculationContext context); /// /// Gets the product cost @@ -232,6 +239,7 @@ decimal GetDiscountAmount(Product product, /// /// Product variant attribute value /// Price adjustment of a variant attribute value - decimal GetProductVariantAttributeValuePriceAdjustment(ProductVariantAttributeValue attributeValue); + decimal GetProductVariantAttributeValuePriceAdjustment(ProductVariantAttributeValue attributeValue, + Product product, Customer customer, PriceCalculationContext context, int productQuantity = 1); } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index a02648a747..0017183f46 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -4,19 +4,26 @@ namespace SmartStore.Services.Catalog { - /// - /// Product attribute parser interface - /// - public partial interface IProductAttributeParser + /// + /// Product attribute parser interface + /// + public partial interface IProductAttributeParser { - #region Product attributes + #region Product attributes - /// - /// Gets selected product variant attributes as a map of integer ids with their corresponding values. - /// + /// + /// Parses, prefetches & caches all passed attribute definitions for the current request + /// + /// All attribute definitions to prefetch + /// Number of fetched attributes + int PrefetchProductVariantAttributes(IEnumerable attributesXml); + + /// + /// Gets selected product variant attributes as a map of integer ids with their corresponding values. + /// /// XML formatted attributes - /// The deserialized map - Multimap DeserializeProductVariantAttributes(string attributesXml); + /// The deserialized map + Multimap DeserializeProductVariantAttributes(string attributesXml); /// /// Gets selected product variant attributes @@ -32,22 +39,26 @@ public partial interface IProductAttributeParser /// Product variant attribute values IEnumerable ParseProductVariantAttributeValues(string attributesXml); - /// - /// Get list of localized product variant attribute values - /// - /// Map of combined attributes - /// Product variant attributes - /// Language identifier - /// List of localized product variant attribute values - IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0); + /// + /// Clear cached product variant attribute values. + /// + void ClearCachedAttributeValues(); /// - /// Gets selected product variant attribute value + /// 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 + /// /// XML formatted attributes - /// Product variant attribute identifier - /// Product variant attribute value - IList ParseValues(string attributesXml, int productVariantAttributeId); + /// Product variant attribute identifier + /// Product variant attribute value + IList ParseValues(string attributesXml, int productVariantAttributeId); /// /// Adds an attribute @@ -58,87 +69,44 @@ public partial interface IProductAttributeParser /// Attributes string AddProductAttribute(string attributesXml, ProductVariantAttribute pva, string value); - /// - /// Are attributes equal - /// - /// The attributes of the first product - /// The attributes of the second product - /// Result - bool AreProductAttributesEqual(string attributeXml1, string attributeXml2); - /// - /// Finds a product variant attribute combination by attributes stored in XML + /// Creates formatted xml for a list of product variant attribute values /// - /// Product identifier - /// XML formatted attributes - /// Found product variant attribute combination - ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml); + /// The attributes map + /// Attributes XML + string CreateAttributesXml(Multimap attributes); /// - /// Deserializes attribute data from an URL query string + /// Are attributes equal /// - /// Json data query string - /// List items with following structure: Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id - List> DeserializeQueryData(string jsonData); + /// The attributes of the first product + /// The attributes of the second product + /// Result + bool AreProductAttributesEqual(string attributeXml1, string attributeXml2); /// - /// Deserializes attribute data + /// Finds a product variant attribute combination by attributes stored in XML /// - /// List with deserialized data - /// XML formatted attributes /// Product identifier - /// Bundle item identifier - void DeserializeQueryData(List> queryData, string attributesXml, int productId, int bundleItemId = 0); - - /// - /// Serializes attribute data - /// /// XML formatted attributes - /// Product identifier - /// Whether to URL encode - /// Json string with attribute data - string SerializeQueryData(string attributesXml, int productId, bool urlEncode = true); + /// Found product variant attribute combination + ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml); - /// - /// Serializes attribute data - /// - /// List with deserialized data - /// Whether to URL encode - /// Json string with attribute data - string SerializeQueryData(List> queryData, bool urlEncode = true); + #endregion - /// - /// Gets the URL of the product page including attributes query string - /// - /// XML formatted attributes - /// Product identifier - /// Product SEO name - /// URL of the product page including attributes query string - string GetProductUrlWithAttributes(string attributesXml, int productId, string productSeName); + #region Gift card attributes /// - /// Gets the URL of the product page including attributes query string + /// Add gift card attrbibutes /// - /// Attribute query data - /// Product SEO name - /// URL of the product page including attributes query string - string GetProductUrlWithAttributes(List> queryData, string productSeName); - - #endregion - - #region Gift card attributes - - /// - /// Add gift card attrbibutes - /// /// XML formatted attributes - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - /// Attributes - string AddGiftCardAttribute(string attributesXml, string recipientName, + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + /// Attributes + string AddGiftCardAttribute(string attributesXml, string recipientName, string recipientEmail, string senderName, string senderEmail, string giftCardMessage); /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs index eacf65250e..7c02afc179 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs @@ -1,14 +1,14 @@ using System.Collections.Generic; using SmartStore.Collections; -using SmartStore.Core.Domain.Catalog; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; namespace SmartStore.Services.Catalog { - /// - /// Product attribute service interface - /// - public partial interface IProductAttributeService + /// + /// Product attribute service interface + /// + public partial interface IProductAttributeService { #region Product attributes @@ -43,15 +43,101 @@ public partial interface IProductAttributeService /// Product attribute void UpdateProductAttribute(ProductAttribute productAttribute); - #endregion + /// + /// 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); - #region Product variant attributes mappings (ProductVariantAttribute) + #endregion - /// - /// Deletes a product variant attribute mapping - /// - /// Product variant attribute mapping - void DeleteProductVariantAttribute(ProductVariantAttribute productVariantAttribute); + #region Product attribute options + + /// + /// Gets an attribute option by id + /// + /// Product attribute option identifier + /// Product attribute option + ProductAttributeOption GetProductAttributeOptionById(int id); + + /// + /// Gets all attribute options by options set identifier + /// + /// Attribute options set identifier + /// List of attribute options + IList GetProductAttributeOptionsByOptionsSetId(int optionsSetId); + + /// + /// Gets all attribute options by attribute identifier + /// + /// Attribute identifier + /// List of attribute options + IList GetProductAttributeOptionsByAttributeId(int attributeId); + + /// + /// Deletes an attribute option + /// + /// Product attribute option + void DeleteProductAttributeOption(ProductAttributeOption productAttributeOption); + + /// + /// Inserts an attribute option + /// + /// Product attribute option + void InsertProductAttributeOption(ProductAttributeOption productAttributeOption); + + /// + /// Updates an attribute option + /// + /// Product attribute option + void UpdateProductAttributeOption(ProductAttributeOption productAttributeOption); + + #endregion + + #region Product attribute options sets + + /// + /// Gets an attribute options set by id + /// + /// Product attribute options set identifier + /// Product attribute options set + ProductAttributeOptionsSet GetProductAttributeOptionsSetById(int id); + + /// + /// Gets all attribute options sets by attribute identifier + /// + /// + /// + IList GetProductAttributeOptionsSetsByAttributeId(int productAttributeId); + + /// + /// Deletes an attribute options set + /// + /// Product attribute options set + void DeleteProductAttributeOptionsSet(ProductAttributeOptionsSet productAttributeOptionsSet); + + /// + /// Inserts an attribute options set + /// + /// Product attribute options set + void InsertProductAttributeOptionsSet(ProductAttributeOptionsSet productAttributeOptionsSet); + + /// + /// Updates an attribute options set + /// + /// Product attribute options set + void UpdateProductAttributeOptionsSet(ProductAttributeOptionsSet productAttributeOptionsSet); + + #endregion + + #region Product variant attributes mappings (ProductVariantAttribute) + + /// + /// Deletes a product variant attribute mapping + /// + /// Product variant attribute mapping + void DeleteProductVariantAttribute(ProductVariantAttribute productVariantAttribute); /// /// Gets product variant attribute mappings by product identifier @@ -95,15 +181,24 @@ public partial interface IProductAttributeService /// The product variant attribute mapping void UpdateProductVariantAttribute(ProductVariantAttribute productVariantAttribute); - #endregion + /// + /// Copies attribute options (if any) to product variant attribute values. Existing values are ignored (identified by name field). + /// + /// The product variant attribute mapping + /// Product attribute options set identifier + /// Indicates whether to delete all existing product variant attribute values + /// Number of inserted product variant attribute values + int CopyAttributeOptions(ProductVariantAttribute productVariantAttribute, int productAttributeOptionsSetId, bool deleteExistingValues); - #region Product variant attribute values (ProductVariantAttributeValue) + #endregion - /// - /// Deletes a product variant attribute value - /// - /// Product variant attribute value - void DeleteProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue); + #region Product variant attribute values (ProductVariantAttributeValue) + + /// + /// Deletes a product variant attribute value + /// + /// Product variant attribute value + void DeleteProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue); /// /// Gets product variant attribute values by product identifier @@ -188,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 0b1f7649d5..89cc3363ae 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; using SmartStore.Collections; -using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; @@ -16,13 +13,13 @@ namespace SmartStore.Services.Catalog /// public partial interface IProductService { - #region Products + #region Products - /// - /// Delete a product - /// - /// Product - void DeleteProduct(Product product); + /// + /// Delete a product + /// + /// Product + void DeleteProduct(Product product); /// /// Gets all products displayed on the home page @@ -41,47 +38,28 @@ public partial interface IProductService /// Gets products by identifier /// /// Product identifiers + /// Which navigation properties to eager load /// Products - IList GetProductsByIds(int[] productIds); - - /// - /// Inserts a product - /// - /// Product - void InsertProduct(Product product); - - /// - /// Updates the product - /// - /// Product - void UpdateProduct(Product product, bool publishEvent = true); - - /// - /// Gets the total count of products matching the criteria - /// - int CountProducts(ProductSearchContext productSearchContext); - - /// - /// Search products - /// - IPagedList SearchProducts(ProductSearchContext productSearchContext); + IList GetProductsByIds(int[] productIds, ProductLoadFlags flags = ProductLoadFlags.None); /// - /// Builds a product query based on the options in ProductSearchContext parameter. + /// Get product by system name. /// - /// Parameters to build the query. - /// Customer role ids (ACL). - /// Whether to search localized values. - IQueryable PrepareProductSearchQuery(ProductSearchContext ctx, IEnumerable allowedCustomerRolesIds = null, bool searchLocalizedValue = false); + /// System name + /// Product entity. + Product GetProductBySystemName(string systemName); /// - /// Builds a product query based on the options in ProductSearchContext parameter. + /// Inserts a product /// - /// Parameters to build the query. - /// Data projector - /// Customer role ids (ACL). - /// Whether to search localized values. - IQueryable PrepareProductSearchQuery(ProductSearchContext ctx, Expression> selector, IEnumerable allowedCustomerRolesIds = null, bool searchLocalizedValue = false); + /// Product + void InsertProduct(Product product); + + /// + /// Updates the product + /// + /// Product + void UpdateProduct(Product product); /// /// Update product review totals @@ -175,19 +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 product specification attributes by product identifiers - /// - /// Product identifiers - /// Map of product specification attributes - Multimap GetProductSpecificationAttributesByProductIds(int[] productIds); + /// + /// Get applied discounts by product identifiers + /// + /// Product identifiers + /// Map of applied discounts + Multimap GetAppliedDiscountsByProductIds(int[] productIds); #endregion @@ -251,12 +230,20 @@ public partial interface IProductService /// Cross-sell product collection IList GetCrossSellProductsByProductId1(int productId1, bool showHidden = false); - /// - /// Gets a cross-sell product - /// - /// Cross-sell product identifier - /// Cross-sell product - CrossSellProduct GetCrossSellProductById(int crossSellProductId); + /// + /// Gets a cross-sell product collection by many product identifiers + /// + /// A sequence of alpha product identifiers + /// A value indicating whether to show hidden records + /// Cross-sell product collection + IList GetCrossSellProductsByProductIds(IEnumerable productIds, bool showHidden = false); + + /// + /// Gets a cross-sell product + /// + /// Cross-sell product identifier + /// Cross-sell product + CrossSellProduct GetCrossSellProductById(int crossSellProductId); /// /// Inserts a cross-sell product @@ -412,7 +399,33 @@ 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] + public enum ProductLoadFlags + { + None = 0, + WithCategories = 1 << 0, + WithManufacturers = 1 << 1, + WithPictures = 1 << 2, + WithReviews = 1 << 3, + WithSpecificationAttributes = 1 << 4, + WithAttributes = 1 << 5, + WithAttributeValues = 1 << 7, + WithAttributeCombinations = 1 << 8, + WithTags = 1 << 9, + WithTierPrices = 1 << 10, + WithDiscounts = 1 << 11, + WithBundleItems = 1 << 12, + WithDeliveryTime = 1 << 13, + Full = WithCategories | WithManufacturers | WithPictures | WithReviews | WithSpecificationAttributes | WithAttributes | WithAttributeValues | WithAttributeCombinations | WithTags | WithTierPrices | WithDiscounts | WithBundleItems | WithDeliveryTime + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs index e9bc4a786e..d4546d153d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; namespace SmartStore.Services.Catalog @@ -67,6 +68,13 @@ public partial interface ISpecificationAttributeService /// Specification attribute option IList GetSpecificationAttributeOptionsBySpecificationAttribute(int specificationAttributeId); + /// + /// Gets specification attribute options by specification attribute id + /// + /// Specification attribute identifiers + /// Map of specification attribute options + Multimap GetSpecificationAttributeOptionsBySpecificationAttributeIds(int[] specificationAttributeIds); + /// /// Deletes a specification attribute option /// @@ -119,6 +127,13 @@ IList GetProductSpecificationAttributesByProductI /// Product specification attribute mapping ProductSpecificationAttribute GetProductSpecificationAttributeById(int productSpecificationAttributeId); + /// + /// Get product specification attributes by product identifiers + /// + /// Product identifiers + /// Map of product specification attributes + Multimap GetProductSpecificationAttributesByProductIds(int[] productIds); + /// /// Inserts a product specification attribute mapping /// @@ -131,8 +146,6 @@ IList GetProductSpecificationAttributesByProductI /// Product specification attribute mapping void UpdateProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute); - void UpdateProductSpecificationMapping(int specificationAttributeId, string field, bool value); - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 3f7315905b..14daae3178 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -8,23 +8,18 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Stores; -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.Services.Seo; -using SmartStore.Services.Stores; 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; @@ -44,7 +39,6 @@ public class CategoryImporter : EntityImporterBase public CategoryImporter( IRepository categoryRepository, IRepository pictureRepository, - ICommonServices services, ICategoryTemplateService categoryTemplateService, IPictureService pictureService, ILocalizedEntityService localizedEntityService, @@ -52,7 +46,6 @@ public CategoryImporter( { _categoryRepository = categoryRepository; _pictureRepository = pictureRepository; - _services = services; _categoryTemplateService = categoryTemplateService; _pictureService = pictureService; _localizedEntityService = localizedEntityService; @@ -65,7 +58,7 @@ protected override void Import(ImportExecuteContext context) var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + using (var scope = new DbContextScope(ctx: context.Services.DbContext, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.DataSegmenter; @@ -76,7 +69,7 @@ protected override void Import(ImportExecuteContext context) var batch = segmenter.GetCurrentBatch(); // Perf: detach all entities - _categoryRepository.Context.DetachAll(false); + _categoryRepository.Context.DetachAll(true); context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); @@ -84,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 @@ -105,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 { @@ -115,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(); @@ -171,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"); } } } @@ -182,76 +177,74 @@ protected override void Import(ImportExecuteContext context) protected virtual int ProcessPictures( ImportExecuteContext context, - IEnumerable> batch, - Dictionary srcToDestId) + IEnumerable> batch) { - Picture picture = null; - 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) - { - if (category.PictureId.HasValue && (picture = _pictureRepository.GetById(category.PictureId.Value)) != null) - currentPictures.Add(picture); - - pictureBinary = _pictureService.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != 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; } @@ -335,11 +328,10 @@ protected virtual int ProcessCategories( { _categoryRepository.AutoCommitEnabled = true; - Category lastInserted = null; - Category lastUpdated = null; 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"); @@ -371,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; @@ -384,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; @@ -397,10 +389,9 @@ protected virtual int ProcessCategories( row.SetProperty(context.Result, (x) => x.MetaKeywords); row.SetProperty(context.Result, (x) => x.MetaDescription); row.SetProperty(context.Result, (x) => x.MetaTitle); - row.SetProperty(context.Result, (x) => x.PageSize, 12); - row.SetProperty(context.Result, (x) => x.AllowCustomersToSelectPageSize, true); + row.SetProperty(context.Result, (x) => x.PageSize); + row.SetProperty(context.Result, (x) => x.AllowCustomersToSelectPageSize); row.SetProperty(context.Result, (x) => x.PageSizeOptions); - row.SetProperty(context.Result, (x) => x.PriceRanges); row.SetProperty(context.Result, (x) => x.ShowOnHomePage); row.SetProperty(context.Result, (x) => x.HasDiscountsApplied); row.SetProperty(context.Result, (x) => x.Published, true); @@ -417,9 +408,6 @@ protected virtual int ProcessCategories( category.CategoryTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); } - row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); - category.UpdatedOnUtc = UtcNow; - if (id != 0 && !srcToDestId.ContainsKey(id)) { srcToDestId.Add(id, new ImportCategoryMapping { Inserted = row.IsTransient }); @@ -428,36 +416,24 @@ protected virtual int ProcessCategories( if (row.IsTransient) { _categoryRepository.Insert(category); - lastInserted = category; } else { _categoryRepository.Update(category); - lastUpdated = category; } } - // 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; - } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } - - if (lastUpdated != null) - { - _services.EventPublisher.EntityUpdated(lastUpdated); + 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 6228f5b443..ed77e3abc3 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -8,231 +8,603 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Events; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; +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 templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - - using (var scope = new DbContextScope(ctx: _productRepository.Context, 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 all entities - _productRepository.Context.DetachAll(false); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - // =========================================================================== - // 1.) Import products - // =========================================================================== - try - { - 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 && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); - - // =========================================================================== - // 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"); - } - } - } - } - } - - 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, @@ -240,11 +612,10 @@ protected virtual int ProcessProducts( { _productRepository.AutoCommitEnabled = false; - Product lastInserted = null; - Product lastUpdated = null; - var defaultTemplateId = templateViewPaths["ProductTemplate.Simple"]; - - foreach (var row in batch) + var defaultTemplateId = templateViewPaths["Product"]; + var hasNameColumn = context.DataSegmenter.HasColumn("Name"); + + foreach (var row in batch) { Product product = null; var id = row.GetDataValue("Id"); @@ -258,7 +629,7 @@ protected virtual int ProcessProducts( switch (keyName) { case "Id": - product = _productService.GetProductById(id); + product = _productRepository.GetById(id); // get it uncached break; case "Sku": product = _productService.GetProductBySku(keyValue); @@ -287,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; @@ -302,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)) { @@ -337,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); @@ -366,8 +737,10 @@ protected virtual int ProcessProducts( row.SetProperty(context.Result, (x) => x.BackorderModeId); row.SetProperty(context.Result, (x) => x.AllowBackInStockSubscriptions); row.SetProperty(context.Result, (x) => x.OrderMinimumQuantity, 1); - row.SetProperty(context.Result, (x) => x.OrderMaximumQuantity, 10000); - row.SetProperty(context.Result, (x) => x.AllowedQuantities); + row.SetProperty(context.Result, (x) => x.OrderMaximumQuantity, 100); + 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); row.SetProperty(context.Result, (x) => x.AvailableForPreOrder); @@ -403,16 +776,19 @@ protected virtual int ProcessProducts( // With new entities, "LimitedToStores" is an implicit field, meaning // it has to be set to true by code if it's absent but "StoreIds" exists. row.SetProperty(context.Result, (x) => x.LimitedToStores, !row.GetDataValue>("StoreIds").IsNullOrEmpty()); + 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; } - row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); - product.UpdatedOnUtc = UtcNow; - if (id != 0 && !srcToDestId.ContainsKey(id)) { srcToDestId.Add(id, new ImportProductMapping { Inserted = row.IsTransient }); @@ -421,36 +797,24 @@ protected virtual int ProcessProducts( if (row.IsTransient) { _productRepository.Insert(product); - lastInserted = product; } else { - _productRepository.Update(product); - lastUpdated = product; + //_productRepository.Update(product); // unnecessary: we use DetectChanges() } } - // 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; - } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } - - if (lastUpdated != null) - { - _services.EventPublisher.EntityUpdated(lastUpdated); + if (id != 0 && srcToDestId.ContainsKey(id)) + { + srcToDestId[id].DestinationId = row.Entity.Id; + } } return num; @@ -470,21 +834,20 @@ 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); if (product != null) { product.ParentGroupedProductId = srcToDestId[parentGroupedProductId].DestinationId; - _productRepository.Update(product); + //_productRepository.Update(product); // unnecessary: we use DetectChanges() } } } } var num = _productRepository.Context.SaveChanges(); - return num; } @@ -493,7 +856,6 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu // true, cause pictures must be saved and assigned an id prior adding a mapping. _productPictureRepository.AutoCommitEnabled = true; - ProductPicture lastInserted = null; var equalPictureId = 0; var numberOfPictures = (context.ExtraData.NumberOfPictures ?? int.MaxValue); @@ -508,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); @@ -520,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 @@ -541,7 +903,8 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu if (pictureBinary != null && pictureBinary.Length > 0) { - var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) + var currentProductPictures = _productPictureRepository.TableUntracked + .Expand(x => x.Picture.MediaStorage) .Where(x => x.ProductId == row.Entity.Id) .ToList(); @@ -551,17 +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; } - - pictureBinary = _pictureService.ValidatePicture(pictureBinary); + 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, false, false); - if (newPicture != null) + var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + + if (newPicture != null) { var mapping = new ProductPicture { @@ -571,40 +933,31 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu }; _productPictureRepository.Insert(mapping); - lastInserted = mapping; } } 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()); } } } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } } protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) { _productManufacturerRepository.AutoCommitEnabled = false; - ProductManufacturer lastInserted = null; - foreach (var row in batch) { var manufacturerIds = row.GetDataValue>("ManufacturerIds"); @@ -616,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) { @@ -628,25 +981,19 @@ protected virtual int ProcessProductManufacturers(ImportExecuteContext context, DisplayOrder = 1 }; _productManufacturerRepository.Insert(productManufacturer); - lastInserted = productManufacturer; } } } } - 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(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - return num; } @@ -654,8 +1001,6 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn { _productCategoryRepository.AutoCommitEnabled = false; - ProductCategory lastInserted = null; - foreach (var row in batch) { var categoryIds = row.GetDataValue>("CategoryIds"); @@ -667,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) { @@ -679,30 +1024,99 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn DisplayOrder = 1 }; _productCategoryRepository.Insert(productCategory); - lastInserted = productCategory; } } } } - 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(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - 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) @@ -713,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 74d4cd22bb..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; @@ -8,24 +9,16 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; +using SmartStore.Data.Caching; namespace SmartStore.Services.Catalog { - /// - /// Manufacturer service - /// - public partial class ManufacturerService : IManufacturerService + public partial class ManufacturerService : IManufacturerService { - #region Constants - 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 MANUFACTURERS_BY_ID_KEY = "SmartStore.manufacturer.id-{0}"; - private const string PRODUCTMANUFACTURERS_PATTERN_KEY = "SmartStore.productmanufacturer."; - - #endregion - - #region Fields + 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; @@ -35,21 +28,7 @@ public partial class ManufacturerService : IManufacturerService private readonly IStoreContext _storeContext; private readonly IEventPublisher _eventPublisher; private readonly IRequestCache _requestCache; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Category repository - /// ProductCategory repository - /// Product repository - /// Store mapping repository - /// Work context - /// Store context - /// Event published + public ManufacturerService(IRequestCache requestCache, IRepository manufacturerRepository, IRepository productManufacturerRepository, @@ -73,14 +52,6 @@ public ManufacturerService(IRequestCache requestCache, public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Methods - - /// - /// Deletes a manufacturer - /// - /// Manufacturer public virtual void DeleteManufacturer(Manufacturer manufacturer) { if (manufacturer == null) @@ -116,23 +87,11 @@ orderby mGroup.Key return query; } - /// - /// Gets all manufacturers - /// - /// A value indicating whether to show hidden records - /// Manufacturer collection public virtual IList GetAllManufacturers(bool showHidden = false) { return GetAllManufacturers(null, 0, showHidden); } - /// - /// Gets all manufacturers - /// - /// Manufacturer name - /// Whether to filter result by store identifier - /// A value indicating whether to show hidden records - /// Manufacturer collection public virtual IList GetAllManufacturers(string manufacturerName, int storeId = 0, bool showHidden = false) { var query = GetManufacturers(showHidden, storeId); @@ -147,15 +106,6 @@ public virtual IList GetAllManufacturers(string manufacturerName, return manufacturers; } - /// - /// Gets all manufacturers - /// - /// Manufacturer name - /// Page index - /// Page size - /// Whether to filter result by store identifier - /// A value indicating whether to show hidden records - /// Manufacturers public virtual IPagedList GetAllManufacturers(string manufacturerName, int pageIndex, int pageSize, int storeId = 0, bool showHidden = false) { @@ -163,26 +113,31 @@ public virtual IPagedList GetAllManufacturers(string manufacturerN return new PagedList(manufacturers, pageIndex, pageSize); } - /// - /// Gets a manufacturer - /// - /// Manufacturer identifier - /// Manufacturer public virtual Manufacturer GetManufacturerById(int manufacturerId) { if (manufacturerId == 0) return null; - string key = string.Format(MANUFACTURERS_BY_ID_KEY, manufacturerId); - return _requestCache.Get(key, () => { - return _manufacturerRepository.GetById(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(); } - /// - /// Inserts a manufacturer - /// - /// Manufacturer public virtual void InsertManufacturer(Manufacturer manufacturer) { if (manufacturer == null) @@ -193,15 +148,8 @@ public virtual void InsertManufacturer(Manufacturer manufacturer) //cache _requestCache.RemoveByPattern(MANUFACTURERS_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTMANUFACTURERS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(manufacturer); } - /// - /// Updates the manufacturer - /// - /// Manufacturer public virtual void UpdateManufacturer(Manufacturer manufacturer) { if (manufacturer == null) @@ -212,38 +160,27 @@ public virtual void UpdateManufacturer(Manufacturer manufacturer) //cache _requestCache.RemoveByPattern(MANUFACTURERS_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTMANUFACTURERS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(manufacturer); } - /// - /// Deletes a product manufacturer mapping - /// - /// Product manufacturer mapping - public virtual void DeleteProductManufacturer(ProductManufacturer productManufacturer) + public virtual void UpdateHasDiscountsApplied(Manufacturer manufacturer) + { + Guard.NotNull(manufacturer, nameof(manufacturer)); + + manufacturer.HasDiscountsApplied = manufacturer.AppliedDiscounts.Count > 0; + UpdateManufacturer(manufacturer); + } + + public virtual void DeleteProductManufacturer(ProductManufacturer productManufacturer) { if (productManufacturer == null) throw new ArgumentNullException("productManufacturer"); _productManufacturerRepository.Delete(productManufacturer); - //cache _requestCache.RemoveByPattern(MANUFACTURERS_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTMANUFACTURERS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(productManufacturer); } - /// - /// Gets product manufacturer collection - /// - /// Manufacturer identifier - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Product manufacturer collection public virtual IPagedList GetProductManufacturersByManufacturerId(int manufacturerId, int pageIndex, int pageSize, bool showHidden = false) { if (manufacturerId == 0) @@ -254,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; @@ -289,12 +224,6 @@ orderby pmGroup.Key }); } - /// - /// Gets a product manufacturer mapping collection - /// - /// Product identifier - /// A value indicating whether to show hidden records - /// Product manufacturer mapping collection public virtual IList GetProductManufacturersByProductId(int productId, bool showHidden = false) { if (productId == 0) @@ -303,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; @@ -316,7 +242,7 @@ orderby pm.DisplayOrder { if (!QuerySettings.IgnoreMultiStore) { - //Store mapping + // Store mapping var currentStoreId = _storeContext.CurrentStore.Id; query = from pm in query join m in _manufacturerRepository.Table on pm.ManufacturerId equals m.Id @@ -327,7 +253,7 @@ from sm in m_sm.DefaultIfEmpty() select pm; } - //only distinct manufacturers (group by ID) + // Only distinct manufacturers (group by ID) query = from pm in query group pm by pm.Id into mGroup orderby mGroup.Key @@ -336,14 +262,16 @@ 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; }); } public virtual Multimap GetProductManufacturersByManufacturerIds(int[] manufacturerIds) { - Guard.ArgumentNotNull(() => manufacturerIds); + Guard.NotNull(manufacturerIds, nameof(manufacturerIds)); var query = _productManufacturerRepository.TableUntracked .Where(x => manufacturerIds.Contains(x.ManufacturerId)) @@ -358,14 +286,21 @@ public virtual Multimap GetProductManufacturersByManuf public virtual Multimap GetProductManufacturersByProductIds(int[] productIds) { - Guard.ArgumentNotNull(() => productIds); + 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) - join m in _manufacturerRepository.TableUntracked on pm.ManufacturerId equals m.Id - where productIds.Contains(pm.ProductId) + 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) @@ -375,11 +310,6 @@ where productIds.Contains(pm.ProductId) return map; } - /// - /// Gets a product manufacturer mapping - /// - /// Product manufacturer mapping identifier - /// Product manufacturer mapping public virtual ProductManufacturer GetProductManufacturerById(int productManufacturerId) { if (productManufacturerId == 0) @@ -388,10 +318,6 @@ public virtual ProductManufacturer GetProductManufacturerById(int productManufac return _productManufacturerRepository.GetById(productManufacturerId); } - /// - /// Inserts a product manufacturer mapping - /// - /// Product manufacturer mapping public virtual void InsertProductManufacturer(ProductManufacturer productManufacturer) { if (productManufacturer == null) @@ -399,18 +325,11 @@ public virtual void InsertProductManufacturer(ProductManufacturer productManufac _productManufacturerRepository.Insert(productManufacturer); - //cache _requestCache.RemoveByPattern(MANUFACTURERS_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTMANUFACTURERS_PATTERN_KEY); - //event notification - _eventPublisher.EntityInserted(productManufacturer); } - /// - /// Updates the product manufacturer mapping - /// - /// Product manufacturer mapping public virtual void UpdateProductManufacturer(ProductManufacturer productManufacturer) { if (productManufacturer == null) @@ -418,14 +337,8 @@ public virtual void UpdateProductManufacturer(ProductManufacturer productManufac _productManufacturerRepository.Update(productManufacturer); - //cache _requestCache.RemoveByPattern(MANUFACTURERS_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTMANUFACTURERS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productManufacturer); } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerTemplateService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerTemplateService.cs index f58f303d3e..eae06162de 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ManufacturerTemplateService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ManufacturerTemplateService.cs @@ -1,67 +1,31 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Events; namespace SmartStore.Services.Catalog { - /// - /// Manufacturer template service - /// public partial class ManufacturerTemplateService : IManufacturerTemplateService { - - #region Fields - private readonly IRepository _manufacturerTemplateRepository; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Manufacturer template repository - /// Event published - public ManufacturerTemplateService(IRequestCache requestCache, - IRepository manufacturerTemplateRepository, - IEventPublisher eventPublisher) + public ManufacturerTemplateService(IRepository manufacturerTemplateRepository, IEventPublisher eventPublisher) { - _requestCache = requestCache; _manufacturerTemplateRepository = manufacturerTemplateRepository; _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - /// - /// Delete manufacturer template - /// - /// Manufacturer template public virtual void DeleteManufacturerTemplate(ManufacturerTemplate manufacturerTemplate) { if (manufacturerTemplate == null) throw new ArgumentNullException("manufacturerTemplate"); _manufacturerTemplateRepository.Delete(manufacturerTemplate); - - //event notification - _eventPublisher.EntityDeleted(manufacturerTemplate); } - /// - /// Gets all manufacturer templates - /// - /// Manufacturer templates public virtual IList GetAllManufacturerTemplates() { var query = from pt in _manufacturerTemplateRepository.Table @@ -72,11 +36,6 @@ orderby pt.DisplayOrder return templates; } - /// - /// Gets a manufacturer template - /// - /// Manufacturer template identifier - /// Manufacturer template public virtual ManufacturerTemplate GetManufacturerTemplateById(int manufacturerTemplateId) { if (manufacturerTemplateId == 0) @@ -85,25 +44,14 @@ public virtual ManufacturerTemplate GetManufacturerTemplateById(int manufacturer return _manufacturerTemplateRepository.GetById(manufacturerTemplateId); } - /// - /// Inserts manufacturer template - /// - /// Manufacturer template public virtual void InsertManufacturerTemplate(ManufacturerTemplate manufacturerTemplate) { if (manufacturerTemplate == null) throw new ArgumentNullException("manufacturerTemplate"); _manufacturerTemplateRepository.Insert(manufacturerTemplate); - - //event notification - _eventPublisher.EntityInserted(manufacturerTemplate); } - /// - /// Updates the manufacturer template - /// - /// Manufacturer template public virtual void UpdateManufacturerTemplate(ManufacturerTemplate manufacturerTemplate) { if (manufacturerTemplate == null) @@ -111,10 +59,6 @@ public virtual void UpdateManufacturerTemplate(ManufacturerTemplate manufacturer _manufacturerTemplateRepository.Update(manufacturerTemplate); - //event notification - _eventPublisher.EntityUpdated(manufacturerTemplate); } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/CheckoutAttributeQueryItem.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/CheckoutAttributeQueryItem.cs new file mode 100644 index 0000000000..cc35919d93 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/CheckoutAttributeQueryItem.cs @@ -0,0 +1,49 @@ +using System; + +namespace SmartStore.Services.Catalog.Modelling +{ + public class CheckoutAttributeQueryItem + { + public CheckoutAttributeQueryItem(int attributeId, string value) + { + Value = value ?? string.Empty; + AttributeId = attributeId; + } + + /// + /// Key used for form names. + /// + /// Checkout attribute identifier + /// Key + public static string CreateKey(int attributeId) + { + return $"cattr{attributeId}"; + } + + public string Value { get; private set; } + public int AttributeId { get; private set; } + public DateTime? Date { get; set; } + public bool IsFile { get; set; } + public bool IsText { get; set; } + + public override string ToString() + { + var key = CreateKey(AttributeId); + + if (Date.HasValue) + { + return key + "-date"; + } + else if (IsFile) + { + return key + "-file"; + } + else if (IsText) + { + return key + "-text"; + } + + return key; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/GiftCardQueryItem.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/GiftCardQueryItem.cs new file mode 100644 index 0000000000..df61daf13b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/GiftCardQueryItem.cs @@ -0,0 +1,40 @@ +namespace SmartStore.Services.Catalog.Modelling +{ + public class GiftCardQueryItem + { + public GiftCardQueryItem(string name, string value) + { + Guard.NotEmpty(name, nameof(name)); + + Name = name.ToLower(); + Value = value ?? string.Empty; + + if (Name.StartsWith(".")) + { + Name = Name.Substring(1); + } + } + + public static string CreateKey(int productId, int bundleItemId, string name) + { + if (name.HasValue()) + { + return $"giftcard{productId}-{bundleItemId}-.{name.EmptyNull().ToLower()}"; + } + + // Just return field prefix for partial views. + return $"giftcard{productId}-{bundleItemId}-"; + } + + public string Name { get; private set; } + public string Value { get; private set; } + + public int ProductId { get; set; } + public int BundleItemId { get; set; } + + public override string ToString() + { + return CreateKey(ProductId, BundleItemId, Name); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/IProductVariantQueryFactory.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/IProductVariantQueryFactory.cs new file mode 100644 index 0000000000..119a770832 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/IProductVariantQueryFactory.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Services.Catalog.Modelling +{ + public interface IProductVariantQueryFactory + { + /// + /// The last created product variant query. The MVC model binder uses this property to avoid repeated binding. + /// + ProductVariantQuery Current { get; } + + /// + /// Creates a name value collection with product variants from the current + /// by looking up corresponding keys in posted form and/or query string + /// + /// Product variant query + ProductVariantQuery CreateFromQuery(); + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQuery.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQuery.cs new file mode 100644 index 0000000000..1eca518b05 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQuery.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Services.Catalog.Modelling +{ + public class ProductVariantQuery + { + private readonly List _variants; + private readonly List _giftCards; + private readonly List _checkoutAttributes; + + public ProductVariantQuery() + { + _variants = new List(); + _giftCards = new List(); + _checkoutAttributes = new List(); + } + + public IReadOnlyList Variants + { + get + { + return _variants; + } + } + + public IReadOnlyList GiftCards + { + get + { + return _giftCards; + } + } + + public IReadOnlyList CheckoutAttributes + { + get + { + return _checkoutAttributes; + } + } + + public void AddVariant(ProductVariantQueryItem item) + { + _variants.Add(item); + } + + public void AddGiftCard(GiftCardQueryItem item) + { + _giftCards.Add(item); + } + + public void AddCheckoutAttribute(CheckoutAttributeQueryItem item) + { + _checkoutAttributes.Add(item); + } + + public string GetGiftCardValue(int productId, int bundleItemId, string name) + { + return _giftCards.FirstOrDefault(x => + x.ProductId == productId && + x.BundleItemId == bundleItemId && + x.Name.IsCaseInsensitiveEqual(name)) + ?.Value; + } + + public override string ToString() + { + var groups = new string[] + { + string.Join("&", Variants.Select(x => x.ToString())), + string.Join("&", GiftCards.Select(x => x.ToString())), + string.Join("&", CheckoutAttributes.Select(x => x.ToString())) + }; + + return string.Join("&", groups.Where(x => x.HasValue())); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryFactory.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryFactory.cs new file mode 100644 index 0000000000..954eebf7d5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryFactory.cs @@ -0,0 +1,340 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using SmartStore.Collections; +using SmartStore.Core.Html; +using SmartStore.Services.Search.Modelling; + +namespace SmartStore.Services.Catalog.Modelling +{ + public class ProductVariantQueryFactory : IProductVariantQueryFactory + { + internal static readonly Regex IsVariantKey = new Regex(@"pvari[0-9]+-[0-9]+-[0-9]+-[0-9]+", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + internal static readonly Regex IsVariantAliasKey = new Regex(@"\w+-[0-9]+-[0-9]+-[0-9]+", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + internal static readonly Regex IsGiftCardKey = new Regex(@"giftcard[0-9]+-[0-9]+-\.\w+$", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + internal static readonly Regex IsCheckoutAttributeKey = new Regex(@"cattr[0-9]+", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled); + + protected readonly HttpContextBase _httpContext; + protected readonly ICommonServices _services; + protected readonly ICatalogSearchQueryAliasMapper _catalogSearchQueryAliasMapper; + private Multimap _queryItems; + + public ProductVariantQueryFactory( + HttpContextBase httpContext, + ICommonServices services, + ICatalogSearchQueryAliasMapper catalogSearchQueryAliasMapper) + { + _httpContext = httpContext; + _services = services; + _catalogSearchQueryAliasMapper = catalogSearchQueryAliasMapper; + } + + protected Multimap QueryItems + { + get + { + if (_queryItems == null) + { + _queryItems = new Multimap(); + + if (_httpContext.Request?.Unvalidated != null) + { + var form = _httpContext.Request.Unvalidated.Form; + + if (form != null) + { + foreach (var key in form.AllKeys) + { + if (key.HasValue()) + { + _queryItems.AddRange(key, form[key].SplitSafe(",")); + } + } + } + + var query = _httpContext.Request.Unvalidated.QueryString; + + if (query != null) + { + foreach (var key in query.AllKeys) + { + if (key.HasValue()) + { + _queryItems.AddRange(key, query[key].SplitSafe(",")); + } + } + } + } + } + + return _queryItems; + } + } + + 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; + var day = 0; + + if (key.EndsWith("-date")) + { + // Convert from one query string item. + var dateItems = value.SplitSafe("-"); + year = dateItems.SafeGet(0).ToInt(); + month = dateItems.SafeGet(1).ToInt(); + day = dateItems.SafeGet(2).ToInt(); + } + else if (key.EndsWith("-year")) + { + // Convert from three form controls. + var dateKey = key.Replace("-year", ""); + year = value.ToInt(); + month = QueryItems[dateKey + "-month"].FirstOrDefault()?.ToInt() ?? 0; + day = QueryItems[dateKey + "-day"].FirstOrDefault()?.ToInt() ?? 0; + } + + if (year > 0 && month > 0 && day > 0) + { + try + { + return new DateTime(year, month, day); + } + catch { } + } + + return null; + } + + protected virtual void ConvertVariant(ProductVariantQuery query, string key, ICollection values) + { + var ids = key.Replace("pvari", "").SplitSafe("-"); + if (ids.Length < 4) + return; + + var isDate = key.EndsWith("-date") || key.EndsWith("-year"); + var isFile = key.EndsWith("-file"); + var isText = key.EndsWith("-text"); + + if (isDate || isFile || isText) + { + var value = isText ? string.Join(",", values) : values.First(); + 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); + } + + query.AddVariant(variant); + } + else + { + foreach (var value in values) + { + var variant = new ProductVariantQueryItem(value) + { + ProductId = ids[0].ToInt(), + BundleItemId = ids[1].ToInt(), + AttributeId = ids[2].ToInt(), + VariantAttributeId = ids[3].ToInt() + }; + + query.AddVariant(variant); + } + } + } + + protected virtual void ConvertVariantAlias(ProductVariantQuery query, string key, ICollection values, int languageId) + { + var ids = key.SplitSafe("-"); + var len = ids.Length; + if (len < 4) + return; + + var isDate = key.EndsWith("-date") || key.EndsWith("-year"); + var isFile = key.EndsWith("-file"); + var isText = key.EndsWith("-text"); + + if (isDate || isFile || isText) + { + ids = ids.Take(len - 1).ToArray(); + len = ids.Length; + } + + var alias = string.Join("-", ids.Take(len - 3)); + var attributeId = _catalogSearchQueryAliasMapper.GetVariantIdByAlias(alias, languageId); + if (attributeId == 0) + return; + + var productId = ids.SafeGet(len - 3).ToInt(); + var bundleItemId = ids.SafeGet(len - 2).ToInt(); + var variantAttributeId = ids.SafeGet(len - 1).ToInt(); + + if (productId == 0 || variantAttributeId == 0) + return; + + if (isDate || isFile || isText) + { + var value = isText ? string.Join(",", values) : values.First(); + 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); + } + + query.AddVariant(variant); + } + else + { + foreach (var value in values) + { + // We cannot use GetVariantOptionIdByAlias. It doesn't necessarily provide a ProductVariantAttributeValue.Id associated with this product. + //var optionId = _catalogSearchQueryAliasMapper.GetVariantOptionIdByAlias(value, attributeId, languageId); + var optionId = 0; + string valueAlias = null; + + var valueIds = value.SplitSafe("-"); + if (valueIds.Length >= 2) + { + optionId = valueIds.SafeGet(valueIds.Length - 1).ToInt(); + valueAlias = string.Join("-", valueIds.Take(valueIds.Length - 1)); + } + + var variant = new ProductVariantQueryItem(optionId == 0 ? value : optionId.ToString()) + { + ProductId = productId, + BundleItemId = bundleItemId, + AttributeId = attributeId, + VariantAttributeId = variantAttributeId, + Alias = alias + }; + + if (optionId != 0) + { + variant.ValueAlias = valueAlias; + } + + query.AddVariant(variant); + } + } + } + + protected virtual void ConvertGiftCard(ProductVariantQuery query, string key, string value) + { + var elements = key.Replace("giftcard", "").SplitSafe("-"); + if (elements.Length > 2) + { + var giftCard = new GiftCardQueryItem(elements[2], value); + giftCard.ProductId = elements[0].ToInt(); + giftCard.BundleItemId = elements[1].ToInt(); + + query.AddGiftCard(giftCard); + } + } + + protected virtual void ConvertCheckoutAttribute(ProductVariantQuery query, string key, ICollection values) + { + var ids = key.Replace("cattr", "").SplitSafe("-"); + if (ids.Length <= 0) + return; + + var attributeId = ids[0].ToInt(); + var isDate = key.EndsWith("-date") || key.EndsWith("-year"); + var isFile = key.EndsWith("-file"); + var isText = key.EndsWith("-text"); + + if (isDate || isFile || isText) + { + var value = isText ? string.Join(",", values) : values.First(); + var attribute = new CheckoutAttributeQueryItem(attributeId, value) + { + IsFile = isFile, + IsText = isText + }; + + if (isDate) + { + attribute.Date = ConvertToDate(key, value); + } + + query.AddCheckoutAttribute(attribute); + } + else + { + foreach (var value in values) + { + query.AddCheckoutAttribute(new CheckoutAttributeQueryItem(attributeId, value)); + } + } + } + + protected virtual void ConvertItems(HttpRequestBase request, ProductVariantQuery query, string key, ICollection values) + { + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryItem.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryItem.cs new file mode 100644 index 0000000000..a41f3ea64b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryItem.cs @@ -0,0 +1,60 @@ +using System; + +namespace SmartStore.Services.Catalog.Modelling +{ + public class ProductVariantQueryItem + { + public ProductVariantQueryItem(string value) + { + Value = value ?? string.Empty; + } + + /// + /// Key used for form names. + /// + /// Product identifier + /// Bundle item identifier. 0 if not a bundle item. + /// Product attribute identifier + /// Product variant attribute identifier + /// Key + public static string CreateKey(int productId, int bundleItemId, int attributeId, int variantAttributeId) + { + return $"pvari{productId}-{bundleItemId}-{attributeId}-{variantAttributeId}"; + } + + public string Value { get; private set; } + + public int ProductId { get; set; } + public int BundleItemId { get; set; } + public int AttributeId { get; set; } + public int VariantAttributeId { get; set; } + public DateTime? Date { get; set; } + public bool IsFile { get; set; } + public bool IsText { get; set; } + + public string Alias { get; set; } + public string ValueAlias { get; set; } + + public override string ToString() + { + var key = Alias.HasValue() + ? $"{Alias}-{ProductId}-{BundleItemId}-{VariantAttributeId}" + : CreateKey(ProductId, BundleItemId, AttributeId, VariantAttributeId); + + if (Date.HasValue) + { + return key + "-date"; + } + else if (IsFile) + { + return key + "-file"; + } + else if (IsText) + { + return key + "-text"; + } + + return key; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryModelBinder.cs new file mode 100644 index 0000000000..536553d3ff --- /dev/null +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryModelBinder.cs @@ -0,0 +1,41 @@ +using System.Web.Mvc; +using Autofac.Integration.Mvc; + +namespace SmartStore.Services.Catalog.Modelling +{ + [ModelBinderType(typeof(ProductVariantQuery))] + public class ProductAttributeQueryModelBinder : IModelBinder + { + private readonly IProductVariantQueryFactory _factory; + + public ProductAttributeQueryModelBinder(IProductVariantQueryFactory 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. + return _factory.Current; + } + + var modelType = bindingContext.ModelType; + + if (modelType != typeof(ProductVariantQuery)) + { + return new ProductVariantQuery(); + } + + var query = _factory.CreateFromQuery(); + return query; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs index 4645f4eac0..eea8195b4f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs @@ -7,66 +7,87 @@ 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 _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> appliedDiscounts) + Func> productManufacturers, + Func> appliedDiscounts, + Func> productBundleItems, + Func> associatedProducts) { if (products == null) { _productIds = new List(); _productIdsTierPrices = new List(); _productIdsAppliedDiscounts = 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)); - } + _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; _funcTierPrices = tierPrices; _funcProductCategories = productCategories; + _funcProductManufacturers = productManufacturers; _funcAppliedDiscounts = appliedDiscounts; + _funcProductBundleItems = productBundleItems; + _funcAssociatedProducts = associatedProducts; } + 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 (_appliedDiscounts != null) - _appliedDiscounts.Clear(); + _attributes?.Clear(); + _attributeCombinations?.Clear(); + _tierPrices?.Clear(); + _productCategories?.Clear(); + _productManufacturers?.Clear(); + _appliedDiscounts?.Clear(); + _productBundleItems?.Clear(); + _associatedProducts?.Clear(); + + _bundledProductIds.Clear(); + _groupedProductIds.Clear(); } public LazyMultimap Attributes @@ -117,6 +138,18 @@ public LazyMultimap ProductCategories } } + public LazyMultimap ProductManufacturers + { + get + { + if (_productManufacturers == null) + { + _productManufacturers = new LazyMultimap(keys => _funcProductManufacturers(keys), _productIds); + } + return _productManufacturers; + } + } + public LazyMultimap AppliedDiscounts { get @@ -129,13 +162,40 @@ public LazyMultimap AppliedDiscounts } } - public void Collect(IEnumerable productIds) + public LazyMultimap ProductBundleItems + { + get + { + if (_productBundleItems == null) + { + _productBundleItems = new LazyMultimap(keys => _funcProductBundleItems(keys), _bundledProductIds); + } + return _productBundleItems; + } + } + + 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); TierPrices.Collect(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 ca45f495d7..5fc429449c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -1,13 +1,15 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; 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; @@ -21,40 +23,43 @@ public partial class PriceCalculationService : IPriceCalculationService { private readonly IDiscountService _discountService; private readonly ICategoryService _categoryService; - private readonly IProductAttributeParser _productAttributeParser; + 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; private readonly ICommonServices _services; private readonly HttpRequestBase _httpRequestBase; private readonly ITaxService _taxService; + private readonly TaxSettings _taxSettings; - public PriceCalculationService( + public PriceCalculationService( IDiscountService discountService, ICategoryService categoryService, - IProductAttributeParser productAttributeParser, + IManufacturerService manufacturerService, + IProductAttributeParser productAttributeParser, IProductService productService, - ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings, IProductAttributeService productAttributeService, IDownloadService downloadService, ICommonServices services, HttpRequestBase httpRequestBase, - ITaxService taxService) + ITaxService taxService, + TaxSettings taxSettings) { - this._discountService = discountService; - this._categoryService = categoryService; - this._productAttributeParser = productAttributeParser; - this._productService = productService; - this._shoppingCartSettings = shoppingCartSettings; - this._catalogSettings = catalogSettings; - this._productAttributeService = productAttributeService; - this._downloadService = downloadService; - this._services = services; - this._httpRequestBase = httpRequestBase; - this._taxService = taxService; + _discountService = discountService; + _categoryService = categoryService; + _manufacturerService = manufacturerService; + _productAttributeParser = productAttributeParser; + _productService = productService; + _catalogSettings = catalogSettings; + _productAttributeService = productAttributeService; + _downloadService = downloadService; + _services = services; + _httpRequestBase = httpRequestBase; + _taxService = taxService; + _taxSettings = taxSettings; } #region Utilities @@ -65,27 +70,36 @@ 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; + // check discounts assigned to the product if (product.HasDiscountsApplied) { - //we use this property ("HasDiscountsApplied") for performance optimziation to avoid unnecessary database calls + // We use this property ("HasDiscountsApplied") for performance optimziation to avoid unnecessary database calls IEnumerable appliedDiscounts = null; - + if (context == null) + { appliedDiscounts = product.AppliedDiscounts; + } else - appliedDiscounts = context.AppliedDiscounts.Load(product.Id); + { + if (!context.AppliedDiscounts.FullyLoaded) + { + context.AppliedDiscounts.LoadAll(); + } + appliedDiscounts = context.AppliedDiscounts.GetOrLoad(product.Id); + } if (appliedDiscounts != null) { 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); } @@ -93,22 +107,22 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer } } - //performance optimization. load all category discounts just to ensure that we have at least one - if (_discountService.GetAllDiscounts(DiscountType.AssignedToCategories).Any()) + // check discounts assigned to categories + var discountsAssignedToCategories = _discountService.GetAllDiscounts(DiscountType.AssignedToCategories); + if (discountsAssignedToCategories != null && discountsAssignedToCategories.Any()) { IEnumerable productCategories = null; if (context == null) productCategories = _categoryService.GetProductCategoriesByProductId(product.Id); else - productCategories = context.ProductCategories.Load(product.Id); + productCategories = context.ProductCategories.GetOrLoad(product.Id); if (productCategories != null) { foreach (var productCategory in productCategories) { var category = productCategory.Category; - if (category.HasDiscountsApplied) { //we use this property ("HasDiscountsApplied") for performance optimziation to avoid unnecessary database calls @@ -116,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); } @@ -126,6 +140,39 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer } } + // check discounts assigned to manufacturers + var discountsAssignedToManufacturers = _discountService.GetAllDiscounts(DiscountType.AssignedToManufacturers); + + if (discountsAssignedToManufacturers != null && discountsAssignedToManufacturers.Any()) + { + IEnumerable productManufacturers = null; + + if (context == null) + productManufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id); + else + productManufacturers = context.ProductManufacturers.GetOrLoad(product.Id); + + if (productManufacturers != null) + { + foreach (var productManufacturer in productManufacturers) + { + var manu = productManufacturer.Manufacturer; + if (manu.HasDiscountsApplied) + { + var manuDiscounts = manu.AppliedDiscounts; + + foreach (var discount in manuDiscounts) + { + if (discount.DiscountType == DiscountType.AssignedToManufacturers && !result.Contains(discount) && _discountService.IsDiscountValid(discount, customer)) + { + result.Add(discount); + } + } + } + } + } + } + return result; } @@ -154,7 +201,7 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer } else { - tierPrices = context.TierPrices.Load(product.Id) + tierPrices = context.TierPrices.GetOrLoad(product.Id) .RemoveDuplicatedQuantities(); } @@ -173,29 +220,96 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer continue; //save new price - previousPrice = tierPrice.Price; + if (tierPrice.CalculationMethod == TierPriceCalculationMethod.Fixed) + { + previousPrice = tierPrice.Price; + } + else if (tierPrice.CalculationMethod == TierPriceCalculationMethod.Percental) + { + previousPrice = product.Price - (product.Price / 100 * tierPrice.Price); + } + else + { + previousPrice = product.Price - tierPrice.Price; + } + previousQty = tierPrice.Quantity; } return previousPrice; } - protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationContext context, ProductBundleItemData bundleItem, IEnumerable bundleItems) - { - var taxRate = decimal.Zero; + protected virtual decimal GetTierPriceAttributeAdjustment(Product product, Customer customer, int quantity, PriceCalculationContext context = null, decimal adjustment = 0) + { + + if (!product.HasTierPrices) + return decimal.Zero; + + IEnumerable tierPrices = null; + + if (context == null) + { + tierPrices = product.TierPrices + .OrderBy(tp => tp.Quantity) + .FilterByStore(_services.StoreContext.CurrentStore.Id) + .FilterForCustomer(customer) + .ToList() + .RemoveDuplicatedQuantities(); + } + else + { + tierPrices = context.TierPrices.GetOrLoad(product.Id) + .RemoveDuplicatedQuantities(); + } + + if (tierPrices == null) + return decimal.Zero; + + int previousQty = 1; + decimal previousPrice = 0; + + foreach (var tierPrice in tierPrices) + { + //check quantity + if (quantity < tierPrice.Quantity) + continue; + if (tierPrice.Quantity < previousQty) + continue; + + //save new price + if (tierPrice.CalculationMethod == TierPriceCalculationMethod.Percental && _catalogSettings.ApplyTierPricePercentageToAttributePriceAdjustments) + { + previousPrice = adjustment - (adjustment / 100 * tierPrice.Price); + } + + previousQty = tierPrice.Quantity; + } + + return previousPrice; + } + + protected virtual decimal GetPreselectedPrice( + Product product, + Customer customer, + Currency currency, + PriceCalculationContext context, + ProductBundleItemData bundleItem, + IEnumerable bundleItems) + { + 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 selectedAttributes = new NameValueCollection(); + var query = new ProductVariantQuery(); var selectedAttributeValues = new List(); - var attributes = context.Attributes.Load(product.Id); + var attributes = context.Attributes.GetOrLoad(product.Id); - // 1. fill selectedAttributes with initially selected attributes - foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Count > 0 && x.ShouldHaveValues())) + // 1. Fill query with initially selected attributes. + foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Any() && x.ShouldHaveValues())) { int preSelectedValueId = 0; ProductVariantAttributeValue defaultValue = null; @@ -204,9 +318,9 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC 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) @@ -214,37 +328,55 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC if (!isBundlePricing && pvaValue.IsPreSelected) { - decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue); - decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate); + var includingTax = _services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue, product, customer, context, 1); + var priceAdjustmentBase = _taxService.GetProductPrice(product, product.TaxCategoryId, attributeValuePriceAdjustment, + includingTax, customer, currency, _taxSettings.PricesIncludeTax, out taxRate); preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase); } } - // value pre-selected by a bundle item filter discards the default pre-selection + // Value pre-selected by a bundle item filter discards the default pre-selection. if (preSelectedValueId != 0 && (defaultValue = pvaValues.FirstOrDefault(x => x.Id == preSelectedValueId)) != null) { //defaultValue.IsPreSelected = true; selectedAttributeValues.Add(defaultValue); - selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId); + query.AddVariant(new ProductVariantQueryItem(defaultValue.Id.ToString()) + { + ProductId = product.Id, + BundleItemId = bundleItemId, + AttributeId = attribute.ProductAttributeId, + VariantAttributeId = attribute.Id, + Alias = attribute.ProductAttribute.Alias, + ValueAlias = defaultValue.Alias + }); } else { foreach (var value in pvaValues.Where(x => x.IsPreSelected)) { selectedAttributeValues.Add(value); - selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, value.Id, product.Id, bundleItemId); + query.AddVariant(new ProductVariantQueryItem(value.Id.ToString()) + { + ProductId = product.Id, + BundleItemId = bundleItemId, + AttributeId = attribute.ProductAttributeId, + VariantAttributeId = attribute.Id, + Alias = attribute.ProductAttribute.Alias, + ValueAlias = value.Alias + }); } } } - // 2. find attribute combination for selected attributes and merge it - if (!isBundle && selectedAttributes.Count > 0) + // 2. Find attribute combination for selected attributes and merge it. + if (!isBundle && query.Variants.Any()) { - var attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization, - _downloadService, _catalogSettings, _httpRequestBase, new List(), true, bundleItemId); + var attributeXml = query.CreateSelectedAttributesXml(product.Id, bundleItemId, attributes, _productAttributeParser, _services.Localization, + _downloadService, _catalogSettings, _httpRequestBase, new List()); - var combinations = context.AttributeCombinations.Load(product.Id); + var combinations = context.AttributeCombinations.GetOrLoad(product.Id); var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml)); @@ -264,7 +396,7 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC { if (selectedAttributeValues.Count > 0) { - selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x)); + selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x, product, customer, context, 1)); } else { @@ -277,18 +409,30 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC bundleItem.AdditionalCharge = attributesTotalPriceBase; } - var result = GetFinalPrice(product, bundleItems, _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem, context); + var result = GetFinalPrice(product, bundleItems, customer, attributesTotalPriceBase, true, 1, bundleItem, context); return result; } - public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable products = null) + public virtual PriceCalculationContext CreatePriceCalculationContext( + IEnumerable products = null, + Customer customer = null, + int? storeId = null) { + if (customer == null) + customer = _services.WorkContext.CurrentCustomer; + + if (!storeId.HasValue) + storeId = _services.StoreContext.CurrentStore.Id; + var context = new PriceCalculationContext(products, x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), - x => _productService.GetTierPricesByProductIds(x, _services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id), + x => _productService.GetTierPricesByProductIds(x, customer, storeId.Value), x => _categoryService.GetProductCategoriesByProductIds(x, true), - x => _productService.GetAppliedDiscountsByProductIds(x) + x => _manufacturerService.GetProductManufacturersByProductIds(x), + x => _productService.GetAppliedDiscountsByProductIds(x), + x => _productService.GetBundleItemsByProductIds(x, true), + x => _productService.GetAssociatedProductsByProductIds(x) ); return context; @@ -335,8 +479,7 @@ public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable /// 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); @@ -349,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); } @@ -379,29 +520,43 @@ public virtual decimal GetFinalPrice( bool includeDiscounts, int quantity, ProductBundleItemData bundleItem = null, - PriceCalculationContext context = null) - { - //initial price + PriceCalculationContext context = null, + bool isTierPrice = false) + { + // Initial price. decimal result = product.Price; - //special price + // Special price. var specialPrice = GetSpecialPrice(product); if (specialPrice.HasValue) + { result = specialPrice.Value; + } - //tier prices - if (product.HasTierPrices && !bundleItem.IsValid()) + if (isTierPrice) { - decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context); - if (tierPrice.HasValue) - result = Math.Min(result, tierPrice.Value); + includeDiscounts = true; } - //discount + additional charge + // Tier prices. + if (product.HasTierPrices && includeDiscounts && !(bundleItem != null && bundleItem.Item != null)) + { + 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) + { + includeDiscounts = false; + result = Math.Min(result, tierPrice.Value); + } + } + + // 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 @@ -410,7 +565,9 @@ public virtual decimal GetFinalPrice( } if (result < decimal.Zero) + { result = decimal.Zero; + } return result; } @@ -427,46 +584,46 @@ public virtual decimal GetFinalPrice( { if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { - decimal result = decimal.Zero; - var items = bundleItems ?? _productService.GetBundleItems(product.Id); + var result = decimal.Zero; + var items = bundleItems; - foreach (var itemData in items.Where(x => x.IsValid())) + if (items == null) { - decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); + items = context == null + ? _productService.GetBundleItems(product.Id) + : context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)).ToList(); + } - result = result + decimal.Multiply(itemPrice, itemData.Item.Quantity); + foreach (var itemData in items.Where(x => x != null && x.Item != null)) + { + var itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); + + 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); } - /// - /// Get the lowest possible price for a product. - /// - /// Product - /// Object with cargo data for better performance - /// Whether to display the from message. - /// The lowest price. - public virtual decimal GetLowestPrice(Product product, PriceCalculationContext context, out bool displayFromMessage) + 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(); - - bool 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, _services.WorkContext.CurrentCustomer, decimal.Zero, true, int.MaxValue, null, context); + displayFromMessage = isBundlePerItemPricing; if (product.LowestAttributeCombinationPrice.HasValue && product.LowestAttributeCombinationPrice.Value < lowestPrice) { @@ -481,13 +638,13 @@ public virtual decimal GetLowestPrice(Product product, PriceCalculationContext c if (!displayFromMessage && product.ProductType != ProductType.BundledProduct) { - var attributes = context.Attributes.Load(product.Id); + var attributes = context.Attributes.GetOrLoad(product.Id); displayFromMessage = attributes.Any(x => x.ProductVariantAttributeValues.Any(y => y.PriceAdjustment != decimal.Zero)); } if (!displayFromMessage && product.HasTierPrices && !isBundlePerItemPricing) { - var tierPrices = context.TierPrices.Load(product.Id) + var tierPrices = context.TierPrices.GetOrLoad(product.Id) .RemoveDuplicatedQuantities(); displayFromMessage = (tierPrices.Count > 0 && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1)); @@ -496,7 +653,12 @@ public virtual decimal GetLowestPrice(Product product, PriceCalculationContext c return lowestPrice; } - public virtual decimal? GetLowestPrice(Product product, PriceCalculationContext context, IEnumerable associatedProducts, out Product lowestPriceProduct) + public virtual decimal? GetLowestPrice( + Product product, + Customer customer, + PriceCalculationContext context, + IEnumerable associatedProducts, + out Product lowestPriceProduct) { if (product == null) throw new ArgumentNullException("product"); @@ -509,10 +671,11 @@ public virtual decimal GetLowestPrice(Product product, PriceCalculationContext c lowestPriceProduct = null; decimal? lowestPrice = null; - var customer = _services.WorkContext.CurrentCustomer; - if (context == null) - context = CreatePriceCalculationContext(); + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } foreach (var associatedProduct in associatedProducts) { @@ -530,49 +693,48 @@ public virtual decimal GetLowestPrice(Product product, PriceCalculationContext c } } - if (lowestPriceProduct == null) - lowestPriceProduct = associatedProducts.FirstOrDefault(); + if (lowestPriceProduct == null) + { + lowestPriceProduct = associatedProducts.FirstOrDefault(); + } return lowestPrice; } - /// - /// Get the initial price including preselected attributes - /// - /// Product - /// Object with cargo data for better performance - /// Preselected price - public virtual decimal GetPreselectedPrice(Product product, PriceCalculationContext context) + 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(); - - 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, 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, context, null, bundleItems); - } - else - { - result = GetPreselectedPrice(product, context, null, null); - } + result = GetPreselectedPrice(product, customer, currency, context, null, bundleItems); + } + else + { + result = GetPreselectedPrice(product, customer, currency, context, null, null); + } return result; } @@ -640,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); } /// @@ -673,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) { @@ -753,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); } @@ -763,7 +926,6 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, _productAttributeParser); var attributesTotalPrice = decimal.Zero; - var pvaValuesEnum = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); if (pvaValuesEnum != null) @@ -772,7 +934,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, foreach (var pvaValue in pvaValues) { - attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); + attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue, product, customer, null, shoppingCartItem.Item.Quantity); } } @@ -780,9 +942,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, } } - if (_shoppingCartSettings.RoundPricesDuringCalculation) - finalPrice = Math.Round(finalPrice, 2); - + finalPrice = finalPrice.RoundIfEnabledFor(_services.WorkContext.WorkingCurrency); return finalPrice; } @@ -795,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); } /// @@ -807,26 +966,28 @@ public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartI /// Discount amount public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartItem, out Discount appliedDiscount) { - var customer = shoppingCartItem.Item.Customer; - appliedDiscount = null; - decimal totalDiscountAmount = decimal.Zero; + appliedDiscount = null; + + var customer = shoppingCartItem.Item.Customer; + var totalDiscountAmount = decimal.Zero; var product = shoppingCartItem.Item.Product; + var quantity = shoppingCartItem.Item.Quantity; + if (product != null) { - decimal attributesTotalPrice = decimal.Zero; - + var attributesTotalPrice = decimal.Zero; var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); + foreach (var pvaValue in pvaValues) { - attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); + attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue, product, customer, null, quantity); } - decimal productDiscountAmount = GetDiscountAmount(product, customer, attributesTotalPrice, shoppingCartItem.Item.Quantity, out appliedDiscount); - totalDiscountAmount = productDiscountAmount * shoppingCartItem.Item.Quantity; + var productDiscountAmount = GetDiscountAmount(product, customer, attributesTotalPrice, quantity, out appliedDiscount); + totalDiscountAmount = productDiscountAmount * quantity; } - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - totalDiscountAmount = Math.Round(totalDiscountAmount, 2); + + totalDiscountAmount = totalDiscountAmount.RoundIfEnabledFor(_services.WorkContext.WorkingCurrency); return totalDiscountAmount; } @@ -836,15 +997,26 @@ public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartI /// /// Product variant attribute value /// Price adjustment of a variant attribute value - public virtual decimal GetProductVariantAttributeValuePriceAdjustment(ProductVariantAttributeValue attributeValue) + 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) - return attributeValue.PriceAdjustment; - - if (attributeValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) + { + if (productQuantity > 1 && attributeValue.PriceAdjustment > 0) + { + var tierPriceAttributeAdjustment = GetTierPriceAttributeAdjustment(product, customer, productQuantity, context, attributeValue.PriceAdjustment); + if (tierPriceAttributeAdjustment != 0) + { + return tierPriceAttributeAdjustment; + } + } + + return attributeValue.PriceAdjustment; + } + + if (attributeValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) { var linkedProduct = _productService.GetProductById(attributeValue.LinkedProductId); @@ -854,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 b53310d921..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,50 +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); - - string currencyString = GetCurrencyString(price, showCurrency, targetCurrency); - 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; - } + 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 + 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); } @@ -240,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 2737620d3f..7ce8def364 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeExtensions.cs @@ -1,4 +1,3 @@ - using System; using System.Diagnostics; using System.Xml; @@ -6,10 +5,7 @@ namespace SmartStore.Services.Catalog { - /// - /// Extensions - /// - public static class ProductAttributeExtensions + public static class ProductAttributeExtensions { /// /// A value indicating whether this product variant attribute should have values @@ -27,10 +23,10 @@ public static bool ShouldHaveValues(this ProductVariantAttribute productVariantA productVariantAttribute.AttributeControlType == AttributeControlType.FileUpload) return false; - //other attribute controle types support values + // All other attribute control types support values. return true; } - + public static string AddProductAttribute(this ProductVariantAttribute pva, string attributes, string value) { string result = string.Empty; @@ -91,5 +87,5 @@ public static string AddProductAttribute(this ProductVariantAttribute pva, strin } return result; } - } + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs index 5bbbb5665a..7d3c6db822 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs @@ -13,10 +13,10 @@ namespace SmartStore.Services.Catalog { - /// - /// Product attribute formatter - /// - public partial class ProductAttributeFormatter : IProductAttributeFormatter + /// + /// Product attribute formatter + /// + public partial class ProductAttributeFormatter : IProductAttributeFormatter { private readonly IWorkContext _workContext; private readonly IProductAttributeService _productAttributeService; @@ -29,8 +29,9 @@ public partial class ProductAttributeFormatter : IProductAttributeFormatter private readonly IDownloadService _downloadService; private readonly IWebHelper _webHelper; private readonly ShoppingCartSettings _shoppingCartSettings; + private readonly CatalogSettings _catalogSettings; - public ProductAttributeFormatter(IWorkContext workContext, + public ProductAttributeFormatter(IWorkContext workContext, IProductAttributeService productAttributeService, IProductAttributeParser productAttributeParser, IPriceCalculationService priceCalculationService, @@ -40,19 +41,21 @@ public ProductAttributeFormatter(IWorkContext workContext, IPriceFormatter priceFormatter, IDownloadService downloadService, IWebHelper webHelper, - ShoppingCartSettings shoppingCartSettings) + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings) { - this._workContext = workContext; - this._productAttributeService = productAttributeService; - this._productAttributeParser = productAttributeParser; - this._priceCalculationService = priceCalculationService; - this._currencyService = currencyService; - this._localizationService = localizationService; - this._taxService = taxService; - this._priceFormatter = priceFormatter; - this._downloadService = downloadService; - this._webHelper = webHelper; - this._shoppingCartSettings = shoppingCartSettings; + _workContext = workContext; + _productAttributeService = productAttributeService; + _productAttributeParser = productAttributeParser; + _priceCalculationService = priceCalculationService; + _currencyService = currencyService; + _localizationService = localizationService; + _taxService = taxService; + _priceFormatter = priceFormatter; + _downloadService = downloadService; + _webHelper = webHelper; + _shoppingCartSettings = shoppingCartSettings; + _catalogSettings = catalogSettings; } /// @@ -86,8 +89,9 @@ public string FormatAttributes(Product product, string attributes, bool allowHyperlinks = true) { var result = new StringBuilder(); + var languageId = _workContext.WorkingLanguage.Id; - //attributes + // Attributes if (renderProductAttributes) { var pvaCollection = _productAttributeParser.ParseProductVariantAttributes(attributes); @@ -105,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, _workContext.WorkingLanguage.Id); + 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) @@ -139,7 +143,7 @@ public string FormatAttributes(Product product, string attributes, //hyperlinks aren't allowed attributeText = fileName; } - var attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + string attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId); //encode (if required) if (htmlEncode) attributeName = HttpUtility.HtmlEncode(attributeName); @@ -149,7 +153,7 @@ public string FormatAttributes(Product product, string attributes, else { //other attributes (textbox, datepicker) - pvaAttribute = string.Format("{0}: {1}", pva.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), valueStr); + pvaAttribute = string.Format("{0}: {1}", pva.ProductAttribute.GetLocalized(a => a.Name, languageId), valueStr); //encode (if required) if (htmlEncode) pvaAttribute = HttpUtility.HtmlEncode(pvaAttribute); @@ -157,20 +161,21 @@ public string FormatAttributes(Product product, string attributes, } else { - //attributes with values + // Attributes with values. int pvaId = 0; if (int.TryParse(valueStr, out pvaId)) { var pvaValue = _productAttributeService.GetProductVariantAttributeValueById(pvaId); if (pvaValue != null) { - pvaAttribute = string.Format("{0}: {1}", pva.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), - pvaValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); + pvaAttribute = "{0}: {1}".FormatInvariant( + pva.ProductAttribute.GetLocalized(a => a.Name, languageId), + pvaValue.GetLocalized(a => a.Name, languageId)); if (renderPrices) { decimal taxRate = decimal.Zero; - decimal attributeValuePriceAdjustment = _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(pvaValue); + decimal attributeValuePriceAdjustment = _priceCalculationService.GetProductVariantAttributeValuePriceAdjustment(pvaValue, product, customer, null, 1); decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, customer, out taxRate); decimal priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); @@ -180,21 +185,25 @@ public string FormatAttributes(Product product, string attributes, pvaAttribute += string.Format(" {0}", pvaValue.Quantity); } - if (priceAdjustmentBase > 0) - { - string priceAdjustmentStr = _priceFormatter.FormatPrice(priceAdjustment, true, false); - pvaAttribute += string.Format(" [+{0}]", priceAdjustmentStr); - } - else if (priceAdjustmentBase < decimal.Zero) - { - string priceAdjustmentStr = _priceFormatter.FormatPrice(-priceAdjustment, true, false); - pvaAttribute += string.Format(" [-{0}]", priceAdjustmentStr); - } + if (_catalogSettings.ShowVariantCombinationPriceAdjustment) + { + if (priceAdjustmentBase > 0) + { + pvaAttribute += " (+{0})".FormatInvariant(_priceFormatter.FormatPrice(priceAdjustment, true, false)); + } + else if (priceAdjustmentBase < decimal.Zero) + { + pvaAttribute += " (-{0})".FormatInvariant(_priceFormatter.FormatPrice(-priceAdjustment, true, false)); + } + } } } - //encode (if required) - if (htmlEncode) - pvaAttribute = HttpUtility.HtmlEncode(pvaAttribute); + + // Encode (if required) + if (htmlEncode) + { + pvaAttribute = HttpUtility.HtmlEncode(pvaAttribute); + } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index d338d23056..7c6f33ce38 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -1,31 +1,27 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; using System.Xml; using System.Xml.Linq; -using Newtonsoft.Json; using SmartStore.Collections; using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Localization; +using SmartStore.Core.Logging; namespace SmartStore.Services.Catalog { - /// - /// Product attribute parser - /// - public partial class ProductAttributeParser : IProductAttributeParser + public partial class ProductAttributeParser : IProductAttributeParser { - // 0 = ProductId, 1 = AttributeXml Hash - private const string ATTRIBUTECOMBINATION_BY_ID_HASH = "SmartStore.parsedattributecombination.id-{0}-{1}"; + // 0 = ProductId, 1 = AttributeXml + private const string ATTRIBUTECOMBINATION_BY_IDXML_KEY = "parsedattributecombination.id-{0}-{1}"; - private readonly IProductAttributeService _productAttributeService; + // 0 = AttributeXml + private const string ATTRIBUTEVALUES_BY_XML_KEY = "parsedattributevalues-{0}"; + private const string ATTRIBUTEVALUES_PATTERN_KEY = "parsedattributevalues-*"; + + private readonly IProductAttributeService _productAttributeService; private readonly IRepository _pvacRepository; private readonly IRequestCache _requestCache; @@ -37,83 +33,140 @@ public ProductAttributeParser( _productAttributeService = productAttributeService; _pvacRepository = pvacRepository; _requestCache = requestCache; - } - #region Product attributes + Logger = NullLogger.Instance; + } - /// - /// Gets selected product variant attribute identifiers - /// - /// Attributes - /// Selected product variant attribute identifiers - private IEnumerable ParseProductVariantAttributeIds(string attributesXml) - { - var ids = new List(); - if (String.IsNullOrEmpty(attributesXml)) - yield break; + public ILogger Logger { get; set; } - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); + #region Product attributes - var nodeList = xmlDoc.SelectNodes(@"//Attributes/ProductVariantAttribute"); - foreach (var node in nodeList.Cast()) - { - string sid = node.GetAttribute("ID").Trim(); - if (sid.HasValue()) - { - int id = 0; - if (int.TryParse(sid, out id)) - { - yield return id; - } - } - } + public virtual int PrefetchProductVariantAttributes(IEnumerable attributesXml) + { + if (attributesXml == null || !attributesXml.Any()) + { + return 0; } - finally { } + + // Determine uncached attributes + var unfetched = attributesXml + .Where(xml => xml.HasValue()) + .Distinct() + .Where(xml => !_requestCache.Contains(ATTRIBUTEVALUES_BY_XML_KEY.FormatInvariant(xml))) + .ToArray(); + + var infos = new List(unfetched.Length); + + foreach (var xml in unfetched) + { + var valueIds = new HashSet(); + var map = DeserializeProductVariantAttributes(xml); + var attributes = _productAttributeService.GetProductVariantAttributesByIds(map.Keys); + + foreach (var attribute in attributes) + { + // Only types that have attribute values! Otherwise entered text is misinterpreted as an attribute value id. + if (!attribute.ShouldHaveValues()) + continue; + + var ids = + from id in map[attribute.Id] + where id.HasValue() + select id.ToInt(); + + valueIds.UnionWith(ids); + } + + var info = new AttributeMapInfo + { + AttributesXml = xml, + DeserializedMap = map, + AllValueIds = valueIds.ToArray() + }; + + infos.Add(info); + } + + // Get all value ids across all maps (each map has many attributes) + var allValueIds = infos.SelectMany(x => x.AllValueIds) + .Distinct() + .ToArray(); + + // Load ALL requested attribute values into a single dictionary in one go (key is Id) + var attributeValues = _productAttributeService.GetProductVariantAttributeValuesByIds(allValueIds).ToDictionarySafe(x => x.Id); + + // Create a single cache entry for each passed xml + foreach (var info in infos) + { + var cachedValues = new List(); + + // Ensure value id order in cached result list is correct + foreach (var id in info.AllValueIds) + { + if (attributeValues.TryGetValue(id, out var value)) + { + cachedValues.Add(value); + } + } + + // Put it in cache + var cacheKey = ATTRIBUTEVALUES_BY_XML_KEY.FormatInvariant(info.AttributesXml); + _requestCache.Put(cacheKey, cachedValues); + } + + return unfetched.Length; } - //public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) - //{ - // var attrs = new Multimap(); - // if (String.IsNullOrEmpty(attributesXml)) - // return attrs; - - // try - // { - // var xmlDoc = new XmlDocument(); - // xmlDoc.LoadXml(attributesXml); - - // var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductVariantAttribute"); - // foreach (var node1 in nodeList1.Cast()) - // { - // string sid = node1.GetAttribute("ID").Trim(); - // if (sid.HasValue()) - // { - // int id = 0; - // if (int.TryParse(sid, out id)) - // { - - // var nodeList2 = node1.SelectNodes(@"ProductVariantAttributeValue/Value").Cast(); - // foreach (var node2 in nodeList2) - // { - // string value = node2.InnerText.Trim(); - // attrs.Add(id, value); - // } - // } - // } - // } - // } - // catch (Exception exc) - // { - // Debug.Write(exc.ToString()); - // } - - // return attrs; - //} - - public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) + public virtual IList ParseProductVariantAttributes(string attributesXml) + { + var values = ParseProductVariantAttributeValues(attributesXml); + var attrMap = DeserializeProductVariantAttributes(attributesXml); + var attrs = _productAttributeService.GetProductVariantAttributesByIds(attrMap.Keys, values.Select(x => x.ProductVariantAttribute).Distinct().ToList()); + + return attrs; + } + + public virtual IEnumerable ParseProductVariantAttributeValues(string attributeXml) + { + if (attributeXml.IsEmpty()) + return new List(); + + var cacheKey = ATTRIBUTEVALUES_BY_XML_KEY.FormatInvariant(attributeXml); + + var result = _requestCache.Get(cacheKey, () => + { + var allValueIds = new HashSet(); + var attrMap = DeserializeProductVariantAttributes(attributeXml); + var attributes = _productAttributeService.GetProductVariantAttributesByIds(attrMap.Keys); + + foreach (var attribute in attributes) + { + // Only types that have attribute values! Otherwise entered text is misinterpreted as an attribute value id. + if (!attribute.ShouldHaveValues()) + continue; + + var ids = + from id in attrMap[attribute.Id] + where id.HasValue() + select id.ToInt(); + + allValueIds.UnionWith(ids); + } + + var values = _productAttributeService.GetProductVariantAttributeValuesByIds(allValueIds.ToArray()); + + return values; + }); + + return result; + } + + public virtual void ClearCachedAttributeValues() + { + _requestCache.RemoveByPattern(ATTRIBUTEVALUES_PATTERN_KEY); + } + + public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) { var attrs = new Multimap(); if (String.IsNullOrEmpty(attributesXml)) @@ -129,8 +182,7 @@ public virtual Multimap DeserializeProductVariantAttributes(string string sid = node1.Attribute("ID").Value; if (sid.HasValue()) { - int id = 0; - if (int.TryParse(sid, out id)) + if (int.TryParse(sid, out var id)) { // ProductVariantAttributeValue/Value foreach (var node2 in node1.Descendants("Value")) @@ -141,106 +193,51 @@ public virtual Multimap DeserializeProductVariantAttributes(string } } } - catch (Exception exc) + catch (Exception exception) { - Debug.Write(exc.ToString()); + Logger.Error(exception); } return attrs; } - /// - /// Gets selected product variant attributes - /// - /// Attributes - /// Selected product variant attributes - public virtual IList ParseProductVariantAttributes(string attributesXml) - { - var ids = ParseProductVariantAttributeIds(attributesXml); - - return _productAttributeService.GetProductVariantAttributesByIds(ids.ToList()); - } - - public virtual IEnumerable ParseProductVariantAttributeValues(string attributeXml) - { - //var pvaValues = Enumerable.Empty(); - - var allIds = new List(); - var attrs = DeserializeProductVariantAttributes(attributeXml); - var pvaCollection = _productAttributeService.GetProductVariantAttributesByIds(attrs.Keys); - - foreach (var pva in pvaCollection) - { - if (!pva.ShouldHaveValues()) - continue; - - var pvaValuesStr = attrs[pva.Id]; - - var ids = - from id in pvaValuesStr - where id.HasValue() - select id.ToInt(); - - allIds.AddRange(ids); - - //var values = _productAttributeService.GetProductVariantAttributeValuesByIds(ids.ToArray()); - //pvaValues = pvaValues.Concat(values); - } - - int[] allDistinctIds = allIds.Distinct().ToArray(); - - var values = _productAttributeService.GetProductVariantAttributeValuesByIds(allDistinctIds); - - return values; - } - - public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0) + public virtual ICollection ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes) { - var values = new List(); + var result = new HashSet(); if (attributeCombination == null || !attributeCombination.Any()) - return values; + return result; - var allValueIds = new List(); + var allValueIds = new HashSet(); - foreach (var pva in attributes.Where(x => x.ShouldHaveValues()).OrderBy(x => x.DisplayOrder)) + foreach (var pva in attributes.Where(x => x.ShouldHaveValues()).OrderBy(x => x.DisplayOrder).ToArray()) { if (attributeCombination.ContainsKey(pva.Id)) { var pvaValuesStr = attributeCombination[pva.Id]; var ids = pvaValuesStr.Where(x => x.HasValue()).Select(x => x.ToInt()); - allValueIds.AddRange(ids); + allValueIds.UnionWith(ids); } } - foreach (int id in allValueIds.Distinct()) + foreach (int id in allValueIds) { foreach (var attribute in attributes) { var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(x => x.Id == id); if (attributeValue != null) { - var value = attributeValue.GetLocalized(x => x.Name, languageId, true, false); - - if (!values.Any(x => x.IsCaseInsensitiveEqual(value))) - values.Add(value); + result.Add(attributeValue); break; } } } - return values; + return result; } - - /// - /// Gets selected product variant attribute value - /// - /// Attributes - /// Product variant attribute identifier - /// Product variant attribute value - public virtual IList ParseValues(string attributesXml, int productVariantAttributeId) + public virtual IList ParseValues(string attributesXml, int productVariantAttributeId) { var selectedProductVariantAttributeValues = new List(); try @@ -254,8 +251,7 @@ public virtual IList ParseValues(string attributesXml, int productVarian if (node1.Attributes != null && node1.Attributes["ID"] != null) { string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id = 0; - if (int.TryParse(str1, out id)) + if (int.TryParse(str1, out var id)) { if (id == productVariantAttributeId) { @@ -270,29 +266,48 @@ public virtual IList ParseValues(string attributesXml, int productVarian } } } - catch (Exception exc) + catch (Exception exception) { - Debug.Write(exc.ToString()); - } + Logger.Error(exception); + } return selectedProductVariantAttributeValues; } - /// - /// Adds an attribute - /// - /// Attributes - /// Product variant attribute - /// Value - /// Attributes public virtual string AddProductAttribute(string attributesXml, ProductVariantAttribute pva, string value) { return pva.AddProductAttribute(attributesXml, value); } + public virtual string CreateAttributesXml(Multimap attributes) + { + Guard.NotNull(attributes, nameof(attributes)); + + if (attributes.Count == 0) + return null; + + var doc = new XmlDocument(); + var root = doc.AppendChild(doc.CreateElement("Attributes")); + + foreach (var attr in attributes) + { + var xelAttr = root.AppendChild(doc.CreateElement("ProductVariantAttribute")) as XmlElement; + xelAttr.SetAttribute("ID", attr.Key.ToString()); + + foreach (var val in attr.Value) + { + var xelAttrValue = xelAttr.AppendChild(doc.CreateElement("ProductVariantAttributeValue")); + var xelValue = xelAttrValue.AppendChild(doc.CreateElement("Value")); + xelValue.InnerText = val; + } + } + + return doc.OuterXml; + } + public virtual bool AreProductAttributesEqual(string attributeXml1, string attributeXml2) { - if (attributeXml1.IsCaseInsensitiveEqual(attributeXml2)) + if (attributeXml1 == attributeXml2) return true; var attributes1 = DeserializeProductVariantAttributes(attributeXml1); @@ -334,15 +349,12 @@ public virtual bool AreProductAttributesEqual(string attributeXml1, string attri return true; } - public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination( - int productId, - string attributesXml) + public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml) { if (attributesXml.IsEmpty()) return null; - var attributesHash = attributesXml.Hash(Encoding.UTF8); - var cacheKey = ATTRIBUTECOMBINATION_BY_ID_HASH.FormatInvariant(productId, attributesHash); + var cacheKey = ATTRIBUTECOMBINATION_BY_IDXML_KEY.FormatInvariant(productId, attributesXml); var result = _requestCache.Get(cacheKey, () => { @@ -371,128 +383,29 @@ public virtual ProductVariantAttributeCombination FindProductVariantAttributeCom return result; } - public virtual List> DeserializeQueryData(string jsonData) - { - try - { - if (jsonData.HasValue()) - { - if (jsonData.StartsWith("[")) - { - return JsonConvert.DeserializeObject>>(jsonData); - } - - return new List> { JsonConvert.DeserializeObject>(jsonData) }; - } - } - catch { } - - return new List>(); - } - - public virtual void DeserializeQueryData(List> queryData, string attributesXml, int productId, int bundleItemId = 0) - { - Guard.ArgumentNotNull(() => queryData); - - if (attributesXml.HasValue() && productId != 0) - { - var attributeValues = ParseProductVariantAttributeValues(attributesXml).ToList(); - - foreach (var value in attributeValues) - { - var lst = new List - { - productId, - value.ProductVariantAttribute.ProductAttributeId, - value.ProductVariantAttributeId, - value.Id - }; - - if (bundleItemId != 0) - lst.Add(bundleItemId); - - queryData.Add(lst); - } - } - } - - public virtual string SerializeQueryData(string attributesXml, int productId, bool urlEncode = true) - { - var data = new List>(); + #endregion - DeserializeQueryData(data, attributesXml, productId); + #region Gift card attributes - return SerializeQueryData(data, urlEncode); - } - - public virtual string SerializeQueryData(List> queryData, bool urlEncode = true) - { - if (queryData.Count > 0) - { - var result = JsonConvert.SerializeObject(queryData); - - return (urlEncode ? HttpUtility.UrlEncode(result) : result); - } - - return ""; - } - - private string CreateProductUrl(string queryString, string productSeName) - { - var url = UrlHelper.GenerateUrl( - "Product", - null, - null, - new RouteValueDictionary(new { SeName = productSeName }), - RouteTable.Routes, - HttpContext.Current.Request.RequestContext, - false); - - if (queryString.HasValue()) - { - url = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", queryString); - } - - return url; - } - - public virtual string GetProductUrlWithAttributes(string attributesXml, int productId, string productSeName) - { - return CreateProductUrl(SerializeQueryData(attributesXml, productId), productSeName); - } - - public virtual string GetProductUrlWithAttributes(List> queryData, string productSeName) - { - return CreateProductUrl(SerializeQueryData(queryData), productSeName); - } - - #endregion - - #region Gift card attributes - - /// - /// Add gift card attrbibutes - /// - /// Attributes - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - /// Attributes - public string AddGiftCardAttribute(string attributesXml, string recipientName, - string recipientEmail, string senderName, string senderEmail, string giftCardMessage) + public string AddGiftCardAttribute( + string attributesXml, + string recipientName, + string recipientEmail, + string senderName, + string senderEmail, + string giftCardMessage) { - string result = string.Empty; + var result = string.Empty; + try { - recipientName = recipientName.Trim(); - recipientEmail = recipientEmail.Trim(); - senderName = senderName.Trim(); - senderEmail = senderEmail.Trim(); + recipientName = recipientName.TrimSafe(); + recipientEmail = recipientEmail.TrimSafe(); + senderName = senderName.TrimSafe(); + senderEmail = senderEmail.TrimSafe(); var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) + if (string.IsNullOrEmpty(attributesXml)) { var element1 = xmlDoc.CreateElement("Attributes"); xmlDoc.AppendChild(element1); @@ -533,25 +446,21 @@ public string AddGiftCardAttribute(string attributesXml, string recipientName, result = xmlDoc.OuterXml; } - catch (Exception exc) + catch (Exception exception) { - Debug.Write(exc.ToString()); + Logger.Error(exception); } + return result; } - /// - /// Get gift card attrbibutes - /// - /// Attributes - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - public void GetGiftCardAttribute(string attributesXml, out string recipientName, - out string recipientEmail, out string senderName, - out string senderEmail, out string giftCardMessage) + public void GetGiftCardAttribute( + string attributesXml, + out string recipientName, + out string recipientEmail, + out string senderName, + out string senderEmail, + out string giftCardMessage) { recipientName = string.Empty; recipientEmail = string.Empty; @@ -581,12 +490,19 @@ public void GetGiftCardAttribute(string attributesXml, out string recipientName, if (messageElement != null) giftCardMessage = messageElement.InnerText; } - catch (Exception exc) + catch (Exception exception) { - Debug.Write(exc.ToString()); - } + Logger.Error(exception); + } } #endregion + + class AttributeMapInfo + { + public string AttributesXml { get; set; } + public Multimap DeserializedMap { get; set; } + public int[] AllValueIds { get; set; } + } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index 3cc5bc3bd0..818343d2ef 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -1,72 +1,73 @@ using System; using System.Collections.Generic; using System.Linq; +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.Localization; +using SmartStore.Core.Domain.Media; using SmartStore.Core.Events; +using SmartStore.Core.Localization; +using SmartStore.Data.Caching; +using SmartStore.Services.Localization; using SmartStore.Services.Media; -using SmartStore.Core; namespace SmartStore.Services.Catalog { - - public partial class ProductAttributeService : IProductAttributeService + public partial class ProductAttributeService : IProductAttributeService { - private const string PRODUCTATTRIBUTES_ALL_KEY = "SmartStore.productattribute.all"; - private const string PRODUCTATTRIBUTES_BY_ID_KEY = "SmartStore.productattribute.id-{0}"; - private const string PRODUCTATTRIBUTES_PATTERN_KEY = "SmartStore.productattribute."; - - private const string PRODUCTVARIANTATTRIBUTES_ALL_KEY = "SmartStore.productvariantattribute.all-{0}"; - private const string PRODUCTVARIANTATTRIBUTES_BY_ID_KEY = "SmartStore.productvariantattribute.id-{0}"; // 0 = ProductId, 1 = PageIndex, 2 = PageSize private const string PRODUCTVARIANTATTRIBUTES_COMBINATIONS_BY_ID_KEY = "SmartStore.productvariantattribute.combinations.id-{0}-{1}-{2}"; - private const string PRODUCTVARIANTATTRIBUTES_PATTERN_KEY = "SmartStore.productvariantattribute."; + private const string PRODUCTVARIANTATTRIBUTES_PATTERN_KEY = "SmartStore.productvariantattribute.*"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_ALL_KEY = "SmartStore.productvariantattributevalue.all-{0}"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_BY_ID_KEY = "SmartStore.productvariantattributevalue.id-{0}"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.productvariantattributevalue."; + // 0 = Attribute value ids, e.g. 16-254-1245 + private const string PRODUCTVARIANTATTRIBUTEVALUES_BY_IDS_KEY = "SmartStore.productvariantattributevalues.ids-{0}"; + private const string PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.productvariantattributevalues*"; private readonly IRepository _productAttributeRepository; - private readonly IRepository _productVariantAttributeRepository; + private readonly IRepository _productAttributeOptionRepository; + private readonly IRepository _productAttributeOptionsSetRepository; + private readonly IRepository _productVariantAttributeRepository; private readonly IRepository _pvacRepository; private readonly IRepository _productVariantAttributeValueRepository; private readonly IRepository _productBundleItemAttributeFilterRepository; - private readonly IEventPublisher _eventPublisher; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly IEventPublisher _eventPublisher; private readonly IRequestCache _requestCache; private readonly IPictureService _pictureService; - - /// - /// Ctor - /// - /// Cache manager - /// Product attribute repository - /// Product variant attribute mapping repository - /// Product variant attribute combination repository - /// Product variant attribute value repository - /// Event published - public ProductAttributeService(IRequestCache requestCache, + public ProductAttributeService( + IRequestCache requestCache, IRepository productAttributeRepository, - IRepository productVariantAttributeRepository, + IRepository productAttributeOptionRepository, + IRepository productAttributeOptionsSetRepository, + IRepository productVariantAttributeRepository, IRepository pvacRepository, IRepository productVariantAttributeValueRepository, IRepository productBundleItemAttributeFilterRepository, - IEventPublisher eventPublisher, + ILocalizedEntityService localizedEntityService, + IEventPublisher eventPublisher, IPictureService pictureService) { _requestCache = requestCache; _productAttributeRepository = productAttributeRepository; + _productAttributeOptionRepository = productAttributeOptionRepository; + _productAttributeOptionsSetRepository = productAttributeOptionsSetRepository; _productVariantAttributeRepository = productVariantAttributeRepository; _pvacRepository = pvacRepository; _productVariantAttributeValueRepository = productVariantAttributeValueRepository; _productBundleItemAttributeFilterRepository = productBundleItemAttributeFilterRepository; + _localizedEntityService = localizedEntityService; _eventPublisher = eventPublisher; _pictureService = pictureService; - } - #region Utilities + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } private IList GetSwitchedLoadedAttributeMappings(ICollection productVariantAttributeIds) { @@ -74,7 +75,7 @@ private IList GetSwitchedLoadedAttributeMappings(IColle { if (productVariantAttributeIds.Count == 1) { - var pva = GetProductVariantAttributeById(productVariantAttributeIds.ElementAt(0)); + var pva = GetProductVariantAttributeById(productVariantAttributeIds.First()); if (pva != null) { return new List { pva }; @@ -82,17 +83,16 @@ private IList GetSwitchedLoadedAttributeMappings(IColle } else { - return _productVariantAttributeRepository.GetMany(productVariantAttributeIds).ToList(); + return _productVariantAttributeRepository + .GetMany(productVariantAttributeIds) + .OrderBy(x => x.DisplayOrder) + .ToList(); } } - + return new List(); } - #endregion - - #region Methods - #region Product attributes public virtual void DeleteProductAttribute(ProductAttribute productAttribute) @@ -103,51 +103,36 @@ public virtual void DeleteProductAttribute(ProductAttribute productAttribute) _productAttributeRepository.Delete(productAttribute); //cache - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(productAttribute); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } public virtual IList GetAllProductAttributes() { - string key = PRODUCTATTRIBUTES_ALL_KEY; - return _requestCache.Get(key, () => - { - var query = from pa in _productAttributeRepository.Table - orderby pa.Name - select pa; - var productAttributes = query.ToList(); - return productAttributes; - }); - } + var query = from pa in _productAttributeRepository.Table + orderby pa.DisplayOrder, pa.Name + select pa; + var productAttributes = query.ToListCached("db.prodattrs.all"); + return productAttributes; + } public virtual ProductAttribute GetProductAttributeById(int productAttributeId) { if (productAttributeId == 0) return null; - string key = string.Format(PRODUCTATTRIBUTES_BY_ID_KEY, productAttributeId); - return _requestCache.Get(key, () => { - return _productAttributeRepository.GetById(productAttributeId); - }); - } + return _productAttributeRepository.GetByIdCached(productAttributeId, "db.prodattr.id-" + productAttributeId); + } public virtual void InsertProductAttribute(ProductAttribute productAttribute) { if (productAttribute == null) throw new ArgumentNullException("productAttribute"); - _productAttributeRepository.Insert(productAttribute); + _productAttributeRepository.Insert(productAttribute); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(productAttribute); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } public virtual void UpdateProductAttribute(ProductAttribute productAttribute) @@ -155,53 +140,191 @@ public virtual void UpdateProductAttribute(ProductAttribute productAttribute) if (productAttribute == null) throw new ArgumentNullException("productAttribute"); - _productAttributeRepository.Update(productAttribute); + _productAttributeRepository.Update(productAttribute); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productAttribute); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } - #endregion + public virtual Multimap GetExportFieldMappings(string fieldPrefix) + { + Guard.NotEmpty(fieldPrefix, nameof(fieldPrefix)); + + var result = new Multimap(StringComparer.OrdinalIgnoreCase); + + if (!fieldPrefix.EndsWith(":")) + { + fieldPrefix = fieldPrefix + ":"; + } - #region Product variant attributes mappings (ProductVariantAttribute) + 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 + + public virtual ProductAttributeOption GetProductAttributeOptionById(int id) + { + if (id == 0) + return null; + + return _productAttributeOptionRepository.GetById(id); + } + + public virtual IList GetProductAttributeOptionsByOptionsSetId(int optionsSetId) + { + if (optionsSetId == 0) + return new List(); + + var entities = _productAttributeOptionRepository.Table + .Where(x => x.ProductAttributeOptionsSetId == optionsSetId) + .OrderBy(x => x.DisplayOrder) + .ThenBy(x => x.Name) + .ToList(); + + return entities; + } + + public virtual IList GetProductAttributeOptionsByAttributeId(int attributeId) + { + if (attributeId == 0) + return new List(); - public virtual void DeleteProductVariantAttribute(ProductVariantAttribute productVariantAttribute) + var entities = + from o in _productAttributeOptionRepository.Table + join os in _productAttributeOptionsSetRepository.Table on o.ProductAttributeOptionsSetId equals os.Id + where os.ProductAttributeId == attributeId + select o; + + return entities.ToList(); + } + + public virtual void DeleteProductAttributeOption(ProductAttributeOption productAttributeOption) + { + Guard.NotNull(productAttributeOption, nameof(productAttributeOption)); + + _productAttributeOptionRepository.Delete(productAttributeOption); + } + + public virtual void InsertProductAttributeOption(ProductAttributeOption productAttributeOption) + { + Guard.NotNull(productAttributeOption, nameof(productAttributeOption)); + + _productAttributeOptionRepository.Insert(productAttributeOption); + } + + public virtual void UpdateProductAttributeOption(ProductAttributeOption productAttributeOption) + { + Guard.NotNull(productAttributeOption, nameof(productAttributeOption)); + + _productAttributeOptionRepository.Update(productAttributeOption); + } + + #endregion + + #region Product attribute options sets + + public virtual ProductAttributeOptionsSet GetProductAttributeOptionsSetById(int id) + { + if (id == 0) + return null; + + return _productAttributeOptionsSetRepository.GetById(id); + } + + public virtual IList GetProductAttributeOptionsSetsByAttributeId(int productAttributeId) + { + if (productAttributeId == 0) + return new List(); + + var entities = _productAttributeOptionsSetRepository.Table + .Where(x => x.ProductAttributeId == productAttributeId) + .OrderBy(x => x.Name) + .ToList(); + + return entities; + } + + public virtual void DeleteProductAttributeOptionsSet(ProductAttributeOptionsSet productAttributeOptionsSet) + { + Guard.NotNull(productAttributeOptionsSet, nameof(productAttributeOptionsSet)); + + _productAttributeOptionsSetRepository.Delete(productAttributeOptionsSet); + } + + public virtual void InsertProductAttributeOptionsSet(ProductAttributeOptionsSet productAttributeOptionsSet) + { + Guard.NotNull(productAttributeOptionsSet, nameof(productAttributeOptionsSet)); + + _productAttributeOptionsSetRepository.Insert(productAttributeOptionsSet); + } + + public virtual void UpdateProductAttributeOptionsSet(ProductAttributeOptionsSet productAttributeOptionsSet) + { + Guard.NotNull(productAttributeOptionsSet, nameof(productAttributeOptionsSet)); + + _productAttributeOptionsSetRepository.Update(productAttributeOptionsSet); + } + + #endregion + + #region Product variant attributes mappings (ProductVariantAttribute) + + public virtual void DeleteProductVariantAttribute(ProductVariantAttribute productVariantAttribute) { if (productVariantAttribute == null) throw new ArgumentNullException("productVariantAttribute"); _productVariantAttributeRepository.Delete(productVariantAttribute); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(productVariantAttribute); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } public virtual IList GetProductVariantAttributesByProductId(int productId) { - string key = string.Format(PRODUCTVARIANTATTRIBUTES_ALL_KEY, productId); + var query = from pva in _productVariantAttributeRepository.Table.Expand(x => x.ProductAttribute) + orderby pva.DisplayOrder + where pva.ProductId == productId + select pva; - return _requestCache.Get(key, () => - { - var query = from pva in _productVariantAttributeRepository.Table - orderby pva.DisplayOrder - where pva.ProductId == productId - select pva; - var productVariantAttributes = query.ToList(); - return productVariantAttributes; - }); - } + var productVariantAttributes = query.ToListCached("db.prodvarattrs.all-" + productId); + return productVariantAttributes; + } public virtual Multimap GetProductVariantAttributesByProductIds(int[] productIds, AttributeControlType? controlType) { - Guard.ArgumentNotNull(() => productIds); + 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) @@ -227,13 +350,8 @@ public virtual ProductVariantAttribute GetProductVariantAttributeById(int produc if (productVariantAttributeId == 0) return null; - string key = string.Format(PRODUCTVARIANTATTRIBUTES_BY_ID_KEY, productVariantAttributeId); - - return _requestCache.Get(key, () => - { - return _productVariantAttributeRepository.GetById(productVariantAttributeId); - }); - } + return _productVariantAttributeRepository.GetByIdCached(productVariantAttributeId, "db.prodvarattr.id-" + productVariantAttributeId); + } public virtual IList GetProductVariantAttributesByIds(IEnumerable productVariantAttributeIds, IEnumerable attributes = null) { @@ -253,11 +371,14 @@ public virtual IList GetProductVariantAttributesByIds(I result.Add(pva); } - var newLoadedMappings = GetSwitchedLoadedAttributeMappings(ids); - - result.AddRange(newLoadedMappings); + if (ids.Count > 0) + { + var newLoadedMappings = GetSwitchedLoadedAttributeMappings(ids); + result.AddRange(newLoadedMappings); + } - return result; + // sort by passed identifier sequence + return result.OrderBySequence(productVariantAttributeIds).ToList(); } return GetSwitchedLoadedAttributeMappings(productVariantAttributeIds.ToList()); @@ -273,22 +394,39 @@ public virtual IEnumerable GetProductVariantAttrib return Enumerable.Empty(); } - return _productVariantAttributeValueRepository.GetMany(productVariantAttributeValueIds); - } + Array.Sort(productVariantAttributeValueIds); + + var key = PRODUCTVARIANTATTRIBUTEVALUES_BY_IDS_KEY.FormatInvariant(string.Join("-", productVariantAttributeValueIds)); + return _requestCache.Get(key, () => + { + var validTypeIds = new[] + { + (int)AttributeControlType.DropdownList, + (int)AttributeControlType.RadioList, + (int)AttributeControlType.Checkboxes, + (int)AttributeControlType.Boxes + }; + + 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(); + }); + } public virtual void InsertProductVariantAttribute(ProductVariantAttribute productVariantAttribute) { - if (productVariantAttribute == null) - throw new ArgumentNullException("productVariantAttribute"); + Guard.NotNull(productVariantAttribute, nameof(productVariantAttribute)); - _productVariantAttributeRepository.Insert(productVariantAttribute); - - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); + _productVariantAttributeRepository.Insert(productVariantAttribute); - //event notification - _eventPublisher.EntityInserted(productVariantAttribute); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } public virtual void UpdateProductVariantAttribute(ProductVariantAttribute productVariantAttribute) @@ -296,74 +434,142 @@ public virtual void UpdateProductVariantAttribute(ProductVariantAttribute produc if (productVariantAttribute == null) throw new ArgumentNullException("productVariantAttribute"); - _productVariantAttributeRepository.Update(productVariantAttribute); + _productVariantAttributeRepository.Update(productVariantAttribute); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productVariantAttribute); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } - #endregion + public virtual int CopyAttributeOptions(ProductVariantAttribute productVariantAttribute, int productAttributeOptionsSetId, bool deleteExistingValues) + { + Guard.NotNull(productVariantAttribute, nameof(productVariantAttribute)); + Guard.NotZero(productVariantAttribute.Id, nameof(productVariantAttribute.Id)); + Guard.NotZero(productAttributeOptionsSetId, nameof(productAttributeOptionsSetId)); + + if (deleteExistingValues) + { + var existingValues = productVariantAttribute.ProductVariantAttributeValues.ToList(); + if (!existingValues.Any()) + existingValues = GetProductVariantAttributeValues(productVariantAttribute.Id).ToList(); + + existingValues.Each(x => DeleteProductVariantAttributeValue(x)); + } - #region Product variant attribute values (ProductVariantAttributeValue) + var result = 0; + var attributeOptions = _productAttributeOptionRepository.TableUntracked + .Where(x => x.ProductAttributeOptionsSetId == productAttributeOptionsSetId) + .ToList(); - public virtual void DeleteProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) + if (!attributeOptions.Any()) + return result; + + // Do not insert already existing values (identified by name field). + var existingValueNames = new HashSet(_productVariantAttributeValueRepository.TableUntracked + .Where(x => x.ProductVariantAttributeId == productVariantAttribute.Id) + .Select(x => x.Name) + .ToList()); + + Picture picture = null; + ProductVariantAttributeValue productVariantAttributeValue = null; + var pictureIds = attributeOptions.Where(x => x.PictureId != 0).Select(x => x.PictureId).Distinct().ToArray(); + var pictures = _pictureService.GetPicturesByIds(pictureIds, true).ToDictionarySafe(x => x.Id); + + using (_localizedEntityService.BeginScope()) + { + foreach (var option in attributeOptions) + { + if (existingValueNames.Contains(option.Name)) + continue; + + productVariantAttributeValue = option.Clone(); + productVariantAttributeValue.PictureId = 0; + productVariantAttributeValue.ProductVariantAttributeId = productVariantAttribute.Id; + + // Copy picture. + if (option.PictureId != 0 && pictures.TryGetValue(option.PictureId, out picture)) + { + var pictureBinary = _pictureService.LoadPictureBinary(picture); + + var newPicture = _pictureService.InsertPicture( + pictureBinary, + picture.MimeType, + picture.SeoFilename, + picture.IsNew, + picture.Width ?? 0, + picture.Height ?? 0, + picture.IsTransient + ); + + productVariantAttributeValue.PictureId = newPicture.Id; + } + + // No scope commit, we need new entity id. + _productVariantAttributeValueRepository.Insert(productVariantAttributeValue); + ++result; + + // Copy localized properties too. + var optionProperties = _localizedEntityService.GetLocalizedProperties(option.Id, "ProductAttributeOption"); + + foreach (var property in optionProperties) + { + _localizedEntityService.InsertLocalizedProperty(new LocalizedProperty + { + EntityId = productVariantAttributeValue.Id, + LocaleKeyGroup = "ProductVariantAttributeValue", + LocaleKey = property.LocaleKey, + LocaleValue = property.LocaleValue, + LanguageId = property.LanguageId + }); + } + } + } + + return result; + } + + #endregion + + #region Product variant attribute values (ProductVariantAttributeValue) + + public virtual void DeleteProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) { if (productVariantAttributeValue == null) throw new ArgumentNullException("productVariantAttributeValue"); _productVariantAttributeValueRepository.Delete(productVariantAttributeValue); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(productVariantAttributeValue); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } public virtual IList GetProductVariantAttributeValues(int productVariantAttributeId) { - string key = string.Format(PRODUCTVARIANTATTRIBUTEVALUES_ALL_KEY, productVariantAttributeId); - return _requestCache.Get(key, () => - { - var query = from pvav in _productVariantAttributeValueRepository.Table - orderby pvav.DisplayOrder - where pvav.ProductVariantAttributeId == productVariantAttributeId - select pvav; - var productVariantAttributeValues = query.ToList(); - return productVariantAttributeValues; - }); - } + var query = from pvav in _productVariantAttributeValueRepository.Table + orderby pvav.DisplayOrder + where pvav.ProductVariantAttributeId == productVariantAttributeId + select pvav; + + var productVariantAttributeValues = query.ToListCached("db.prodvarattrvalues.all-" + productVariantAttributeId); + return productVariantAttributeValues; + } public virtual ProductVariantAttributeValue GetProductVariantAttributeValueById(int productVariantAttributeValueId) { if (productVariantAttributeValueId == 0) return null; - string key = string.Format(PRODUCTVARIANTATTRIBUTEVALUES_BY_ID_KEY, productVariantAttributeValueId); - return _requestCache.Get(key, () => - { - return _productVariantAttributeValueRepository.GetById(productVariantAttributeValueId); - }); - } + return _productVariantAttributeValueRepository.GetByIdCached(productVariantAttributeValueId, "db.prodvarattrval.id-" + productVariantAttributeValueId); + } public virtual void InsertProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) { if (productVariantAttributeValue == null) throw new ArgumentNullException("productVariantAttributeValue"); - _productVariantAttributeValueRepository.Insert(productVariantAttributeValue); + _productVariantAttributeValueRepository.Insert(productVariantAttributeValue); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(productVariantAttributeValue); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } public virtual void UpdateProductVariantAttributeValue(ProductVariantAttributeValue productVariantAttributeValue) @@ -371,14 +577,10 @@ public virtual void UpdateProductVariantAttributeValue(ProductVariantAttributeVa if (productVariantAttributeValue == null) throw new ArgumentNullException("productVariantAttributeValue"); - _productVariantAttributeValueRepository.Update(productVariantAttributeValue); + _productVariantAttributeValueRepository.Update(productVariantAttributeValue); - _requestCache.RemoveByPattern(PRODUCTATTRIBUTES_PATTERN_KEY); _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productVariantAttributeValue); + _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } #endregion @@ -407,9 +609,6 @@ public virtual void DeleteProductVariantAttributeCombination(ProductVariantAttri throw new ArgumentNullException("combination"); _pvacRepository.Delete(combination); - - //event notification - _eventPublisher.EntityDeleted(combination); } public virtual IPagedList GetAllProductVariantAttributeCombinations( @@ -468,7 +667,12 @@ public virtual IList GetAllProductVariantAttributeCombinationPictureIds(int public virtual Multimap GetProductVariantAttributeCombinations(int[] productIds) { - Guard.ArgumentNotNull(() => productIds); + Guard.NotNull(productIds, nameof(productIds)); + + if (!productIds.Any()) + { + return new Multimap(); + } var query = from pvac in _pvacRepository.TableUntracked @@ -509,27 +713,44 @@ 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); + 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) throw new ArgumentNullException("combination"); - //if (combination.IsDefaultCombination) - //{ - // EnsureSingleDefaultVariant(combination); - //} - _pvacRepository.Insert(combination); - //event notification - _eventPublisher.EntityInserted(combination); } public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttributeCombination combination) @@ -562,20 +783,21 @@ public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttri //} _pvacRepository.Update(combination); - - //event notification - _eventPublisher.EntityUpdated(combination); } public virtual void CreateAllProductVariantAttributeCombinations(Product product) { - // delete all existing combinations + // Delete all existing combinations. _pvacRepository.DeleteAll(x => x.ProductId == product.Id); var attributes = GetProductVariantAttributesByProductId(product.Id); if (attributes == null || attributes.Count <= 0) return; + var mappedAttributes = attributes + .SelectMany(x => x.ProductVariantAttributeValues) + .ToDictionarySafe(x => x.Id, x => x.ProductVariantAttribute); + var toCombine = new List>(); var resultMatrix = new List>(); var tmp = new List(); @@ -594,23 +816,22 @@ public virtual void CreateAllProductVariantAttributeCombinations(Product product using (var scope = new DbContextScope(ctx: _pvacRepository.Context, autoCommit: false, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) { ProductVariantAttributeCombination combination = null; - var idx = 0; + foreach (var values in resultMatrix) { idx++; + var attributesXml = ""; - string attrXml = ""; - for (var i = 0; i < values.Count; ++i) + foreach (var value in values) { - var value = values[i]; - attrXml = attributes[i].AddProductAttribute(attrXml, value.Id.ToString()); + attributesXml = mappedAttributes[value.Id].AddProductAttribute(attributesXml, value.Id.ToString()); } combination = new ProductVariantAttributeCombination { ProductId = product.Id, - AttributesXml = attrXml, + AttributesXml = attributesXml, StockQuantity = 10000, AllowOutOfStockOrders = true, IsActive = true @@ -620,19 +841,14 @@ public virtual void CreateAllProductVariantAttributeCombinations(Product product } scope.Commit(); - - if (combination != null) - { - // Perf: publish event for last one only - _eventPublisher.EntityInserted(combination); - } } - } - //foreach (var y in resultMatrix) { - // StringBuilder sb = new StringBuilder(); - // foreach (var x in y) { + //foreach (var y in resultMatrix) + //{ + // var sb = new System.Text.StringBuilder(); + // foreach (var x in y) + // { // sb.AppendFormat("{0} ", x.Name); // } // sb.ToString().Dump(); @@ -664,8 +880,6 @@ public virtual void InsertProductBundleItemAttributeFilter(ProductBundleItemAttr if (attributeFilter.AttributeId != 0 && attributeFilter.AttributeValueId != 0) { _productBundleItemAttributeFilterRepository.Insert(attributeFilter); - - _eventPublisher.EntityInserted(attributeFilter); } } @@ -675,8 +889,6 @@ public virtual void UpdateProductBundleItemAttributeFilter(ProductBundleItemAttr throw new ArgumentNullException("attributeFilter"); _productBundleItemAttributeFilterRepository.Update(attributeFilter); - - _eventPublisher.EntityUpdated(attributeFilter); } public virtual void DeleteProductBundleItemAttributeFilter(ProductBundleItemAttributeFilter attributeFilter) @@ -685,8 +897,6 @@ public virtual void DeleteProductBundleItemAttributeFilter(ProductBundleItemAttr throw new ArgumentNullException("attributeFilter"); _productBundleItemAttributeFilterRepository.Delete(attributeFilter); - - _eventPublisher.EntityDeleted(attributeFilter); } public virtual void DeleteProductBundleItemAttributeFilter(ProductBundleItem bundleItem) @@ -723,7 +933,5 @@ from x in _productBundleItemAttributeFilterRepository.Table } #endregion - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 051fc841f2..679dddfea8 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -1,20 +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; namespace SmartStore.Services.Catalog { - public static class ProductExtensions + public static class ProductExtensions { public static ProductVariantAttributeCombination MergeWithCombination(this Product product, string selectedAttributes) { @@ -23,17 +21,21 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ public static ProductVariantAttributeCombination MergeWithCombination(this Product product, string selectedAttributes, IProductAttributeParser productAttributeParser) { - Guard.ArgumentNotNull(productAttributeParser, "productAttributeParser"); + Guard.NotNull(productAttributeParser, "productAttributeParser"); if (selectedAttributes.IsEmpty()) return null; - // let's find appropriate record + // Let's find appropriate record. var combination = productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null && combination.IsActive) + if (combination != null && combination.IsActive) { - product.MergeWithCombination(combination); + product.MergeWithCombination(combination); + } + else if (product.MergedDataValues != null) + { + product.MergedDataValues.Clear(); } return combination; @@ -41,50 +43,52 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ public static void MergeWithCombination(this Product product, ProductVariantAttributeCombination combination) { - Guard.ArgumentNotNull(product, "product"); + Guard.NotNull(product, "product"); + + var values = product.MergedDataValues; - if (product.MergedDataValues != null) - product.MergedDataValues.Clear(); + if (values != null) + values.Clear(); if (combination == null) return; - if (product.MergedDataValues == null) - product.MergedDataValues = new Dictionary(); + if (values == null) + product.MergedDataValues = values = new Dictionary(); - if (ManageInventoryMethod.ManageStockByAttributes == (ManageInventoryMethod)product.ManageInventoryMethodId) - { - product.MergedDataValues.Add("StockQuantity", combination.StockQuantity); - product.MergedDataValues.Add("BackorderModeId", combination.AllowOutOfStockOrders ? (int)BackorderMode.AllowQtyBelow0 : (int)BackorderMode.NoBackorders); - } + if (ManageInventoryMethod.ManageStockByAttributes == (ManageInventoryMethod)product.ManageInventoryMethodId) + { + values.Add("StockQuantity", combination.StockQuantity); + values.Add("BackorderModeId", combination.AllowOutOfStockOrders ? (int)BackorderMode.AllowQtyBelow0 : (int)BackorderMode.NoBackorders); + } if (combination.Sku.HasValue()) - product.MergedDataValues.Add("Sku", combination.Sku); + values.Add("Sku", combination.Sku); if (combination.Gtin.HasValue()) - product.MergedDataValues.Add("Gtin", combination.Gtin); + values.Add("Gtin", combination.Gtin); if (combination.ManufacturerPartNumber.HasValue()) - product.MergedDataValues.Add("ManufacturerPartNumber", combination.ManufacturerPartNumber); + values.Add("ManufacturerPartNumber", combination.ManufacturerPartNumber); if (combination.Price.HasValue) - product.MergedDataValues.Add("Price", combination.Price.Value); + values.Add("Price", combination.Price.Value); if (combination.DeliveryTimeId.HasValue && combination.DeliveryTimeId.Value > 0) - product.MergedDataValues.Add("DeliveryTimeId", combination.DeliveryTimeId); + values.Add("DeliveryTimeId", combination.DeliveryTimeId); if (combination.QuantityUnitId.HasValue && combination.QuantityUnitId.Value > 0) - product.MergedDataValues.Add("QuantityUnitId", combination.QuantityUnitId); + values.Add("QuantityUnitId", combination.QuantityUnitId); if (combination.Length.HasValue) - product.MergedDataValues.Add("Length", combination.Length.Value); + values.Add("Length", combination.Length.Value); if (combination.Width.HasValue) - product.MergedDataValues.Add("Width", combination.Width.Value); + values.Add("Width", combination.Width.Value); if (combination.Height.HasValue) - product.MergedDataValues.Add("Height", combination.Height.Value); + values.Add("Height", combination.Height.Value); if (combination.BasePriceAmount.HasValue) - product.MergedDataValues.Add("BasePriceAmount", combination.BasePriceAmount); + values.Add("BasePriceAmount", combination.BasePriceAmount); if (combination.BasePriceBaseAmount.HasValue) - product.MergedDataValues.Add("BasePriceBaseAmount", combination.BasePriceBaseAmount); + values.Add("BasePriceBaseAmount", combination.BasePriceBaseAmount); } public static IList GetAllCombinationPictureIds(this IEnumerable combinations) @@ -100,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); } } @@ -141,39 +144,26 @@ 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; - } - - /// - /// Get a default picture of a product - /// - /// Source - /// Picture service - /// Product picture - public static Picture GetDefaultProductPicture(this Product source, IPictureService pictureService) - { - if (source == null) - throw new ArgumentNullException("source"); - - if (pictureService == null) - throw new ArgumentNullException("pictureService"); + } - var picture = pictureService.GetPicturesByProductId(source.Id, 1).FirstOrDefault(); - return picture; + return null; } public static bool IsAvailableByStock(this Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(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; } @@ -185,13 +175,10 @@ public static bool IsAvailableByStock(this Product product) /// The stock message public static string FormatStockMessage(this Product product, ILocalizationService localizationService) { - if (product == null) - throw new ArgumentNullException("product"); - - if (localizationService == null) - throw new ArgumentNullException("localizationService"); + Guard.NotNull(product, nameof(product)); + Guard.NotNull(localizationService, nameof(localizationService)); - string stockMessage = string.Empty; + string stockMessage = string.Empty; if ((product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) && product.DisplayStockAvailability) @@ -215,35 +202,63 @@ public static string FormatStockMessage(this Product product, ILocalizationServi return stockMessage; } - /// - /// Formats the stock availability/quantity message - /// - /// Product - /// Localization service - /// The stock message public static bool DisplayDeliveryTimeAccordingToStock(this Product product, CatalogSettings catalogSettings) { - if (product == null) - throw new ArgumentNullException("product"); + 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; return (product.StockQuantity > 0); } + return true; } - - public static bool ProductTagExists(this Product product, int productTagId) + public static int? GetDeliveryTimeIdAccordingToStock(this Product product, CatalogSettings catalogSettings) { + Guard.NotNull(catalogSettings, nameof(catalogSettings)); + if (product == null) - throw new ArgumentNullException("product"); + { + return null; + } - bool result = product.ProductTags.ToList().Find(pt => pt.Id == productTagId) != null; - return result; + 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) + { + return ((DateTime.UtcNow - product.CreatedOnUtc).Days <= catalogSettings.LabelAsNewForMaxDays.Value); + } + + return false; + } + + public static bool ProductTagExists(this Product product, int productTagId) + { + Guard.NotNull(product, nameof(product)); + + var result = product.ProductTags.Any(x => x.Id == productTagId); + return result; } /// @@ -253,10 +268,9 @@ public static bool ProductTagExists(this Product product, int productTagId) /// Result public static int[] ParseAllowedQuatities(this Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); - var result = new List(); + var result = new List(); if (!String.IsNullOrWhiteSpace(product.AllowedQuantities)) { product @@ -265,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); } @@ -278,8 +291,7 @@ public static int[] ParseAllowedQuatities(this Product product) public static int[] ParseRequiredProductIds(this Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); if (String.IsNullOrEmpty(product.RequiredProductIds)) return new int[0]; @@ -290,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); } @@ -307,9 +318,9 @@ public static int[] ParseRequiredProductIds(this Product product) /// Currency service /// Tax service /// Price calculation service + /// 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, @@ -317,30 +328,27 @@ public static string GetBasePriceInfo(this Product product, ICurrencyService currencyService, ITaxService taxService, IPriceCalculationService priceCalculationService, + Customer customer, Currency currency, - decimal priceAdjustment = decimal.Zero, - bool languageInsensitive = false) + decimal priceAdjustment = decimal.Zero) { - Guard.ArgumentNotNull(() => product); - Guard.ArgumentNotNull(() => currencyService); - Guard.ArgumentNotNull(() => taxService); - Guard.ArgumentNotNull(() => priceCalculationService); - Guard.ArgumentNotNull(() => currency); - - if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) + Guard.NotNull(product, nameof(product)); + Guard.NotNull(currencyService, nameof(currencyService)); + Guard.NotNull(taxService, nameof(taxService)); + Guard.NotNull(priceCalculationService, nameof(priceCalculationService)); + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(currency, nameof(currency)); + + if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero) { - var workContext = EngineContext.Current.Resolve(); - - var taxrate = decimal.Zero; - var currentPrice = priceCalculationService.GetFinalPrice(product, workContext.CurrentCustomer, true); - var price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), out taxrate); - + var currentPrice = priceCalculationService.GetFinalPrice(product, customer, true); + 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; } /// @@ -351,27 +359,24 @@ 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.ArgumentNotNull(() => product); - Guard.ArgumentNotNull(() => localizationService); - Guard.ArgumentNotNull(() => priceFormatter); - Guard.ArgumentNotNull(() => currency); + Guard.NotNull(product, nameof(product)); + Guard.NotNull(localizationService, nameof(localizationService)); + Guard.NotNull(priceFormatter, nameof(priceFormatter)); + Guard.NotNull(currency, nameof(currency)); - if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) + if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero) { 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, @@ -383,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) { @@ -438,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); @@ -449,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/ProductSearchContext.cs b/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs deleted file mode 100644 index 001e18789b..0000000000 --- a/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using SmartStore.Core.Domain.Catalog; - -namespace SmartStore.Services.Catalog -{ - public class ProductSearchContext - { - public ProductSearchContext() - { - CategoryIds = new List(); - FilteredSpecs = new List(); - FilterableSpecificationAttributeOptionIds = new List(); - PageSize = 12; - } - - /// - /// Optional query to use to build the product query. Otherwise the repository of the product service is used (default). - /// - public IQueryable Query { get; set; } - - /// - /// Category identifiers - /// - public IList CategoryIds { get; set; } - - /// - /// Filter by product identifiers - /// - /// Only implemented in LINQ mode at the moment - public IList ProductIds { get; set; } - - /// - /// Minimum product identifier - /// - public int IdMin { get; set; } - - /// - /// Maximum product identifier - /// - public int IdMax { get; set; } - - /// - /// A value indicating whether ALL given must be assigned to the resulting products (default is ANY) - /// - /// Only works in LINQ mode at the moment - public bool MatchAllcategories { get; set; } - - /// - /// A value indicating whether to load products without any catgory mapping - /// - public bool? WithoutCategories { get; set; } - - /// - /// Manufacturer identifier; 0 to load all records - /// - public int ManufacturerId { get; set; } - - /// - /// A value indicating whether to load products without any manufacturer mapping - /// - public bool? WithoutManufacturers { get; set; } - - /// - /// A value indicating whether loaded products are marked as featured (relates only to categories and manufacturers). 0 to load featured products only, 1 to load not featured products only, null to load all products - /// - public bool? FeaturedProducts { get; set; } - - /// - /// Minimum price; null to load all records - /// - public decimal? PriceMin { get; set; } - - /// - /// Maximum price; null to load all records - /// - public decimal? PriceMax { get; set; } - - /// - /// Product tag identifier; 0 to load all records - /// - public int ProductTagId { get; set; } - - /// - /// Keywords - /// - public string Keywords { get; set; } - - /// - /// A value indicating whether to search in descriptions - /// - public bool SearchDescriptions { get; set; } - - /// - /// A value indicating whether to search by a specified "keyword" in product tags - /// - public bool SearchProductTags { get; set; } - - /// - /// Language identifier - /// - public int LanguageId { get; set; } - - /// - /// Filtered product specification identifiers - /// - public IList FilteredSpecs { get; set; } - - /// - /// Order by - /// - public ProductSortingEnum OrderBy { get; set; } - - /// - /// Page index - /// - public int PageIndex { get; set; } - - /// - /// Page size - /// - public int PageSize { get; set; } - - /// - /// A value indicating whether we should load the specification attribute option identifiers applied to loaded products (all pages) - /// - public bool LoadFilterableSpecificationAttributeOptionIds { get; set; } - - /// - /// The specification attribute option identifiers applied to loaded products (all pages) - /// - public IList FilterableSpecificationAttributeOptionIds { get; set; } - - /// - /// A value indicating whether to show hidden records - /// - public bool ShowHidden { get; set; } - - /// - /// Store identifier; 0 to load all records - /// - public int StoreId { get; set; } - - /// - /// Parent product identifier (used with grouped products); 0 to load all records - /// - public int ParentGroupedProductId { get; set; } - - /// - /// A values indicating whether to load only products marked as "visible individually"; "false" to load all records; "true" to load "visible individually" only - /// - public bool VisibleIndividuallyOnly { get; set; } - - /// - /// Product type; 0 to load all records - /// - public ProductType? ProductType { get; set; } - - /// - /// A value indicating whether to search by a specified "Keyword" in product SKU - /// - public bool SearchSku { get; set; } - - /// - /// Any value indicating the origin of the search request, - /// e.g. the category id, if the caller is is a category page. - /// Can be useful in customization scenarios. - /// - public string Origin { get; set; } - - /// - /// A value indicating whether to load only published or non published products - /// - public bool? IsPublished { get; set; } - - /// - /// A value indicating whether to load only products displayed on the homepage - /// - public bool? HomePageProducts { get; set; } - - /// - /// Search by minimum availability - /// - public int? AvailabilityMinimum { get; set; } - - /// - /// Search by maximum availability - /// - public int? AvailabilityMaximum { get; set; } - - /// - /// Search by created from date - /// - public DateTime? CreatedFromUtc { get; set; } - - /// - /// Search by created to date - /// - public DateTime? CreatedToUtc { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 1b0e53ff8e..bd20bc65e9 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1,144 +1,68 @@ using System; using System.Collections.Generic; using System.Data; -using System.Globalization; +using System.Data.Entity; using System.Linq; -using System.Linq.Expressions; using SmartStore.Collections; -using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; -using SmartStore.Services.Localization; +using SmartStore.Data.Caching; using SmartStore.Services.Messages; using SmartStore.Services.Orders; namespace SmartStore.Services.Catalog { - /// - /// Product service - /// public partial class ProductService : IProductService { - #region Constants - - private const string PRODUCTS_BY_ID_KEY = "SmartStore.product.id-{0}"; - private const string PRODUCTS_PATTERN_KEY = "SmartStore.product."; - - #endregion - - #region Fields - private readonly IRepository _productRepository; private readonly IRepository _relatedProductRepository; private readonly IRepository _crossSellProductRepository; private readonly IRepository _tierPriceRepository; - private readonly IRepository _localizedPropertyRepository; - private readonly IRepository _aclRepository; - private readonly IRepository _storeMappingRepository; private readonly IRepository _productPictureRepository; - private readonly IRepository _productSpecificationAttributeRepository; private readonly IRepository _productVariantAttributeCombinationRepository; private readonly IRepository _productBundleItemRepository; - private readonly IProductAttributeService _productAttributeService; + private readonly IRepository _shoppingCartItemRepository; + private readonly IProductAttributeService _productAttributeService; private readonly IProductAttributeParser _productAttributeParser; - private readonly ILanguageService _languageService; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IDataProvider _dataProvider; private readonly IDbContext _dbContext; - private readonly IRequestCache _requestCache; private readonly LocalizationSettings _localizationSettings; - private readonly CommonSettings _commonSettings; private readonly ICommonServices _services; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Product repository - /// Related product repository - /// Cross-sell product repository - /// Tier price repository - /// Localized property repository - /// ACL record repository - /// Store mapping repository - /// Product picture repository - /// Product specification attribute repository - /// Product attribute service - /// Product attribute parser service - /// Language service - /// Workflow message service - /// Data provider - /// Database Context - /// Work context - /// Store context - /// Localization settings - /// Common settings - /// Event published public ProductService( IRepository productRepository, IRepository relatedProductRepository, IRepository crossSellProductRepository, IRepository tierPriceRepository, IRepository productPictureRepository, - IRepository localizedPropertyRepository, - IRepository aclRepository, - IRepository storeMappingRepository, - IRepository productSpecificationAttributeRepository, IRepository productVariantAttributeCombinationRepository, IRepository productBundleItemRepository, - IProductAttributeService productAttributeService, + IRepository shoppingCartItemRepository, + IProductAttributeService productAttributeService, IProductAttributeParser productAttributeParser, - ILanguageService languageService, - IWorkflowMessageService workflowMessageService, - IDataProvider dataProvider, IDbContext dbContext, - IRequestCache requestCache, LocalizationSettings localizationSettings, - CommonSettings commonSettings, ICommonServices services) { - this._productRepository = productRepository; - this._relatedProductRepository = relatedProductRepository; - this._crossSellProductRepository = crossSellProductRepository; - this._tierPriceRepository = tierPriceRepository; - this._productPictureRepository = productPictureRepository; - this._localizedPropertyRepository = localizedPropertyRepository; - this._aclRepository = aclRepository; - this._storeMappingRepository = storeMappingRepository; - this._productSpecificationAttributeRepository = productSpecificationAttributeRepository; - this._productVariantAttributeCombinationRepository = productVariantAttributeCombinationRepository; - this._productBundleItemRepository = productBundleItemRepository; - this._productAttributeService = productAttributeService; - this._productAttributeParser = productAttributeParser; - this._languageService = languageService; - this._workflowMessageService = workflowMessageService; - this._dataProvider = dataProvider; - this._dbContext = dbContext; - this._requestCache = requestCache; - this._localizationSettings = localizationSettings; - this._commonSettings = commonSettings; - this._services = services; - - this.QuerySettings = DbQuerySettings.Default; + _productRepository = productRepository; + _relatedProductRepository = relatedProductRepository; + _crossSellProductRepository = crossSellProductRepository; + _tierPriceRepository = tierPriceRepository; + _productPictureRepository = productPictureRepository; + _productVariantAttributeCombinationRepository = productVariantAttributeCombinationRepository; + _productBundleItemRepository = productBundleItemRepository; + _shoppingCartItemRepository = shoppingCartItemRepository; + _productAttributeService = productAttributeService; + _productAttributeParser = productAttributeParser; + _dbContext = dbContext; + _localizationSettings = localizationSettings; + _services = services; } - public DbQuerySettings QuerySettings { get; set; } - - #endregion - #region Utilities protected virtual int EnsureMutuallyRelatedProducts(List productIds) @@ -150,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) @@ -191,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) @@ -218,22 +142,16 @@ join p in _productRepository.Table on rp.ProductId2 equals p.Id #endregion - #region Methods - #region Products - /// - /// Delete a product - /// - /// Product - public virtual void DeleteProduct(Product product) + public virtual void DeleteProduct(Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); - product.Deleted = true; + product.Deleted = true; product.DeliveryTimeId = null; product.QuantityUnitId = null; + product.CountryOfOriginId = null; UpdateProduct(product); @@ -249,10 +167,6 @@ public virtual void DeleteProduct(Product product) } } - /// - /// Gets all products displayed on the home page - /// - /// Product collection public virtual IList GetAllProductsDisplayedOnHomePage() { var query = @@ -265,788 +179,136 @@ where p.Published && !p.Deleted && p.ShowOnHomePage return products; } - /// - /// Gets product - /// - /// Product identifier - /// Product public virtual Product GetProductById(int productId) { if (productId == 0) return null; - string key = string.Format(PRODUCTS_BY_ID_KEY, productId); - return _requestCache.Get(key, () => - { - return _productRepository.GetById(productId); - }); - } + return _productRepository.GetByIdCached(productId, "db.product.id-" + productId); + } - /// - /// Get products by identifiers - /// - /// Product identifiers - /// Products - public virtual IList GetProductsByIds(int[] productIds) + 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) select p; - var products = query.ToList(); - - // sort by passed identifier sequence - var sortQuery = from i in productIds - join p in products on i equals p.Id - select p; - - return sortQuery.ToList(); - } - - /// - /// Inserts a product - /// - /// Product - public virtual void InsertProduct(Product product) - { - if (product == null) - throw new ArgumentNullException("product"); - - //insert - _productRepository.Insert(product); - - //clear cache - _requestCache.RemoveByPattern(PRODUCTS_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityInserted(product); - } - - /// - /// Updates the product - /// - /// Product - public virtual void UpdateProduct(Product product, bool publishEvent = true) - { - if (product == null) - throw new ArgumentNullException("product"); - bool modified = false; - if (publishEvent) + if (flags > ProductLoadFlags.None) { - modified = _productRepository.IsModified(product); + query = ApplyLoadFlags(query, flags); } - // update - _productRepository.Update(product); - - // cache - _requestCache.RemoveByPattern(PRODUCTS_PATTERN_KEY); - - // event notification - if (publishEvent && modified) - { - _services.EventPublisher.EntityUpdated(product); - } - } - - - public virtual int CountProducts(ProductSearchContext ctx) - { - Guard.ArgumentNotNull(() => ctx); - - var query = this.PrepareProductSearchQuery(ctx); - return query.Distinct().Count(); - } - - public virtual IPagedList SearchProducts(ProductSearchContext ctx) - { - ctx.LoadFilterableSpecificationAttributeOptionIds = false; - - ctx.FilterableSpecificationAttributeOptionIds = new List(); - - _services.EventPublisher.Publish(new ProductsSearchingEvent(ctx)); - - //search by keyword - bool searchLocalizedValue = false; - if (ctx.LanguageId > 0) - { - if (ctx.ShowHidden) - { - searchLocalizedValue = true; - } - else - { - //ensure that we have at least two published languages - var totalPublishedLanguages = _languageService.GetAllLanguages(storeId: ctx.StoreId).Count; - searchLocalizedValue = totalPublishedLanguages >= 2; - } - } - - //validate "categoryIds" parameter - if (ctx.CategoryIds != null && ctx.CategoryIds.Contains(0)) - ctx.CategoryIds.Remove(0); - - //Access control list. Allowed customer roles - var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles - .Where(cr => cr.Active).Select(cr => cr.Id).ToList(); - - if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) - { - //stored procedures are enabled and supported by the database. - //It's much faster than the LINQ implementation below - - #region Use stored procedure - - //pass categry identifiers as comma-delimited string - string commaSeparatedCategoryIds = ""; - if (ctx.CategoryIds != null && !(ctx.WithoutCategories ?? false)) - { - for (int i = 0; i < ctx.CategoryIds.Count; i++) - { - commaSeparatedCategoryIds += ctx.CategoryIds[i].ToString(); - if (i != ctx.CategoryIds.Count - 1) - { - commaSeparatedCategoryIds += ","; - } - } - } - - //pass customer role identifiers as comma-delimited string - string commaSeparatedAllowedCustomerRoleIds = ""; - for (int i = 0; i < allowedCustomerRolesIds.Count; i++) - { - commaSeparatedAllowedCustomerRoleIds += allowedCustomerRolesIds[i].ToString(); - if (i != allowedCustomerRolesIds.Count - 1) - { - commaSeparatedAllowedCustomerRoleIds += ","; - } - } - - //pass specification identifiers as comma-delimited string - string commaSeparatedSpecIds = ""; - if (ctx.FilteredSpecs != null) - { - ((List)ctx.FilteredSpecs).Sort(); - for (int i = 0; i < ctx.FilteredSpecs.Count; i++) - { - commaSeparatedSpecIds += ctx.FilteredSpecs[i].ToString(); - if (i != ctx.FilteredSpecs.Count - 1) - { - commaSeparatedSpecIds += ","; - } - } - } - - //some databases don't support int.MaxValue - if (ctx.PageSize == int.MaxValue) - ctx.PageSize = int.MaxValue - 1; - - //prepare parameters - var pCategoryIds = _dataProvider.GetParameter(); - pCategoryIds.ParameterName = "CategoryIds"; - pCategoryIds.Value = commaSeparatedCategoryIds != null ? (object)commaSeparatedCategoryIds : DBNull.Value; - pCategoryIds.DbType = DbType.String; - - var pManufacturerId = _dataProvider.GetParameter(); - pManufacturerId.ParameterName = "ManufacturerId"; - pManufacturerId.Value = (ctx.WithoutManufacturers ?? false) ? 0 : ctx.ManufacturerId; - pManufacturerId.DbType = DbType.Int32; - - var pStoreId = _dataProvider.GetParameter(); - pStoreId.ParameterName = "StoreId"; - pStoreId.Value = QuerySettings.IgnoreMultiStore ? 0 : ctx.StoreId; - pStoreId.DbType = DbType.Int32; - - var pParentGroupedProductId = _dataProvider.GetParameter(); - pParentGroupedProductId.ParameterName = "ParentGroupedProductId"; - pParentGroupedProductId.Value = ctx.ParentGroupedProductId; - pParentGroupedProductId.DbType = DbType.Int32; - - var pProductTypeId = _dataProvider.GetParameter(); - pProductTypeId.ParameterName = "ProductTypeId"; - pProductTypeId.Value = ctx.ProductType.HasValue ? (object)ctx.ProductType.Value : DBNull.Value; - pProductTypeId.DbType = DbType.Int32; - - var pVisibleIndividuallyOnly = _dataProvider.GetParameter(); - pVisibleIndividuallyOnly.ParameterName = "VisibleIndividuallyOnly"; - pVisibleIndividuallyOnly.Value = ctx.VisibleIndividuallyOnly; - pVisibleIndividuallyOnly.DbType = DbType.Int32; - - var pProductTagId = _dataProvider.GetParameter(); - pProductTagId.ParameterName = "ProductTagId"; - pProductTagId.Value = ctx.ProductTagId; - pProductTagId.DbType = DbType.Int32; - - var pFeaturedProducts = _dataProvider.GetParameter(); - pFeaturedProducts.ParameterName = "FeaturedProducts"; - pFeaturedProducts.Value = ctx.FeaturedProducts.HasValue ? (object)ctx.FeaturedProducts.Value : DBNull.Value; - pFeaturedProducts.DbType = DbType.Boolean; - - var pPriceMin = _dataProvider.GetParameter(); - pPriceMin.ParameterName = "PriceMin"; - pPriceMin.Value = ctx.PriceMin.HasValue ? (object)ctx.PriceMin.Value : DBNull.Value; - pPriceMin.DbType = DbType.Decimal; - - var pPriceMax = _dataProvider.GetParameter(); - pPriceMax.ParameterName = "PriceMax"; - pPriceMax.Value = ctx.PriceMax.HasValue ? (object)ctx.PriceMax.Value : DBNull.Value; - pPriceMax.DbType = DbType.Decimal; - - var pKeywords = _dataProvider.GetParameter(); - pKeywords.ParameterName = "Keywords"; - pKeywords.Value = ctx.Keywords != null ? (object)ctx.Keywords : DBNull.Value; - pKeywords.DbType = DbType.String; - - var pSearchDescriptions = _dataProvider.GetParameter(); - pSearchDescriptions.ParameterName = "SearchDescriptions"; - pSearchDescriptions.Value = ctx.SearchDescriptions; - pSearchDescriptions.DbType = DbType.Boolean; - - var pSearchSku = _dataProvider.GetParameter(); - pSearchSku.ParameterName = "SearchSku"; - pSearchSku.Value = ctx.SearchSku; - pSearchSku.DbType = DbType.Boolean; - - var pSearchProductTags = _dataProvider.GetParameter(); - pSearchProductTags.ParameterName = "SearchProductTags"; - pSearchProductTags.Value = ctx.SearchDescriptions; - pSearchProductTags.DbType = DbType.Boolean; - - var pUseFullTextSearch = _dataProvider.GetParameter(); - pUseFullTextSearch.ParameterName = "UseFullTextSearch"; - pUseFullTextSearch.Value = _commonSettings.UseFullTextSearch; - pUseFullTextSearch.DbType = DbType.Boolean; - - var pFullTextMode = _dataProvider.GetParameter(); - pFullTextMode.ParameterName = "FullTextMode"; - pFullTextMode.Value = (int)_commonSettings.FullTextMode; - pFullTextMode.DbType = DbType.Int32; - - var pFilteredSpecs = _dataProvider.GetParameter(); - pFilteredSpecs.ParameterName = "FilteredSpecs"; - pFilteredSpecs.Value = commaSeparatedSpecIds != null ? (object)commaSeparatedSpecIds : DBNull.Value; - pFilteredSpecs.DbType = DbType.String; - - var pLanguageId = _dataProvider.GetParameter(); - pLanguageId.ParameterName = "LanguageId"; - pLanguageId.Value = searchLocalizedValue ? ctx.LanguageId : 0; - pLanguageId.DbType = DbType.Int32; - - var pOrderBy = _dataProvider.GetParameter(); - pOrderBy.ParameterName = "OrderBy"; - pOrderBy.Value = (int)ctx.OrderBy; - pOrderBy.DbType = DbType.Int32; - - var pAllowedCustomerRoleIds = _dataProvider.GetParameter(); - pAllowedCustomerRoleIds.ParameterName = "AllowedCustomerRoleIds"; - pAllowedCustomerRoleIds.Value = commaSeparatedAllowedCustomerRoleIds; - pAllowedCustomerRoleIds.DbType = DbType.String; - - var pPageIndex = _dataProvider.GetParameter(); - pPageIndex.ParameterName = "PageIndex"; - pPageIndex.Value = ctx.PageIndex; - pPageIndex.DbType = DbType.Int32; - - var pPageSize = _dataProvider.GetParameter(); - pPageSize.ParameterName = "PageSize"; - pPageSize.Value = ctx.PageSize; - pPageSize.DbType = DbType.Int32; - - var pShowHidden = _dataProvider.GetParameter(); - pShowHidden.ParameterName = "ShowHidden"; - pShowHidden.Value = ctx.ShowHidden; - pShowHidden.DbType = DbType.Boolean; - - var pLoadFilterableSpecificationAttributeOptionIds = _dataProvider.GetParameter(); - pLoadFilterableSpecificationAttributeOptionIds.ParameterName = "LoadFilterableSpecificationAttributeOptionIds"; - pLoadFilterableSpecificationAttributeOptionIds.Value = ctx.LoadFilterableSpecificationAttributeOptionIds; - pLoadFilterableSpecificationAttributeOptionIds.DbType = DbType.Boolean; - - var pWithoutCategories = _dataProvider.GetParameter(); - pWithoutCategories.ParameterName = "WithoutCategories"; - pWithoutCategories.Value = (ctx.WithoutCategories.HasValue ? (object)ctx.WithoutCategories.Value : DBNull.Value); - pWithoutCategories.DbType = DbType.Boolean; - - var pWithoutManufacturers = _dataProvider.GetParameter(); - pWithoutManufacturers.ParameterName = "WithoutManufacturers"; - pWithoutManufacturers.Value = (ctx.WithoutManufacturers.HasValue ? (object)ctx.WithoutManufacturers.Value : DBNull.Value); - pWithoutManufacturers.DbType = DbType.Boolean; - - var pIsPublished = _dataProvider.GetParameter(); - pIsPublished.ParameterName = "IsPublished"; - pIsPublished.Value = (ctx.IsPublished.HasValue ? (object)ctx.IsPublished.Value : DBNull.Value); - pIsPublished.DbType = DbType.Boolean; - - var pHomePageProducts = _dataProvider.GetParameter(); - pHomePageProducts.ParameterName = "HomePageProducts"; - pHomePageProducts.Value = (ctx.HomePageProducts.HasValue ? (object)ctx.HomePageProducts.Value : DBNull.Value); - pHomePageProducts.DbType = DbType.Boolean; - - var pIdMin = _dataProvider.GetParameter(); - pIdMin.ParameterName = "IdMin"; - pIdMin.Value = ctx.IdMin; - pIdMin.DbType = DbType.Int32; - - var pIdMax = _dataProvider.GetParameter(); - pIdMax.ParameterName = "IdMax"; - pIdMax.Value = ctx.IdMin; - pIdMax.DbType = DbType.Int32; - - var pAvailabilityMin = _dataProvider.GetParameter(); - pAvailabilityMin.ParameterName = "AvailabilityMin"; - pAvailabilityMin.Value = ctx.AvailabilityMinimum.HasValue ? (object)ctx.AvailabilityMinimum.Value : DBNull.Value; - pAvailabilityMin.DbType = DbType.Int32; - - var pAvailabilityMax = _dataProvider.GetParameter(); - pAvailabilityMax.ParameterName = "AvailabilityMax"; - pAvailabilityMax.Value = ctx.AvailabilityMaximum.HasValue ? (object)ctx.AvailabilityMaximum.Value : DBNull.Value; - pAvailabilityMax.DbType = DbType.Int32; - - var pCreatedFromUtc = _dataProvider.GetParameter(); - pCreatedFromUtc.ParameterName = "CreatedFromUtc"; - pCreatedFromUtc.Value = ctx.CreatedFromUtc.HasValue ? (object)ctx.CreatedFromUtc.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) : DBNull.Value; - pCreatedFromUtc.DbType = DbType.String; - - var pCreatedToUtc = _dataProvider.GetParameter(); - pCreatedToUtc.ParameterName = "CreatedToUtc"; - pCreatedToUtc.Value = ctx.CreatedToUtc.HasValue ? (object)ctx.CreatedToUtc.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) : DBNull.Value; - pCreatedToUtc.DbType = DbType.String; - - - var pFilterableSpecificationAttributeOptionIds = _dataProvider.GetParameter(); - pFilterableSpecificationAttributeOptionIds.ParameterName = "FilterableSpecificationAttributeOptionIds"; - pFilterableSpecificationAttributeOptionIds.Direction = ParameterDirection.Output; - pFilterableSpecificationAttributeOptionIds.Size = int.MaxValue - 1; - pFilterableSpecificationAttributeOptionIds.DbType = DbType.String; - - var pTotalRecords = _dataProvider.GetParameter(); - pTotalRecords.ParameterName = "TotalRecords"; - pTotalRecords.Direction = ParameterDirection.Output; - pTotalRecords.DbType = DbType.Int32; - - //invoke stored procedure - var products = _dbContext.ExecuteStoredProcedureList( - "ProductLoadAllPaged", - pCategoryIds, - pManufacturerId, - pStoreId, - pParentGroupedProductId, - pProductTypeId, - pVisibleIndividuallyOnly, - pProductTagId, - pFeaturedProducts, - pPriceMin, - pPriceMax, - pKeywords, - pSearchDescriptions, - pSearchSku, - pSearchProductTags, - pUseFullTextSearch, - pFullTextMode, - pFilteredSpecs, - pLanguageId, - pOrderBy, - pAllowedCustomerRoleIds, - pPageIndex, - pPageSize, - pShowHidden, - pLoadFilterableSpecificationAttributeOptionIds, - pWithoutCategories, - pWithoutManufacturers, - pIsPublished, - pHomePageProducts, - pIdMin, - pIdMax, - pAvailabilityMin, - pAvailabilityMax, - pCreatedFromUtc, - pCreatedToUtc, - pFilterableSpecificationAttributeOptionIds, - pTotalRecords); - - // get filterable specification attribute option identifier - string filterableSpecificationAttributeOptionIdsStr = (pFilterableSpecificationAttributeOptionIds.Value != DBNull.Value) ? (string)pFilterableSpecificationAttributeOptionIds.Value : ""; - if (ctx.LoadFilterableSpecificationAttributeOptionIds && !string.IsNullOrWhiteSpace(filterableSpecificationAttributeOptionIdsStr)) - { - ctx.FilterableSpecificationAttributeOptionIds.AddRange(filterableSpecificationAttributeOptionIdsStr - .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x.Trim()))); - } - - // return products - int totalRecords = (pTotalRecords.Value != DBNull.Value) ? Convert.ToInt32(pTotalRecords.Value) : 0; - return new PagedList(products, ctx.PageIndex, ctx.PageSize, totalRecords); - - #endregion - } - else - { - //stored procedures aren't supported. Use LINQ - - #region Search products - - var query = this.PrepareProductSearchQuery(ctx, allowedCustomerRolesIds, searchLocalizedValue); - - // only distinct products (group by ID) - // if we use standard Distinct() method, then all fields will be compared (low performance) - // it'll not work in SQL Server Compact when searching products by a keyword) - query = from p in query - group p by p.Id into pGroup - orderby pGroup.Key - select pGroup.FirstOrDefault(); - - //sort products - if (ctx.OrderBy == ProductSortingEnum.Position && ctx.CategoryIds != null && ctx.CategoryIds.Count > 0) - { - //category position - var firstCategoryId = ctx.CategoryIds[0]; - query = query.OrderBy(p => p.ProductCategories.Where(pc => pc.CategoryId == firstCategoryId).FirstOrDefault().DisplayOrder); - } - else if (ctx.OrderBy == ProductSortingEnum.Position && ctx.ManufacturerId > 0) - { - //manufacturer position - query = query.OrderBy(p => p.ProductManufacturers.Where(pm => pm.ManufacturerId == ctx.ManufacturerId).FirstOrDefault().DisplayOrder); - } - else if (ctx.OrderBy == ProductSortingEnum.Position && ctx.ParentGroupedProductId > 0) - { - //parent product specified (sort associated products) - query = query.OrderBy(p => p.DisplayOrder); - } - else if (ctx.OrderBy == ProductSortingEnum.Position) - { - //otherwise sort by name - query = query.OrderBy(p => p.Name); - } - else if (ctx.OrderBy == ProductSortingEnum.NameAsc) - { - //Name: A to Z - query = query.OrderBy(p => p.Name); - } - else if (ctx.OrderBy == ProductSortingEnum.NameDesc) - { - //Name: Z to A - query = query.OrderByDescending(p => p.Name); - } - else if (ctx.OrderBy == ProductSortingEnum.PriceAsc) - { - //Price: Low to High - query = query.OrderBy(p => p.Price); - } - else if (ctx.OrderBy == ProductSortingEnum.PriceDesc) - { - //Price: High to Low - query = query.OrderByDescending(p => p.Price); - } - else if (ctx.OrderBy == ProductSortingEnum.CreatedOn) - { - //creation date - query = query.OrderByDescending(p => p.CreatedOnUtc); - } - else if (ctx.OrderBy == ProductSortingEnum.CreatedOnAsc) - { - // creation date: old to new - query = query.OrderBy(p => p.CreatedOnUtc); - } - else - { - //actually this code is not reachable - query = query.OrderBy(p => p.Name); - } - - var products = new PagedList(query, ctx.PageIndex, ctx.PageSize); - - //get filterable specification attribute option identifier - if (ctx.LoadFilterableSpecificationAttributeOptionIds) - { - var querySpecs = from p in query - join psa in _productSpecificationAttributeRepository.Table on p.Id equals psa.ProductId - where psa.AllowFiltering - select psa.SpecificationAttributeOptionId; - //only distinct attributes - ctx.FilterableSpecificationAttributeOptionIds = querySpecs - .Distinct() - .ToList(); - } - - return products; - - #endregion - } - } + var products = query.ToList(); - public virtual IQueryable PrepareProductSearchQuery( - ProductSearchContext ctx, - IEnumerable allowedCustomerRolesIds = null, - bool searchLocalizedValue = false) - { - return PrepareProductSearchQuery(ctx, x => x, allowedCustomerRolesIds, searchLocalizedValue); + // Sort by passed identifier sequence. + return products.OrderBySequence(productIds).ToList(); } - public virtual IQueryable PrepareProductSearchQuery( - ProductSearchContext ctx, - Expression> selector, - IEnumerable allowedCustomerRolesIds = null, - bool searchLocalizedValue = false) + public virtual Product GetProductBySystemName(string systemName) { - Guard.ArgumentNotNull(() => ctx); - Guard.ArgumentNotNull(() => selector); - - if (allowedCustomerRolesIds == null) + if (systemName.IsEmpty()) { - allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(cr => cr.Active).Select(cr => cr.Id).ToList(); + return null; } - - // products - var query = ctx.Query ?? _productRepository.Table; - query = query.Where(p => !p.Deleted); - if (!ctx.IsPublished.HasValue) - { - if (!ctx.ShowHidden) - query = query.Where(p => p.Published); - } - else - { - query = query.Where(p => p.Published == ctx.IsPublished.Value); - } + var product = _productRepository.Table.FirstOrDefault(x => x.SystemName == systemName && x.IsSystemProduct); + return product; + } - if (ctx.ParentGroupedProductId > 0) + private IQueryable ApplyLoadFlags(IQueryable query, ProductLoadFlags flags) + { + if (flags.HasFlag(ProductLoadFlags.WithAttributeCombinations)) { - query = query.Where(p => p.ParentGroupedProductId == ctx.ParentGroupedProductId); + query = query.Include(x => x.ProductVariantAttributeCombinations); } - if (ctx.VisibleIndividuallyOnly) + if (flags.HasFlag(ProductLoadFlags.WithBundleItems)) { - query = query.Where(p => p.VisibleIndividually); + query = query.Include(x => x.ProductBundleItems.Select(y => y.Product)); } - if (ctx.HomePageProducts.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithCategories)) { - query = query.Where(p => p.ShowOnHomePage == ctx.HomePageProducts.Value); + query = query.Include(x => x.ProductCategories.Select(y => y.Category)); } - if (ctx.ProductType.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithDiscounts)) { - int productTypeId = (int)ctx.ProductType.Value; - query = query.Where(p => p.ProductTypeId == productTypeId); + query = query.Include(x => x.AppliedDiscounts); } - if (ctx.ProductIds != null && ctx.ProductIds.Count > 0) + if (flags.HasFlag(ProductLoadFlags.WithManufacturers)) { - query = query.Where(x => ctx.ProductIds.Contains(x.Id)); + query = query.Include(x => x.ProductManufacturers.Select(y => y.Manufacturer)); } - else - { - if (ctx.IdMin != 0) - query = query.Where(x => x.Id >= ctx.IdMin); - if (ctx.IdMax != 0) - query = query.Where(x => x.Id <= ctx.IdMax); - } - - if (ctx.AvailabilityMinimum.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithPictures)) { - query = query.Where(x => x.StockQuantity >= ctx.AvailabilityMinimum.Value); + query = query.Include(x => x.ProductPictures); } - if (ctx.AvailabilityMaximum.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithReviews)) { - query = query.Where(x => x.StockQuantity <= ctx.AvailabilityMaximum.Value); + query = query.Include(x => x.ProductReviews); } - if (ctx.CreatedFromUtc.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithSpecificationAttributes)) { - query = query.Where(x => x.CreatedOnUtc >= ctx.CreatedFromUtc.Value); + query = query.Include(x => x.ProductSpecificationAttributes.Select(y => y.SpecificationAttributeOption)); } - if (ctx.CreatedToUtc.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithTags)) { - query = query.Where(x => x.CreatedOnUtc <= ctx.CreatedToUtc.Value); + query = query.Include(x => x.ProductTags); } - //The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. - //That's why we pass the date value - var nowUtc = DateTime.UtcNow; - - if (ctx.PriceMin.HasValue) + if (flags.HasFlag(ProductLoadFlags.WithTierPrices)) { - //min price - query = query.Where(p => - //special price (specified price and valid date range) - ((p.SpecialPrice.HasValue && - ((!p.SpecialPriceStartDateTimeUtc.HasValue || - p.SpecialPriceStartDateTimeUtc.Value < nowUtc) && - (!p.SpecialPriceEndDateTimeUtc.HasValue || - p.SpecialPriceEndDateTimeUtc.Value > nowUtc))) && - (p.SpecialPrice >= ctx.PriceMin.Value)) - || - //regular price (price isn't specified or date range isn't valid) - ((!p.SpecialPrice.HasValue || - ((p.SpecialPriceStartDateTimeUtc.HasValue && - p.SpecialPriceStartDateTimeUtc.Value > nowUtc) || - (p.SpecialPriceEndDateTimeUtc.HasValue && - p.SpecialPriceEndDateTimeUtc.Value < nowUtc))) && - (p.Price >= ctx.PriceMin.Value))); - } - if (ctx.PriceMax.HasValue) - { - //max price - query = query.Where(p => - //special price (specified price and valid date range) - ((p.SpecialPrice.HasValue && - ((!p.SpecialPriceStartDateTimeUtc.HasValue || - p.SpecialPriceStartDateTimeUtc.Value < nowUtc) && - (!p.SpecialPriceEndDateTimeUtc.HasValue || - p.SpecialPriceEndDateTimeUtc.Value > nowUtc))) && - (p.SpecialPrice <= ctx.PriceMax.Value)) - || - //regular price (price isn't specified or date range isn't valid) - ((!p.SpecialPrice.HasValue || - ((p.SpecialPriceStartDateTimeUtc.HasValue && - p.SpecialPriceStartDateTimeUtc.Value > nowUtc) || - (p.SpecialPriceEndDateTimeUtc.HasValue && - p.SpecialPriceEndDateTimeUtc.Value < nowUtc))) && - (p.Price <= ctx.PriceMax.Value))); - } - if (!ctx.ShowHidden) - { - //available dates - query = query.Where(p => - (!p.AvailableStartDateTimeUtc.HasValue || p.AvailableStartDateTimeUtc.Value < nowUtc) && - (!p.AvailableEndDateTimeUtc.HasValue || p.AvailableEndDateTimeUtc.Value > nowUtc)); + query = query.Include(x => x.TierPrices); } - // searching by keyword - if (!String.IsNullOrWhiteSpace(ctx.Keywords)) + if (flags.HasFlag(ProductLoadFlags.WithAttributes)) { - query = from p in query - join lp in _localizedPropertyRepository.Table on p.Id equals lp.EntityId into p_lp - from lp in p_lp.DefaultIfEmpty() - from pt in p.ProductTags.DefaultIfEmpty() - where (p.Name.Contains(ctx.Keywords)) || - (ctx.SearchDescriptions && p.ShortDescription.Contains(ctx.Keywords)) || - (ctx.SearchDescriptions && p.FullDescription.Contains(ctx.Keywords)) || - (ctx.SearchSku && p.Sku.Contains(ctx.Keywords)) || - (ctx.SearchProductTags && pt.Name.Contains(ctx.Keywords)) || - //localized values - (searchLocalizedValue && lp.LanguageId == ctx.LanguageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "Name" && lp.LocaleValue.Contains(ctx.Keywords)) || - (ctx.SearchDescriptions && searchLocalizedValue && lp.LanguageId == ctx.LanguageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "ShortDescription" && lp.LocaleValue.Contains(ctx.Keywords)) || - (ctx.SearchDescriptions && searchLocalizedValue && lp.LanguageId == ctx.LanguageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "FullDescription" && lp.LocaleValue.Contains(ctx.Keywords)) - //UNDONE search localized values in associated product tags - select p; + query = query.Include(x => x.ProductVariantAttributes.Select(y => y.ProductAttribute)); } - if (!ctx.ShowHidden && !QuerySettings.IgnoreAcl) + if (flags.HasFlag(ProductLoadFlags.WithAttributeValues)) { - 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() - where !p.SubjectToAcl || allowedCustomerRolesIds.Contains(acl.CustomerRoleId) - select p; + query = query.Include(x => x.ProductVariantAttributes.Select(y => y.ProductVariantAttributeValues)); } - if (ctx.StoreId > 0 && !QuerySettings.IgnoreMultiStore) + if (flags.HasFlag(ProductLoadFlags.WithDeliveryTime)) { - 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() - where !p.LimitedToStores || ctx.StoreId == sm.StoreId - select p; + query = query.Include(x => x.DeliveryTime); } - // search by specs - if (ctx.FilteredSpecs != null && ctx.FilteredSpecs.Count > 0) - { - query = - from p in query - where !ctx.FilteredSpecs.Except - ( - p.ProductSpecificationAttributes - .Where(psa => psa.AllowFiltering) - .Select(psa => psa.SpecificationAttributeOptionId) - ).Any() - select p; - } + return query; + } - // category filtering - if (ctx.WithoutCategories.HasValue) - { - if (ctx.WithoutCategories.Value) - query = query.Where(x => x.ProductCategories.Count == 0); - else - query = query.Where(x => x.ProductCategories.Count > 0); - } - else if (ctx.CategoryIds != null && ctx.CategoryIds.Count > 0) - { - //search in subcategories - if (ctx.MatchAllcategories) - { - query = from p in query - where ctx.CategoryIds.All(i => p.ProductCategories.Any(p2 => p2.CategoryId == i)) - from pc in p.ProductCategories - where (!ctx.FeaturedProducts.HasValue || ctx.FeaturedProducts.Value == pc.IsFeaturedProduct) - select p; - } - else - { - query = from p in query - from pc in p.ProductCategories.Where(pc => ctx.CategoryIds.Contains(pc.CategoryId)) - where (!ctx.FeaturedProducts.HasValue || ctx.FeaturedProducts.Value == pc.IsFeaturedProduct) - select p; - } - } + public virtual void InsertProduct(Product product) + { + Guard.NotNull(product, nameof(product)); - // manufacturer filtering - if (ctx.WithoutManufacturers.HasValue) - { - if (ctx.WithoutManufacturers.Value) - query = query.Where(x => x.ProductManufacturers.Count == 0); - else - query = query.Where(x => x.ProductManufacturers.Count > 0); - } - else if (ctx.ManufacturerId > 0) - { - query = from p in query - from pm in p.ProductManufacturers.Where(pm => pm.ManufacturerId == ctx.ManufacturerId) - where (!ctx.FeaturedProducts.HasValue || ctx.FeaturedProducts.Value == pm.IsFeaturedProduct) - select p; - } + _productRepository.Insert(product); + } - // related products filtering - //if (relatedToProductId > 0) - //{ - // query = from p in query - // join rp in _relatedProductRepository.Table on p.Id equals rp.ProductId2 - // where (relatedToProductId == rp.ProductId1) - // select p; - //} - - // tag filtering - if (ctx.ProductTagId > 0) - { - query = from p in query - from pt in p.ProductTags.Where(pt => pt.Id == ctx.ProductTagId) - select p; - } + public virtual void UpdateProduct(Product product) + { + Guard.NotNull(product, nameof(product)); - return query.Select(selector); - } + _productRepository.Update(product); + } - /// - /// Update product review totals - /// - /// Product public virtual void UpdateProductReviewTotals(Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); - int approvedRatingSum = 0; + int approvedRatingSum = 0; int notApprovedRatingSum = 0; int approvedTotalReviews = 0; int notApprovedTotalReviews = 0; @@ -1071,17 +333,13 @@ public virtual void UpdateProductReviewTotals(Product product) product.NotApprovedTotalReviews = notApprovedTotalReviews; UpdateProduct(product); } - - /// - /// Get low stock products - /// - /// Result + 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; @@ -1090,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; @@ -1109,10 +367,6 @@ orderby pGroup.Key return result; } - /// Gets a product by SKU - /// - /// SKU - /// Product public virtual Product GetProductBySku(string sku) { if (String.IsNullOrEmpty(sku)) @@ -1128,11 +382,6 @@ public virtual Product GetProductBySku(string sku) return product; } - /// - /// Gets a product by GTIN - /// - /// GTIN - /// Product public virtual Product GetProductByGtin(string gtin) { if (String.IsNullOrEmpty(gtin)) @@ -1142,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; @@ -1179,12 +427,6 @@ public virtual Product GetProductByName(string name) return product; } - /// - /// Adjusts inventory - /// - /// Shopping cart item - /// A value indicating whether to increase or descrease product stock quantity - /// Adjust inventory result public virtual AdjustInventoryResult AdjustInventory(OrganizedShoppingCartItem sci, bool decrease) { if (sci == null) @@ -1207,17 +449,9 @@ public virtual AdjustInventoryResult AdjustInventory(OrganizedShoppingCartItem s } } - /// - /// Adjusts inventory - /// - /// Order item - /// A value indicating whether to increase or descrease product stock quantity - /// Quantity - /// Adjust inventory result public virtual AdjustInventoryResult AdjustInventory(OrderItem orderItem, bool decrease, int quantity) { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); + Guard.NotNull(orderItem, nameof(orderItem)); if (orderItem.Product.ProductType == ProductType.BundledProduct && orderItem.Product.BundlePerItemShoppingCart) { @@ -1244,18 +478,9 @@ public virtual AdjustInventoryResult AdjustInventory(OrderItem orderItem, bool d } } - /// - /// Adjusts inventory - /// - /// Product - /// A value indicating whether to increase or descrease product stock quantity - /// Quantity - /// Attributes in XML format - /// Adjust inventory result public virtual AdjustInventoryResult AdjustInventory(Product product, bool decrease, int quantity, string attributesXml) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); var result = new AdjustInventoryResult(); @@ -1296,10 +521,10 @@ public virtual AdjustInventoryResult AdjustInventory(Product product, bool decre product.Published = newPublished; UpdateProduct(product); - + //send email notification if (decrease && product.NotifyAdminForQuantityBelow > result.StockQuantityNew) - _workflowMessageService.SendQuantityBelowStoreOwnerNotification(product, _localizationSettings.DefaultAdminLanguageId); + _services.MessageFactory.SendQuantityBelowStoreOwnerNotification(product, _localizationSettings.DefaultAdminLanguageId); } break; case ManageInventoryMethod.ManageStockByAttributes: @@ -1337,14 +562,9 @@ public virtual AdjustInventoryResult AdjustInventory(Product product, bool decre return result; } - /// - /// Update HasTierPrices property (used for performance optimization) - /// - /// Product public virtual void UpdateHasTierPricesProperty(Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); var prevValue = product.HasTierPrices; product.HasTierPrices = product.TierPrices.Count > 0; @@ -1352,14 +572,9 @@ public virtual void UpdateHasTierPricesProperty(Product product) UpdateProduct(product); } - /// - /// Update LowestAttributeCombinationPrice property (used for performance optimization) - /// - /// Product public virtual void UpdateLowestAttributeCombinationPriceProperty(Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); var prevValue = product.LowestAttributeCombinationPrice; @@ -1369,14 +584,9 @@ public virtual void UpdateLowestAttributeCombinationPriceProperty(Product produc UpdateProduct(product); } - /// - /// Update HasDiscountsApplied property (used for performance optimization) - /// - /// Product public virtual void UpdateHasDiscountsApplied(Product product) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); var prevValue = product.HasDiscountsApplied; product.HasDiscountsApplied = product.AppliedDiscounts.Count > 0; @@ -1386,9 +596,15 @@ public virtual void UpdateHasDiscountsApplied(Product product) public virtual Multimap GetProductTagsByProductIds(int[] productIds) { - Guard.ArgumentNotNull(() => productIds); + 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 @@ -1397,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); @@ -1408,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.ArgumentNotNull(() => productIds); + Guard.NotNull(productIds, nameof(productIds)); + + var map = new Multimap(); + if (!productIds.Any()) + { + return map; + } - var query = _productRepository.TableUntracked + 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 @@ -1421,59 +666,25 @@ public virtual Multimap GetAppliedDiscountsByProductIds(int[] pro Discounts = x.AppliedDiscounts }); - var map = new Multimap(); - foreach (var item in query.ToList()) { - foreach (var discount in item.Discounts) - map.Add(item.ProductId, discount); + map.AddRange(item.ProductId, item.Discounts); } return map; } - public virtual Multimap GetProductSpecificationAttributesByProductIds(int[] productIds) - { - Guard.ArgumentNotNull(() => productIds); - - var query = _productSpecificationAttributeRepository.TableUntracked - .Expand(x => x.SpecificationAttributeOption) - .Expand(x => x.SpecificationAttributeOption.SpecificationAttribute) - .Where(x => productIds.Contains(x.ProductId)); - - var map = query - .OrderBy(x => x.DisplayOrder) - .ToList() - .ToMultimap(x => x.ProductId, x => x); - - return map; - } - #endregion #region Related products - /// - /// Deletes a related product - /// - /// Related product public virtual void DeleteRelatedProduct(RelatedProduct relatedProduct) { - if (relatedProduct == null) - throw new ArgumentNullException("relatedProduct"); - - _relatedProductRepository.Delete(relatedProduct); + Guard.NotNull(relatedProduct, nameof(relatedProduct)); - //event notification - _services.EventPublisher.EntityDeleted(relatedProduct); + _relatedProductRepository.Delete(relatedProduct); } - /// - /// Gets a related product collection by product identifier - /// - /// The first product identifier - /// A value indicating whether to show hidden records - /// Related product collection public virtual IList GetRelatedProductsByProductId1(int productId1, bool showHidden = false) { var query = from rp in _relatedProductRepository.Table @@ -1486,11 +697,6 @@ orderby rp.DisplayOrder return relatedProducts; } - /// - /// Gets a related product - /// - /// Related product identifier - /// Related product public virtual RelatedProduct GetRelatedProductById(int relatedProductId) { if (relatedProductId == 0) @@ -1500,41 +706,20 @@ public virtual RelatedProduct GetRelatedProductById(int relatedProductId) return relatedProduct; } - /// - /// Inserts a related product - /// - /// Related product public virtual void InsertRelatedProduct(RelatedProduct relatedProduct) { - if (relatedProduct == null) - throw new ArgumentNullException("relatedProduct"); - - _relatedProductRepository.Insert(relatedProduct); + Guard.NotNull(relatedProduct, nameof(relatedProduct)); - //event notification - _services.EventPublisher.EntityInserted(relatedProduct); + _relatedProductRepository.Insert(relatedProduct); } - /// - /// Updates a related product - /// - /// Related product public virtual void UpdateRelatedProduct(RelatedProduct relatedProduct) { - if (relatedProduct == null) - throw new ArgumentNullException("relatedProduct"); - - _relatedProductRepository.Update(relatedProduct); + Guard.NotNull(relatedProduct, nameof(relatedProduct)); - //event notification - _services.EventPublisher.EntityUpdated(relatedProduct); + _relatedProductRepository.Update(relatedProduct); } - /// - /// Ensure existence of all mutually related products - /// - /// First product identifier - /// Number of inserted related products public virtual int EnsureMutuallyRelatedProducts(int productId1) { var relatedProducts = GetRelatedProductsByProductId1(productId1, true); @@ -1551,46 +736,40 @@ public virtual int EnsureMutuallyRelatedProducts(int productId1) #region Cross-sell products - /// - /// Deletes a cross-sell product - /// - /// Cross-sell identifier public virtual void DeleteCrossSellProduct(CrossSellProduct crossSellProduct) { - if (crossSellProduct == null) - throw new ArgumentNullException("crossSellProduct"); - - _crossSellProductRepository.Delete(crossSellProduct); + Guard.NotNull(crossSellProduct, nameof(crossSellProduct)); - //event notification - _services.EventPublisher.EntityDeleted(crossSellProduct); + _crossSellProductRepository.Delete(crossSellProduct); } - /// - /// Gets a cross-sell product collection by product identifier - /// - /// The first product identifier - /// A value indicating whether to show hidden records - /// Cross-sell product collection public virtual IList GetCrossSellProductsByProductId1(int productId1, bool showHidden = false) { var query = from csp in _crossSellProductRepository.Table join p in _productRepository.Table on csp.ProductId2 equals p.Id - where csp.ProductId1 == productId1 && - !p.Deleted && - (showHidden || p.Published) + where csp.ProductId1 == productId1 && !p.Deleted && (showHidden || p.Published) orderby csp.Id select csp; + var crossSellProducts = query.ToList(); return crossSellProducts; } - /// - /// Gets a cross-sell product - /// - /// Cross-sell product identifier - /// Cross-sell product - public virtual CrossSellProduct GetCrossSellProductById(int crossSellProductId) + public virtual IList GetCrossSellProductsByProductIds(IEnumerable productIds, bool showHidden = false) + { + Guard.NotNull(productIds, nameof(productIds)); + + var query = from csp in _crossSellProductRepository.Table + join p in _productRepository.Table on csp.ProductId2 equals p.Id + where productIds.Contains(csp.ProductId1) && !p.Deleted && (showHidden || p.Published) + orderby csp.Id + select csp; + + var crossSellProducts = query.ToList(); + return crossSellProducts; + } + + public virtual CrossSellProduct GetCrossSellProductById(int crossSellProductId) { if (crossSellProductId == 0) return null; @@ -1599,90 +778,42 @@ public virtual CrossSellProduct GetCrossSellProductById(int crossSellProductId) return crossSellProduct; } - /// - /// Inserts a cross-sell product - /// - /// Cross-sell product public virtual void InsertCrossSellProduct(CrossSellProduct crossSellProduct) { - if (crossSellProduct == null) - throw new ArgumentNullException("crossSellProduct"); - - _crossSellProductRepository.Insert(crossSellProduct); + Guard.NotNull(crossSellProduct, nameof(crossSellProduct)); - //event notification - _services.EventPublisher.EntityInserted(crossSellProduct); + _crossSellProductRepository.Insert(crossSellProduct); } - /// - /// Updates a cross-sell product - /// - /// Cross-sell product public virtual void UpdateCrossSellProduct(CrossSellProduct crossSellProduct) { - if (crossSellProduct == null) - throw new ArgumentNullException("crossSellProduct"); + Guard.NotNull(crossSellProduct, nameof(crossSellProduct)); - _crossSellProductRepository.Update(crossSellProduct); - - //event notification - _services.EventPublisher.EntityUpdated(crossSellProduct); + _crossSellProductRepository.Update(crossSellProduct); } - /// - /// Gets a cross-sells - /// - /// Shopping cart - /// Number of products to return - /// Cross-sells public virtual IList GetCrosssellProductsByShoppingCart(IList cart, int numberOfProducts) - { - var result = new List(); + { + var result = new List(); - if (numberOfProducts == 0) - return result; + if (numberOfProducts == 0) + return result; - if (cart == null || cart.Count == 0) - return result; + if (cart == null || cart.Count == 0) + return result; - var cartProductIds = new List(); - foreach (var sci in cart) - { - int prodId = sci.Item.ProductId; - if (!cartProductIds.Contains(prodId)) - cartProductIds.Add(prodId); - } + var cartProductIds = new HashSet(cart.Select(x => x.Item.ProductId)); + var csItems = GetCrossSellProductsByProductIds(cartProductIds); + var productIdsToLoad = new HashSet(csItems.Select(x => x.ProductId2).Except(cartProductIds)); - foreach (var sci in cart) - { - var crossSells = GetCrossSellProductsByProductId1(sci.Item.ProductId); - foreach (var crossSell in crossSells) - { - //validate that this product is not added to result yet - //validate that this product is not in the cart - if (result.Find(p => p.Id == crossSell.ProductId2) == null && - !cartProductIds.Contains(crossSell.ProductId2)) - { - var productToAdd = GetProductById(crossSell.ProductId2); - //validate product - if (productToAdd == null || productToAdd.Deleted || !productToAdd.Published) - continue; - - //add a product to result - result.Add(productToAdd); - if (result.Count >= numberOfProducts) - return result; - } - } - } - return result; - } + if (productIdsToLoad.Count > 0) + { + result.AddRange(GetProductsByIds(productIdsToLoad.Take(numberOfProducts).ToArray())); + } + + return result; + } - /// - /// Ensure existence of all mutually cross selling products - /// - /// First product identifier - /// Number of inserted cross selling products public virtual int EnsureMutuallyCrossSellProducts(int productId1) { var crossSellProducts = GetCrossSellProductsByProductId1(productId1, true); @@ -1699,28 +830,13 @@ public virtual int EnsureMutuallyCrossSellProducts(int productId1) #region Tier prices - /// - /// Deletes a tier price - /// - /// Tier price public virtual void DeleteTierPrice(TierPrice tierPrice) { - if (tierPrice == null) - throw new ArgumentNullException("tierPrice"); - - _tierPriceRepository.Delete(tierPrice); + Guard.NotNull(tierPrice, nameof(tierPrice)); - _requestCache.RemoveByPattern(PRODUCTS_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityDeleted(tierPrice); + _tierPriceRepository.Delete(tierPrice); } - /// - /// Gets a tier price - /// - /// Tier price identifier - /// Tier price public virtual TierPrice GetTierPriceById(int tierPriceId) { if (tierPriceId == 0) @@ -1732,7 +848,12 @@ public virtual TierPrice GetTierPriceById(int tierPriceId) public virtual Multimap GetTierPricesByProductIds(int[] productIds, Customer customer = null, int storeId = 0) { - Guard.ArgumentNotNull(() => productIds); + Guard.NotNull(productIds, nameof(productIds)); + + if (!productIds.Any()) + { + return new Multimap(); + } var query = from x in _tierPriceRepository.TableUntracked @@ -1755,59 +876,31 @@ where productIds.Contains(x.ProductId) return map; } - /// - /// Inserts a tier price - /// - /// Tier price public virtual void InsertTierPrice(TierPrice tierPrice) { - if (tierPrice == null) - throw new ArgumentNullException("tierPrice"); + Guard.NotNull(tierPrice, nameof(tierPrice)); - _tierPriceRepository.Insert(tierPrice); - - _requestCache.RemoveByPattern(PRODUCTS_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityInserted(tierPrice); + _tierPriceRepository.Insert(tierPrice); } - /// - /// Updates the tier price - /// - /// Tier price public virtual void UpdateTierPrice(TierPrice tierPrice) { - if (tierPrice == null) - throw new ArgumentNullException("tierPrice"); - - _tierPriceRepository.Update(tierPrice); + Guard.NotNull(tierPrice, nameof(tierPrice)); - _requestCache.RemoveByPattern(PRODUCTS_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityUpdated(tierPrice); + _tierPriceRepository.Update(tierPrice); } #endregion #region Product pictures - /// - /// Deletes a product picture - /// - /// Product picture public virtual void DeleteProductPicture(ProductPicture productPicture) { - if (productPicture == null) - throw new ArgumentNullException("productPicture"); + Guard.NotNull(productPicture, nameof(productPicture)); - UnassignDeletedPictureFromVariantCombinations(productPicture); + UnassignDeletedPictureFromVariantCombinations(productPicture); _productPictureRepository.Delete(productPicture); - - //event notification - _services.EventPublisher.EntityDeleted(productPicture); } private void UnassignDeletedPictureFromVariantCombinations(ProductPicture productPicture) @@ -1840,11 +933,6 @@ from c in this._productVariantAttributeCombinationRepository.Table } } - /// - /// Gets a product pictures by product identifier - /// - /// The product identifier - /// Product pictures public virtual IList GetProductPicturesByProductId(int productId) { var query = from pp in _productPictureRepository.Table @@ -1857,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 @@ -1882,11 +977,6 @@ where productIds.Contains(pp.ProductId) } } - /// - /// Gets a product picture - /// - /// Product picture identifier - /// Product picture public virtual ProductPicture GetProductPictureById(int productPictureId) { if (productPictureId == 0) @@ -1896,19 +986,11 @@ public virtual ProductPicture GetProductPictureById(int productPictureId) return pp; } - /// - /// Inserts a product picture - /// - /// Product picture public virtual void InsertProductPicture(ProductPicture productPicture) { - if (productPicture == null) - throw new ArgumentNullException("productPicture"); - - _productPictureRepository.Insert(productPicture); + Guard.NotNull(productPicture, nameof(productPicture)); - //event notification - _services.EventPublisher.EntityInserted(productPicture); + _productPictureRepository.Insert(productPicture); } /// @@ -1917,27 +999,18 @@ public virtual void InsertProductPicture(ProductPicture productPicture) /// Product picture public virtual void UpdateProductPicture(ProductPicture productPicture) { - if (productPicture == null) - throw new ArgumentNullException("productPicture"); - - _productPictureRepository.Update(productPicture); + Guard.NotNull(productPicture, nameof(productPicture)); - //event notification - _services.EventPublisher.EntityUpdated(productPicture); + _productPictureRepository.Update(productPicture); } #endregion #region Bundled products - /// - /// Inserts a product bundle item - /// - /// Product bundle item public virtual void InsertBundleItem(ProductBundleItem bundleItem) { - if (bundleItem == null) - throw new ArgumentNullException("bundleItem"); + Guard.NotNull(bundleItem, nameof(bundleItem)); if (bundleItem.BundleProductId == 0) throw new SmartException("BundleProductId of a bundle item cannot be 0."); @@ -1949,46 +1022,47 @@ public virtual void InsertBundleItem(ProductBundleItem bundleItem) throw new SmartException("A bundle item cannot be an element of itself."); _productBundleItemRepository.Insert(bundleItem); - - //event notification - _services.EventPublisher.EntityInserted(bundleItem); } - /// - /// Updates a product bundle item - /// - /// Product bundle item public virtual void UpdateBundleItem(ProductBundleItem bundleItem) { - if (bundleItem == null) - throw new ArgumentNullException("bundleItem"); + Guard.NotNull(bundleItem, nameof(bundleItem)); _productBundleItemRepository.Update(bundleItem); - - //event notification - _services.EventPublisher.EntityUpdated(bundleItem); } - /// - /// Deletes a product bundle item - /// - /// Product bundle item public virtual void DeleteBundleItem(ProductBundleItem bundleItem) { - if (bundleItem == null) - throw new ArgumentNullException("bundleItem"); + Guard.NotNull(bundleItem, nameof(bundleItem)); - _productBundleItemRepository.Delete(bundleItem); + // remove bundles from shopping carts (otherwise bundle item cannot be deleted) + var parentCartItemIds = _shoppingCartItemRepository.TableUntracked + .Where(x => x.BundleItemId == bundleItem.Id && x.ParentItemId != null) + .Select(x => x.ParentItemId) + .ToList(); + + if (parentCartItemIds.Any()) + { + var cartItems = _shoppingCartItemRepository.Table + .Where(x => parentCartItemIds.Contains(x.Id)) + .ToList(); + + foreach (var parentItem in cartItems) + { + var childItems = _shoppingCartItemRepository.Table + .Where(x => x.ParentItemId != null && x.ParentItemId.Value == parentItem.Id && x.Id != parentItem.Id) + .ToList(); + + childItems.Each(x => _shoppingCartItemRepository.Delete(x)); + + _shoppingCartItemRepository.Delete(parentItem); + } + } - //event notification - _services.EventPublisher.EntityDeleted(bundleItem); + // delete bundle item + _productBundleItemRepository.Delete(bundleItem); } - /// - /// Get a product bundle item by item identifier - /// - /// Product bundle item identifier - /// Product bundle item public virtual ProductBundleItem GetBundleItemById(int bundleItemId) { if (bundleItemId == 0) @@ -1997,13 +1071,7 @@ public virtual ProductBundleItem GetBundleItemById(int bundleItemId) return _productBundleItemRepository.GetById(bundleItemId); } - /// - /// Gets a list of bundle items for a particular product identifier - /// - /// Product identifier - /// A value indicating whether to show hidden records - /// List of bundle items - 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 @@ -2012,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(); @@ -2021,9 +1089,14 @@ orderby pbi.DisplayOrder return bundleItemData; } - public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) + public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) { - Guard.ArgumentNotNull(() => productIds); + Guard.NotNull(productIds, nameof(productIds)); + + if (!productIds.Any()) + { + return new Multimap(); + } var query = from pbi in _productBundleItemRepository.TableUntracked @@ -2032,15 +1105,32 @@ where productIds.Contains(pbi.BundleProductId) && !p.Deleted && (showHidden || ( orderby pbi.DisplayOrder select pbi; - var map = query - .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; + } - #endregion - } + 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/ProductTagService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs index a80d073138..798df87df0 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductTagService.cs @@ -24,12 +24,12 @@ public partial class ProductTagService : IProductTagService /// /// {0} : store ID /// - private const string PRODUCTTAG_COUNT_KEY = "sm.producttag.count-{0}"; + private const string PRODUCTTAG_COUNT_KEY = "producttag:count-{0}"; /// /// Key pattern to clear cache /// - private const string PRODUCTTAG_PATTERN_KEY = "sm.producttag."; + private const string PRODUCTTAG_PATTERN_KEY = "producttag:*"; #endregion @@ -154,9 +154,6 @@ public virtual void DeleteProductTag(ProductTag productTag) //cache _cacheManager.RemoveByPattern(PRODUCTTAG_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(productTag); } /// @@ -222,11 +219,7 @@ public virtual void InsertProductTag(ProductTag productTag) _productTagRepository.Insert(productTag); - //cache _cacheManager.RemoveByPattern(PRODUCTTAG_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(productTag); } /// @@ -240,11 +233,7 @@ public virtual void UpdateProductTag(ProductTag productTag) _productTagRepository.Update(productTag); - //cache _cacheManager.RemoveByPattern(PRODUCTTAG_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productTag); } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductTemplateService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductTemplateService.cs index 6964de3a78..fbf625225e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductTemplateService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductTemplateService.cs @@ -49,9 +49,6 @@ public virtual void DeleteProductTemplate(ProductTemplate productTemplate) throw new ArgumentNullException("productTemplate"); _productTemplateRepository.Delete(productTemplate); - - //event notification - _eventPublisher.EntityDeleted(productTemplate); } /// @@ -91,9 +88,6 @@ public virtual void InsertProductTemplate(ProductTemplate productTemplate) throw new ArgumentNullException("productTemplate"); _productTemplateRepository.Insert(productTemplate); - - //event notification - _eventPublisher.EntityInserted(productTemplate); } /// @@ -106,9 +100,6 @@ public virtual void UpdateProductTemplate(ProductTemplate productTemplate) throw new ArgumentNullException("productTemplate"); _productTemplateRepository.Update(productTemplate); - - //event notification - _eventPublisher.EntityUpdated(productTemplate); } #endregion diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductsSearchingEvent.cs b/src/Libraries/SmartStore.Services/Catalog/ProductsSearchingEvent.cs deleted file mode 100644 index 24b70ab6cf..0000000000 --- a/src/Libraries/SmartStore.Services/Catalog/ProductsSearchingEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace SmartStore.Services.Catalog -{ - public class ProductsSearchingEvent - { - public ProductsSearchingEvent(ProductSearchContext ctx) - { - SearchContext = ctx; - } - - public ProductSearchContext SearchContext { get; private set; } - } -} diff --git a/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs b/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs index 13b86c2e3d..5ee34a2a5c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs @@ -3,19 +3,21 @@ using System.Linq; using System.Web; using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Security; namespace SmartStore.Services.Catalog { - /// - /// Recently viewed products service - /// - public partial class RecentlyViewedProductsService : IRecentlyViewedProductsService + /// + /// Recently viewed products service + /// + public partial class RecentlyViewedProductsService : IRecentlyViewedProductsService { #region Fields private readonly HttpContextBase _httpContext; private readonly IProductService _productService; - private readonly CatalogSettings _catalogSettings; + private readonly IAclService _aclService; + private readonly CatalogSettings _catalogSettings; #endregion @@ -27,12 +29,16 @@ public partial class RecentlyViewedProductsService : IRecentlyViewedProductsServ /// HTTP context /// Product service /// Catalog settings - public RecentlyViewedProductsService(HttpContextBase httpContext, IProductService productService, - CatalogSettings catalogSettings) + public RecentlyViewedProductsService( + HttpContextBase httpContext, + IProductService productService, + IAclService aclService, + CatalogSettings catalogSettings) { - this._httpContext = httpContext; - this._productService = productService; - this._catalogSettings = catalogSettings; + _httpContext = httpContext; + _productService = productService; + _aclService = aclService; + _catalogSettings = catalogSettings; } #endregion @@ -73,7 +79,6 @@ protected IList GetRecentlyViewedProductsIds(int number) #region Methods - /// /// Gets a "recently viewed products" list /// @@ -81,12 +86,13 @@ protected IList GetRecentlyViewedProductsIds(int number) /// "recently viewed products" list public virtual IList GetRecentlyViewedProducts(int number) { - var products = new List(); var productIds = GetRecentlyViewedProductsIds(number); - var recentlyViewedProducts = _productService.GetProductsByIds(productIds.ToArray()).Where(x => x.Published && !x.Deleted); + var recentlyViewedProducts = _productService + .GetProductsByIds(productIds.ToArray()) + .Where(x => x.Published && !x.Deleted && _aclService.Authorize(x)) + .ToList(); - products.AddRange(recentlyViewedProducts); - return products; + return recentlyViewedProducts; } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs index cb9808fd64..ac5bda82b6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs @@ -1,95 +1,58 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core; -using SmartStore.Core.Caching; +using SmartStore.Collections; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Events; +using SmartStore.Core.Localization; +using SmartStore.Data.Caching; namespace SmartStore.Services.Catalog { - /// - /// Specification attribute service - /// - public partial class SpecificationAttributeService : ISpecificationAttributeService - { - #region Constants - private const string PRODUCTSPECIFICATIONATTRIBUTE_ALLBYPRODUCTID_KEY = "SmartStore.productspecificationattribute.allbyproductid-{0}-{1}-{2}"; - private const string PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY = "SmartStore.productspecificationattribute."; - #endregion - - #region Fields - - private readonly IRepository _specificationAttributeRepository; - private readonly IRepository _specificationAttributeOptionRepository; - private readonly IRepository _productSpecificationAttributeRepository; - private readonly IRequestCache _requestCache; - private readonly IEventPublisher _eventPublisher; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Specification attribute repository - /// Specification attribute option repository - /// Product specification attribute repository - /// Event published - public SpecificationAttributeService(IRequestCache requestCache, - IRepository specificationAttributeRepository, - IRepository specificationAttributeOptionRepository, - IRepository productSpecificationAttributeRepository, - IEventPublisher eventPublisher) - { - _requestCache = requestCache; - _specificationAttributeRepository = specificationAttributeRepository; - _specificationAttributeOptionRepository = specificationAttributeOptionRepository; - _productSpecificationAttributeRepository = productSpecificationAttributeRepository; - _eventPublisher = eventPublisher; - } + public partial class SpecificationAttributeService : ISpecificationAttributeService + { + private readonly IRepository _specificationAttributeRepository; + private readonly IRepository _specificationAttributeOptionRepository; + private readonly IRepository _productSpecificationAttributeRepository; + private readonly IEventPublisher _eventPublisher; + + public SpecificationAttributeService( + IRepository specificationAttributeRepository, + IRepository specificationAttributeOptionRepository, + IRepository productSpecificationAttributeRepository, + IEventPublisher eventPublisher) + { + _specificationAttributeRepository = specificationAttributeRepository; + _specificationAttributeOptionRepository = specificationAttributeOptionRepository; + _productSpecificationAttributeRepository = productSpecificationAttributeRepository; + _eventPublisher = eventPublisher; - #endregion + T = NullLocalizer.Instance; + } - #region Methods + public Localizer T { get; set; } - #region Specification attribute + #region Specification attribute - /// - /// Gets a specification attribute - /// - /// The specification attribute identifier - /// Specification attribute - public virtual SpecificationAttribute GetSpecificationAttributeById(int specificationAttributeId) - { - if (specificationAttributeId == 0) - return null; + public virtual SpecificationAttribute GetSpecificationAttributeById(int specificationAttributeId) + { + if (specificationAttributeId == 0) + return null; - return _specificationAttributeRepository.GetById(specificationAttributeId); - } + return _specificationAttributeRepository.GetById(specificationAttributeId); + } - /// - /// Gets specification attributes - /// - /// Specification attribute query - public virtual IQueryable GetSpecificationAttributes() - { - var query = + public virtual IQueryable GetSpecificationAttributes() + { + var query = from sa in _specificationAttributeRepository.Table orderby sa.DisplayOrder, sa.Name select sa; - return query; - } + return query; + } - /// - /// Gets specification attributes by identifier - /// - /// Identifiers - /// Specification attribute query public virtual IQueryable GetSpecificationAttributesByIds(int[] ids) { if (ids == null || ids.Length == 0) @@ -104,304 +67,207 @@ where ids.Contains(sa.Id) return query; } - /// - /// Deletes a specification attribute - /// - /// The specification attribute - public virtual void DeleteSpecificationAttribute(SpecificationAttribute specificationAttribute) - { - if (specificationAttribute == null) - throw new ArgumentNullException("specificationAttribute"); - - // (delete localized properties of options) - var options = GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttribute.Id); - foreach (var itm in options) { - DeleteSpecificationAttributeOption(itm); - } - - _specificationAttributeRepository.Delete(specificationAttribute); + public virtual void DeleteSpecificationAttribute(SpecificationAttribute specificationAttribute) + { + if (specificationAttribute == null) + throw new ArgumentNullException("specificationAttribute"); - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + _specificationAttributeRepository.Delete(specificationAttribute); + } - //event notification - _eventPublisher.EntityDeleted(specificationAttribute); - } + public virtual void InsertSpecificationAttribute(SpecificationAttribute specificationAttribute) + { + if (specificationAttribute == null) + throw new ArgumentNullException("specificationAttribute"); - /// - /// Inserts a specification attribute - /// - /// The specification attribute - public virtual void InsertSpecificationAttribute(SpecificationAttribute specificationAttribute) - { - if (specificationAttribute == null) - throw new ArgumentNullException("specificationAttribute"); + _specificationAttributeRepository.Insert(specificationAttribute); + } - _specificationAttributeRepository.Insert(specificationAttribute); + public virtual void UpdateSpecificationAttribute(SpecificationAttribute specificationAttribute) + { + if (specificationAttribute == null) + throw new ArgumentNullException("specificationAttribute"); - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + _specificationAttributeRepository.Update(specificationAttribute); + } - //event notification - _eventPublisher.EntityInserted(specificationAttribute); - } + #endregion - /// - /// Updates the specification attribute - /// - /// The specification attribute - public virtual void UpdateSpecificationAttribute(SpecificationAttribute specificationAttribute) - { - if (specificationAttribute == null) - throw new ArgumentNullException("specificationAttribute"); + #region Specification attribute option - _specificationAttributeRepository.Update(specificationAttribute); + public virtual SpecificationAttributeOption GetSpecificationAttributeOptionById(int specificationAttributeOptionId) + { + if (specificationAttributeOptionId == 0) + return null; - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + return _specificationAttributeOptionRepository.GetById(specificationAttributeOptionId); + } - //event notification - _eventPublisher.EntityUpdated(specificationAttribute); - } + public virtual IList GetSpecificationAttributeOptionsBySpecificationAttribute(int specificationAttributeId) + { + if (specificationAttributeId == 0) + { + return new List(); + } - #endregion + var query = from sao in _specificationAttributeOptionRepository.Table + orderby sao.DisplayOrder + where sao.SpecificationAttributeId == specificationAttributeId + select sao; - #region Specification attribute option + var specificationAttributeOptions = query.ToList(); + return specificationAttributeOptions; + } - /// - /// Gets a specification attribute option - /// - /// The specification attribute option identifier - /// Specification attribute option - public virtual SpecificationAttributeOption GetSpecificationAttributeOptionById(int specificationAttributeOptionId) + public virtual Multimap GetSpecificationAttributeOptionsBySpecificationAttributeIds(int[] specificationAttributeIds) { - if (specificationAttributeOptionId == 0) - return null; + Guard.NotNull(specificationAttributeIds, nameof(specificationAttributeIds)); - return _specificationAttributeOptionRepository.GetById(specificationAttributeOptionId); - } + var options = _specificationAttributeOptionRepository.TableUntracked + .Where(x => specificationAttributeIds.Contains(x.SpecificationAttributeId)) + .OrderBy(x => x.DisplayOrder) + .ToList(); - /// - /// Gets a specification attribute option by specification attribute id - /// - /// The specification attribute identifier - /// Specification attribute option - public virtual IList GetSpecificationAttributeOptionsBySpecificationAttribute(int specificationAttributeId) - { - var query = from sao in _specificationAttributeOptionRepository.Table - orderby sao.DisplayOrder - where sao.SpecificationAttributeId == specificationAttributeId - select sao; - var specificationAttributeOptions = query.ToList(); - return specificationAttributeOptions; + var map = options.ToMultimap(x => x.SpecificationAttributeId, x => x); + return map; } - /// - /// Deletes a specification attribute option - /// - /// The specification attribute option public virtual void DeleteSpecificationAttributeOption(SpecificationAttributeOption specificationAttributeOption) - { - if (specificationAttributeOption == null) - throw new ArgumentNullException("specificationAttributeOption"); - - _specificationAttributeOptionRepository.Delete(specificationAttributeOption); - - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + { + if (specificationAttributeOption == null) + throw new ArgumentNullException("specificationAttributeOption"); - //event notification - _eventPublisher.EntityDeleted(specificationAttributeOption); - } + _specificationAttributeOptionRepository.Delete(specificationAttributeOption); + } - /// - /// Inserts a specification attribute option - /// - /// The specification attribute option - public virtual void InsertSpecificationAttributeOption(SpecificationAttributeOption specificationAttributeOption) - { - if (specificationAttributeOption == null) - throw new ArgumentNullException("specificationAttributeOption"); + public virtual void InsertSpecificationAttributeOption(SpecificationAttributeOption specificationAttributeOption) + { + if (specificationAttributeOption == null) + throw new ArgumentNullException("specificationAttributeOption"); - _specificationAttributeOptionRepository.Insert(specificationAttributeOption); + _specificationAttributeOptionRepository.Insert(specificationAttributeOption); + } - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + public virtual void UpdateSpecificationAttributeOption(SpecificationAttributeOption specificationAttributeOption) + { + if (specificationAttributeOption == null) + throw new ArgumentNullException("specificationAttributeOption"); - //event notification - _eventPublisher.EntityInserted(specificationAttributeOption); - } + _specificationAttributeOptionRepository.Update(specificationAttributeOption); + } - /// - /// Updates the specification attribute - /// - /// The specification attribute option - public virtual void UpdateSpecificationAttributeOption(SpecificationAttributeOption specificationAttributeOption) - { - if (specificationAttributeOption == null) - throw new ArgumentNullException("specificationAttributeOption"); + #endregion - _specificationAttributeOptionRepository.Update(specificationAttributeOption); + #region Product specification attribute - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + public virtual void DeleteProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute) + { + if (productSpecificationAttribute == null) + throw new ArgumentNullException("productSpecificationAttribute"); - //event notification - _eventPublisher.EntityUpdated(specificationAttributeOption); - } + _productSpecificationAttributeRepository.Delete(productSpecificationAttribute); + } - #endregion + public virtual IList GetProductSpecificationAttributesByProductId(int productId) + { + return GetProductSpecificationAttributesByProductId(productId, null, null); + } - #region Product specification attribute + public virtual IList GetProductSpecificationAttributesByProductId(int productId, bool? allowFiltering, bool? showOnProductPage) + { + if (productId == 0) + { + return new List(); + } - /// - /// Deletes a product specification attribute mapping - /// - /// Product specification attribute - public virtual void DeleteProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute) - { - if (productSpecificationAttribute == null) - throw new ArgumentNullException("productSpecificationAttribute"); + if (allowFiltering.HasValue || showOnProductPage.HasValue) + { + // Note: Join or Expand of SpecificationAttribute, both provides the same SQL. + var joinedQuery = + from psa in _productSpecificationAttributeRepository.Table + join sao in _specificationAttributeOptionRepository.Table on psa.SpecificationAttributeOptionId equals sao.Id + where psa.ProductId == productId + select new + { + ProductAttribute = psa, + Attribute = sao.SpecificationAttribute + }; + + if (allowFiltering.HasValue) + { + joinedQuery = joinedQuery.Where(x => + (x.ProductAttribute.AllowFiltering == null && x.Attribute.AllowFiltering == allowFiltering.Value) || + (x.ProductAttribute.AllowFiltering != null && x.ProductAttribute.AllowFiltering == allowFiltering.Value) + ); + } - _productSpecificationAttributeRepository.Delete(productSpecificationAttribute); + if (showOnProductPage.HasValue) + { + joinedQuery = joinedQuery.Where(x => + (x.ProductAttribute.ShowOnProductPage == null && x.Attribute.ShowOnProductPage == showOnProductPage.Value) || + (x.ProductAttribute.ShowOnProductPage != null && x.ProductAttribute.ShowOnProductPage == showOnProductPage.Value) + ); + } - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + var query = joinedQuery.Select(x => x.ProductAttribute).OrderBy(x => x.DisplayOrder); + return query.ToListCached(); + } + else + { + var query = _productSpecificationAttributeRepository.Table + .Where(x => x.ProductId == productId) + .OrderBy(x => x.DisplayOrder); - //event notification - _eventPublisher.EntityDeleted(productSpecificationAttribute); - } + return query.ToListCached(); + } + } - /// - /// Gets a product specification attribute mapping collection - /// - /// Product identifier - /// Product specification attribute mapping collection - public virtual IList GetProductSpecificationAttributesByProductId(int productId) + public virtual Multimap GetProductSpecificationAttributesByProductIds(int[] productIds) { - return GetProductSpecificationAttributesByProductId(productId, null, null); - } + Guard.NotNull(productIds, nameof(productIds)); - /// - /// Gets a product specification attribute mapping collection - /// - /// Product identifier - /// 0 to load attributes with AllowFiltering set to false, 0 to load attributes with AllowFiltering set to true, null to load all attributes - /// 0 to load attributes with ShowOnProductPage set to false, 0 to load attributes with ShowOnProductPage set to true, null to load all attributes - /// Product specification attribute mapping collection - public virtual IList GetProductSpecificationAttributesByProductId(int productId, - bool? allowFiltering, bool? showOnProductPage) - { - string allowFilteringCacheStr = "null"; - if (allowFiltering.HasValue) - allowFilteringCacheStr = allowFiltering.ToString(); - string showOnProductPageCacheStr = "null"; - if (showOnProductPage.HasValue) - showOnProductPageCacheStr = showOnProductPage.ToString(); - string key = string.Format(PRODUCTSPECIFICATIONATTRIBUTE_ALLBYPRODUCTID_KEY, productId, allowFilteringCacheStr, showOnProductPageCacheStr); - - return _requestCache.Get(key, () => + if (!productIds.Any()) { - var query = _productSpecificationAttributeRepository.Table; - query = query.Where(psa => psa.ProductId == productId); - if (allowFiltering.HasValue) - query = query.Where(psa => psa.AllowFiltering == allowFiltering.Value); - if (showOnProductPage.HasValue) - query = query.Where(psa => psa.ShowOnProductPage == showOnProductPage.Value); - query = query.OrderBy(psa => psa.DisplayOrder); - - var productSpecificationAttributes = query.ToList(); - return productSpecificationAttributes; - }); - } - - /// - /// Gets a product specification attribute mapping - /// - /// Product specification attribute mapping identifier - /// Product specification attribute mapping - public virtual ProductSpecificationAttribute GetProductSpecificationAttributeById(int productSpecificationAttributeId) - { - if (productSpecificationAttributeId == 0) - return null; - - var productSpecificationAttribute = _productSpecificationAttributeRepository.GetById(productSpecificationAttributeId); - return productSpecificationAttribute; - } + return new Multimap(); + } - /// - /// Inserts a product specification attribute mapping - /// - /// Product specification attribute mapping - public virtual void InsertProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute) - { - if (productSpecificationAttribute == null) - throw new ArgumentNullException("productSpecificationAttribute"); - - _productSpecificationAttributeRepository.Insert(productSpecificationAttribute); + var query = _productSpecificationAttributeRepository.TableUntracked + .Expand(x => x.SpecificationAttributeOption.SpecificationAttribute) + .Where(x => productIds.Contains(x.ProductId)); - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); + var map = query + .OrderBy(x => x.DisplayOrder) + .ToList() + .ToMultimap(x => x.ProductId, x => x); - //event notification - _eventPublisher.EntityInserted(productSpecificationAttribute); + return map; } - /// - /// Updates the product specification attribute mapping - /// - /// Product specification attribute mapping - public virtual void UpdateProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute) - { - if (productSpecificationAttribute == null) - throw new ArgumentNullException("productSpecificationAttribute"); - - _productSpecificationAttributeRepository.Update(productSpecificationAttribute); - - _requestCache.RemoveByPattern(PRODUCTSPECIFICATIONATTRIBUTE_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(productSpecificationAttribute); - } - - public virtual void UpdateProductSpecificationMapping(int specificationAttributeId, string field, bool value) { - if (specificationAttributeId == 0 || field.IsEmpty()) - return; - - bool isAllowFiltering = field.IsCaseInsensitiveEqual("AllowFiltering"); - bool isShowOnProductPage = field.IsCaseInsensitiveEqual("ShowOnProductPage"); - - if (!isAllowFiltering && !isShowOnProductPage) - return; - - var optionIds = ( - from sao in _specificationAttributeOptionRepository.Table - where sao.SpecificationAttributeId == specificationAttributeId - select sao.Id).ToList(); - + public virtual ProductSpecificationAttribute GetProductSpecificationAttributeById(int productSpecificationAttributeId) + { + if (productSpecificationAttributeId == 0) + return null; - foreach (int optionId in optionIds) { - var query = - from psa in _productSpecificationAttributeRepository.Table - where psa.SpecificationAttributeOptionId == optionId - select psa; + var productSpecificationAttribute = _productSpecificationAttributeRepository.GetById(productSpecificationAttributeId); + return productSpecificationAttribute; + } - if (isAllowFiltering) { - query = query.Where(a => a.AllowFiltering != value); - } - else if (isShowOnProductPage) { - query = query.Where(a => a.ShowOnProductPage != value); - } + public virtual void InsertProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute) + { + if (productSpecificationAttribute == null) + throw new ArgumentNullException("productSpecificationAttribute"); - var attributes = query.ToList(); + _productSpecificationAttributeRepository.Insert(productSpecificationAttribute); + } - foreach (var attribute in attributes) { - if (isAllowFiltering) { - attribute.AllowFiltering = value; - } - else if (isShowOnProductPage) { - attribute.ShowOnProductPage = value; - } + public virtual void UpdateProductSpecificationAttribute(ProductSpecificationAttribute productSpecificationAttribute) + { + if (productSpecificationAttribute == null) + throw new ArgumentNullException("productSpecificationAttribute"); - UpdateProductSpecificationAttribute(attribute); - } - } + _productSpecificationAttributeRepository.Update(productSpecificationAttribute); } - #endregion - - #endregion - } + #endregion + } } 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/Cms/WidgetExtensions.cs b/src/Libraries/SmartStore.Services/Cms/WidgetExtensions.cs index 5b4632827d..9e435b04d4 100644 --- a/src/Libraries/SmartStore.Services/Cms/WidgetExtensions.cs +++ b/src/Libraries/SmartStore.Services/Cms/WidgetExtensions.cs @@ -9,15 +9,15 @@ public static class WidgetExtensions { public static bool IsWidgetActive(this Provider widget, WidgetSettings widgetSettings) { - Guard.ArgumentNotNull(() => widget); + Guard.NotNull(widget, nameof(widget)); return widget.ToLazy().IsWidgetActive(widgetSettings); } public static bool IsWidgetActive(this Lazy widget, WidgetSettings widgetSettings) { - Guard.ArgumentNotNull(() => widget); - Guard.ArgumentNotNull(() => widgetSettings); + Guard.NotNull(widget, nameof(widget)); + Guard.NotNull(widgetSettings, nameof(widgetSettings)); if (widgetSettings.ActiveWidgetSystemNames == null) { diff --git a/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs b/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs index 1055aaa340..100749d906 100644 --- a/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs +++ b/src/Libraries/SmartStore.Services/Common/AddressExtentions.cs @@ -83,22 +83,36 @@ public static Address FindAddress( return source.FirstOrDefault(addressMatcher); } - /// Returns the full name of the address. - public static string GetFullName(this Address address) + /// + /// Returns the full name of the address. + /// + public static string GetFullName(this Address address, bool withCompanyName = true) { - if (address != null) - { - var sb = new StringBuilder(address.FirstName); + if (address == null) + return null; - sb.Grow(address.LastName, " "); + string result = string.Empty; + if (address.FirstName.HasValue() || address.LastName.HasValue()) + { + result = string.Format("{0} {1}", address.FirstName, address.LastName).Trim(); + } - if (address.Company.HasValue()) - { - sb.Grow("({0})".FormatWith(address.Company), " "); - } - return sb.ToString(); + if (withCompanyName && address.Company.HasValue()) + { + result = string.Concat(result, result.HasValue() ? ", " : "", address.Company); } - return null; + + return result; + } + + public static string GetFullSalutaion(this Address address) + { + if (address == null) + return null; + + return string.Format("{0}{1}", + address.Salutation.EmptyNull(), + address.Title.HasValue() ? " " + address.Title : ""); } /// diff --git a/src/Libraries/SmartStore.Services/Common/AddressService.cs b/src/Libraries/SmartStore.Services/Common/AddressService.cs index 6ba5546ccb..de52593d01 100644 --- a/src/Libraries/SmartStore.Services/Common/AddressService.cs +++ b/src/Libraries/SmartStore.Services/Common/AddressService.cs @@ -5,62 +5,47 @@ using SmartStore.Services.Directory; using SmartStore.Core.Events; using System.Collections.Generic; +using SmartStore.Templating; +using SmartStore.Services.Messages; +using SmartStore.Core.Domain.Directory; +using System.Globalization; +using SmartStore.Core.Html; namespace SmartStore.Services.Common { - /// - /// Address service - /// public partial class AddressService : IAddressService { - #region Fields - private readonly IRepository
_addressRepository; private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; - private readonly IEventPublisher _eventPublisher; + private readonly ICommonServices _services; private readonly AddressSettings _addressSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Address repository - /// Country service - /// State/province service - /// Event publisher - /// Address settings - public AddressService(IRepository
addressRepository, - ICountryService countryService, IStateProvinceService stateProvinceService, - IEventPublisher eventPublisher, AddressSettings addressSettings) + private readonly ITemplateEngine _templateEngine; + private readonly IMessageModelProvider _messageModelProvider; + + public AddressService( + IRepository
addressRepository, + ICountryService countryService, + IStateProvinceService stateProvinceService, + ICommonServices services, + AddressSettings addressSettings, + ITemplateEngine templateEngine, + IMessageModelProvider messageModelProvider) { - this._addressRepository = addressRepository; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._eventPublisher = eventPublisher; - this._addressSettings = addressSettings; + _addressRepository = addressRepository; + _countryService = countryService; + _stateProvinceService = stateProvinceService; + _services = services; + _addressSettings = addressSettings; + _templateEngine = templateEngine; + _messageModelProvider = messageModelProvider; } - #endregion - - #region Methods - - /// - /// Deletes an address - /// - /// Address public virtual void DeleteAddress(Address address) { - if (address == null) - throw new ArgumentNullException("address"); + Guard.NotNull(address, nameof(address)); _addressRepository.Delete(address); - - //event notification - _eventPublisher.EntityDeleted(address); } public virtual void DeleteAddress(int id) @@ -70,11 +55,6 @@ public virtual void DeleteAddress(int id) DeleteAddress(address); } - /// - /// Gets total number of addresses by country identifier - /// - /// Country identifier - /// Number of addresses public virtual int GetAddressTotalByCountryId(int countryId) { if (countryId == 0) @@ -86,11 +66,6 @@ public virtual int GetAddressTotalByCountryId(int countryId) return query.Count(); } - /// - /// Gets total number of addresses by state/province identifier - /// - /// State/province identifier - /// Number of addresses public virtual int GetAddressTotalByStateProvinceId(int stateProvinceId) { if (stateProvinceId == 0) @@ -102,11 +77,6 @@ public virtual int GetAddressTotalByStateProvinceId(int stateProvinceId) return query.Count(); } - /// - /// Gets an address by address identifier - /// - /// Address identifier - /// Address public virtual Address GetAddressById(int addressId) { if (addressId == 0) @@ -118,7 +88,7 @@ public virtual Address GetAddressById(int addressId) public virtual IList
GetAddressByIds(int[] addressIds) { - Guard.ArgumentNotNull(() => addressIds); + Guard.NotNull(addressIds, nameof(addressIds)); var query = from x in _addressRepository.TableUntracked.Expand(x => x.Country).Expand(x => x.StateProvince) @@ -128,16 +98,11 @@ where addressIds.Contains(x.Id) return query.ToList(); } - /// - /// Inserts an address - /// - /// Address public virtual void InsertAddress(Address address) { - if (address == null) - throw new ArgumentNullException("address"); + Guard.NotNull(address, nameof(address)); - address.CreatedOnUtc = DateTime.UtcNow; + address.CreatedOnUtc = DateTime.UtcNow; //some validation if (address.CountryId == 0) @@ -146,43 +111,26 @@ public virtual void InsertAddress(Address address) address.StateProvinceId = null; _addressRepository.Insert(address); - - //event notification - _eventPublisher.EntityInserted(address); } - /// - /// Updates the address - /// - /// Address public virtual void UpdateAddress(Address address) { - if (address == null) - throw new ArgumentNullException("address"); + Guard.NotNull(address, nameof(address)); - //some validation - if (address.CountryId == 0) + //some validation + if (address.CountryId == 0) address.CountryId = null; if (address.StateProvinceId == 0) address.StateProvinceId = null; _addressRepository.Update(address); - - //event notification - _eventPublisher.EntityUpdated(address); } - /// - /// Gets a value indicating whether address is valid (can be saved) - /// - /// Address to validate - /// Result public virtual bool IsAddressValid(Address address) { - if (address == null) - throw new ArgumentNullException("address"); + Guard.NotNull(address, nameof(address)); - if (String.IsNullOrWhiteSpace(address.FirstName)) + if (String.IsNullOrWhiteSpace(address.FirstName)) return false; if (String.IsNullOrWhiteSpace(address.LastName)) @@ -254,6 +202,63 @@ public virtual bool IsAddressValid(Address address) return true; } - #endregion - } + public virtual string FormatAddress(CompanyInformationSettings settings, bool newLineToBr = false) + { + Guard.NotNull(settings, nameof(settings)); + + var address = new Address + { + Address1 = settings.Street, + Address2 = settings.Street2, + City = settings.City, + Company = settings.CompanyName, + FirstName = settings.Firstname, + LastName = settings.Lastname, + Salutation = settings.Salutation, + Title = settings.Title, + ZipPostalCode = settings.ZipCode, + CountryId = settings.CountryId, + Country = _countryService.GetCountryById(settings.CountryId) + }; + + return FormatAddress(address, newLineToBr); + } + + public virtual string FormatAddress(Address address, bool newLineToBr = false) + { + Guard.NotNull(address, nameof(address)); + + var messageContext = new MessageContext + { + Language = _services.WorkContext.WorkingLanguage, + Store = _services.StoreContext.CurrentStore, + Model = new TemplateModel() + }; + + _messageModelProvider.AddModelPart(address, messageContext, "Address"); + var model = messageContext.Model["Address"]; + + var result = FormatAddress(model, address?.Country?.AddressFormat, messageContext.FormatProvider); + + if (newLineToBr) + { + result = HtmlUtils.ConvertPlainTextToHtml(result); + } + + return result; + } + + public virtual string FormatAddress(object address, string template = null, IFormatProvider formatProvider = null) + { + Guard.NotNull(address, nameof(address)); + + template = template.NullEmpty() ?? Address.DefaultAddressFormat; + + var result = _templateEngine + .Render(template, address, formatProvider ?? CultureInfo.CurrentCulture) + .Compact(true); + + return result; + } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/FulltextService.cs b/src/Libraries/SmartStore.Services/Common/FulltextService.cs deleted file mode 100644 index 847b89c032..0000000000 --- a/src/Libraries/SmartStore.Services/Common/FulltextService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Linq; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Common; -using SmartStore.Data; - -namespace SmartStore.Services.Common -{ - /// - /// Full-Text service - /// - public partial class FulltextService : IFulltextService - { - #region Fields - - private readonly IDataProvider _dataProvider; - private readonly IDbContext _dbContext; - private readonly CommonSettings _commonSettings; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Data provider - /// Database Context - /// Common settings - public FulltextService(IDataProvider dataProvider, IDbContext dbContext, - CommonSettings commonSettings) - { - this._dataProvider = dataProvider; - this._dbContext = dbContext; - this._commonSettings = commonSettings; - } - - #endregion - - #region Methods - - /// - /// Gets value indicating whether Full-Text is supported - /// - /// Result - public virtual bool IsFullTextSupported() - { - if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) - { - //stored procedures are enabled and supported by the database. - try - { - var result = _dbContext.SqlQuery("EXEC [FullText_IsSupported]"); - return result.Any() && result.FirstOrDefault() > 0; - } - catch - { - return false; - } - } - else - { - //stored procedures aren't supported - return false; - } - } - - /// - /// Enable Full-Text support - /// - public virtual void EnableFullText() - { - if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) - { - //stored procedures are enabled and supported by the database. - _dbContext.ExecuteSqlCommand("EXEC [FullText_Enable]", true); - } - else - { - throw new Exception("Stored procedures are not supported by your database"); - } - } - - /// - /// Disable Full-Text support - /// - public virtual void DisableFullText() - { - if (_commonSettings.UseStoredProceduresIfSupported && _dataProvider.StoredProceduresSupported) - { - //stored procedures are enabled and supported by the database. - _dbContext.ExecuteSqlCommand("EXEC [FullText_Disable]", true); - } - else - { - throw new Exception("Stored procedures are not supported by your database"); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs index 22532e9bf8..47f7a8edf1 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs @@ -9,86 +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.GetUnproxiedEntityType().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.ArgumentNotNull(() => genericAttributeService); - Guard.ArgumentNotEmpty(() => 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 98e0a29907..3fb471d1d0 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs @@ -3,80 +3,42 @@ using System.Linq; using SmartStore.Collections; using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; 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; namespace SmartStore.Services.Common { - /// - /// Generic attribute service - /// public partial class GenericAttributeService : IGenericAttributeService { - #region Constants - - private const string GENERICATTRIBUTE_KEY = "SmartStore.genericattribute.{0}-{1}"; - private const string GENERICATTRIBUTE_PATTERN_KEY = "SmartStore.genericattribute."; - #endregion - - #region Fields - - private readonly IRepository _genericAttributeRepository; - private readonly IRequestCache _requestCache; + private readonly IRepository _genericAttributeRepository; private readonly IEventPublisher _eventPublisher; private readonly IRepository _orderRepository; + private readonly IDictionary> _valueCache; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Generic attribute repository - /// Event published - /// Order repository - public GenericAttributeService(IRequestCache requestCache, + public GenericAttributeService( IRepository genericAttributeRepository, IEventPublisher eventPublisher, IRepository orderRepository) { - this._requestCache = requestCache; - this._genericAttributeRepository = genericAttributeRepository; - this._eventPublisher = eventPublisher; - this._orderRepository = orderRepository; - } - - #endregion - - #region Methods + _genericAttributeRepository = genericAttributeRepository; + _eventPublisher = eventPublisher; + _orderRepository = orderRepository; + _valueCache = new Dictionary>(); + } - /// - /// Deletes an attribute - /// - /// Attribute 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; _genericAttributeRepository.Delete(attribute); - //cache - _requestCache.RemoveByPattern(GENERICATTRIBUTE_PATTERN_KEY); - - //event notifications - _eventPublisher.EntityDeleted(attribute); - if (keyGroup.IsCaseInsensitiveEqual("Order") && entityId != 0) { var order = _orderRepository.GetById(entityId); @@ -84,11 +46,6 @@ public virtual void DeleteAttribute(GenericAttribute attribute) } } - /// - /// Gets an attribute - /// - /// Attribute identifier - /// An attribute public virtual GenericAttribute GetAttributeById(int attributeId) { if (attributeId == 0) @@ -98,22 +55,11 @@ public virtual GenericAttribute GetAttributeById(int attributeId) return attribute; } - /// - /// Inserts an attribute - /// - /// attribute public virtual void InsertAttribute(GenericAttribute attribute) { - if (attribute == null) - throw new ArgumentNullException("attribute"); + Guard.NotNull(attribute, nameof(attribute)); - _genericAttributeRepository.Insert(attribute); - - //cache - _requestCache.RemoveByPattern(GENERICATTRIBUTE_PATTERN_KEY); - - //event notifications - _eventPublisher.EntityInserted(attribute); + _genericAttributeRepository.Insert(attribute); if (attribute.KeyGroup.IsCaseInsensitiveEqual("Order") && attribute.EntityId != 0) { @@ -122,22 +68,11 @@ public virtual void InsertAttribute(GenericAttribute attribute) } } - /// - /// Updates the attribute - /// - /// Attribute public virtual void UpdateAttribute(GenericAttribute attribute) { - if (attribute == null) - throw new ArgumentNullException("attribute"); - - _genericAttributeRepository.Update(attribute); + Guard.NotNull(attribute, nameof(attribute)); - //cache - _requestCache.RemoveByPattern(GENERICATTRIBUTE_PATTERN_KEY); - - //event notifications - _eventPublisher.EntityUpdated(attribute); + _genericAttributeRepository.Update(attribute); if (attribute.KeyGroup.IsCaseInsensitiveEqual("Order") && attribute.EntityId != 0) { @@ -146,32 +81,21 @@ public virtual void UpdateAttribute(GenericAttribute attribute) } } - /// - /// Get attributes - /// - /// Entity identifier - /// Key group - /// Get attributes - public virtual IList GetAttributesForEntity(int entityId, string keyGroup) + public virtual IList GetAttributesForEntity(int entityId, string entityName) { - string key = string.Format(GENERICATTRIBUTE_KEY, entityId, keyGroup); - return _requestCache.Get(key, () => - { - var query = from ga in _genericAttributeRepository.Table - where ga.EntityId == entityId && - ga.KeyGroup == keyGroup - select ga; - var attributes = query.ToList(); - return attributes; - }); - } + var query = from ga in _genericAttributeRepository.Table + where ga.EntityId == entityId && ga.KeyGroup == entityName + select ga; - public virtual Multimap GetAttributesForEntity(int[] entityIds, string keyGroup) + return query.ToListCached("db.ga.{0}-{1}".FormatInvariant(entityId, entityName)); + } + + public virtual Multimap GetAttributesForEntity(int[] entityIds, string entityName) { - Guard.ArgumentNotNull(() => entityIds); + 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() @@ -180,50 +104,74 @@ public virtual Multimap GetAttributesForEntity(int[] enti return map; } - /// - /// Get queryable attributes - /// - /// The key - /// The key group - /// Queryable attributes - 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; } - /// - /// Save attribute value - /// - /// Property type - /// Entity - /// Key - /// Value - /// Store identifier; pass 0 if this attribute will be available for all stores - 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) { - if (entity == null) - throw new ArgumentNullException("entity"); + Guard.NotNull(entity, nameof(entity)); - SaveAttribute(entity.Id, key, entity.GetUnproxiedEntityType().Name, value, storeId); + 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.ArgumentNotZero(entityId, "entityId"); - - var props = GetAttributesForEntity(entityId, keyGroup) - .Where(x => x.StoreId == storeId) - .ToList(); + Guard.NotZero(entityId, nameof(entityId)); - var prop = props.FirstOrDefault(ga => - ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); // should be culture invariant + var valueStr = value.Convert(); + var props = GetAttributesForEntity(entityId, entityName); - string valueStr = value.Convert(); + // should be culture invariant + var prop = props.FirstOrDefault(ga => ga.StoreId == storeId && ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); if (prop != null) { @@ -231,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 @@ -248,15 +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); } } } - #endregion + 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/IAddressService.cs b/src/Libraries/SmartStore.Services/Common/IAddressService.cs index 37faa65bea..5ece5f704d 100644 --- a/src/Libraries/SmartStore.Services/Common/IAddressService.cs +++ b/src/Libraries/SmartStore.Services/Common/IAddressService.cs @@ -1,7 +1,7 @@ - - +using System; using System.Collections.Generic; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Common { @@ -64,5 +64,29 @@ public partial interface IAddressService /// Address to validate /// Result bool IsAddressValid(Address address); - } + + /// + /// Formats the address according to the countries address formatting template + /// + /// Address to format + /// Whether new lines should be replaced with html BR tags + /// The formatted address + string FormatAddress(CompanyInformationSettings settings, bool newLineToBr = false); + + /// + /// Formats the address according to the countries address formatting template + /// + /// Address to format + /// Whether new lines should be replaced with html BR tags + /// The formatted address + string FormatAddress(Address address, bool newLineToBr = false); + + /// + /// Formats the address according to the countries address formatting template + /// + /// Address to format. Usually passed by the template engine as a dictionary + /// The (liquid) formatting template. If null, the system global template will be used. + /// The formatted address + string FormatAddress(object address, string template = null, IFormatProvider formatProvider = null); + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/IFulltextService.cs b/src/Libraries/SmartStore.Services/Common/IFulltextService.cs deleted file mode 100644 index 4ddd1561ed..0000000000 --- a/src/Libraries/SmartStore.Services/Common/IFulltextService.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace SmartStore.Services.Common -{ - /// - /// Full-Text service interface - /// - public partial interface IFulltextService - { - /// - /// Gets value indicating whether Full-Text is supported - /// - /// Result - bool IsFullTextSupported(); - - /// - /// Enable Full-Text support - /// - void EnableFullText(); - - /// - /// Disable Full-Text support - /// - void DisableFullText(); - } -} \ 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/IMobileDeviceHelper.cs b/src/Libraries/SmartStore.Services/Common/IMobileDeviceHelper.cs index 07bbdf2ac8..16a24b174e 100644 --- a/src/Libraries/SmartStore.Services/Common/IMobileDeviceHelper.cs +++ b/src/Libraries/SmartStore.Services/Common/IMobileDeviceHelper.cs @@ -1,4 +1,3 @@ - using System.Web; namespace SmartStore.Services.Common @@ -13,15 +12,5 @@ public partial interface IMobileDeviceHelper ///
/// Result bool IsMobileDevice(); - - /// - /// Returns a value indicating whether mobile devices support is enabled - /// - bool MobileDevicesSupported(); - - /// - /// Returns a value indicating whether current customer prefer to use full desktop version (even request is made by a mobile device) - /// - bool CustomerDontUseMobileVersion(); } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/IUserAgent.cs b/src/Libraries/SmartStore.Services/Common/IUserAgent.cs index a232323b07..ff20f7b696 100644 --- a/src/Libraries/SmartStore.Services/Common/IUserAgent.cs +++ b/src/Libraries/SmartStore.Services/Common/IUserAgent.cs @@ -5,8 +5,7 @@ using System.Threading.Tasks; namespace SmartStore.Services.Common -{ - +{ public interface IUserAgent { string RawValue { get; set; } @@ -73,6 +72,7 @@ public sealed class UserAgentInfo }; private bool? _isBot; + private bool? _supportsWebP; public UserAgentInfo(string family, string major, string minor, string patch) { @@ -101,6 +101,41 @@ public bool IsBot return _isBot.Value; } } + public bool SupportsWebP + { + get + { + if (_supportsWebP == null) + { + if (Family == "Chrome") + { + _supportsWebP = Major.ToInt() >= 49; + } + else if (Family.StartsWith("Chrome Mobile")) + { + _supportsWebP = Major.ToInt() >= 61; + } + else if (Family == "Opera") + { + _supportsWebP = Major.ToInt() >= 48; + } + else if (Family == "Opera Mini") + { + _supportsWebP = true; + } + else if (Family == "Android") + { + _supportsWebP = Major.ToInt() >= 5 || (Major.ToInt() == 4 && Minor.ToInt() >= 4); + } + else + { + _supportsWebP = false; + } + } + + return _supportsWebP.Value; + } + } } internal static class VersionString 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/MobileDeviceHelper.cs b/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs index 98576528cc..0586d08a98 100644 --- a/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs +++ b/src/Libraries/SmartStore.Services/Common/MobileDeviceHelper.cs @@ -1,74 +1,21 @@ -using System.Web; using SmartStore.Core; -using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Themes; namespace SmartStore.Services.Common { - /// - /// Mobile device helper - /// public partial class MobileDeviceHelper : IMobileDeviceHelper { - #region Fields - - private readonly ThemeSettings _themeSettings; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; private readonly IUserAgent _userAgent; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Work context - /// Store context - public MobileDeviceHelper( - ThemeSettings themeSettings, - IWorkContext workContext, - IStoreContext storeContext, - IUserAgent userAgent) + public MobileDeviceHelper(IUserAgent userAgent) { - this._themeSettings = themeSettings; - this._workContext = workContext; - this._storeContext = storeContext; - this._userAgent = userAgent; + _userAgent = userAgent; } - #endregion - - #region Methods - - - /// - /// Returns a value indicating whether request is made by a mobile device - /// - /// HTTP context - /// Result public virtual bool IsMobileDevice() { - return _themeSettings.EmulateMobileDevice || (_userAgent.IsMobileDevice && !_userAgent.IsTablet); - } - - /// - /// Returns a value indicating whether mobile devices support is enabled - /// - public virtual bool MobileDevicesSupported() - { - return _themeSettings.MobileDevicesSupported; - } - - /// - /// Returns a value indicating whether current customer prefer to use full desktop version (even request is made by a mobile device) - /// - public virtual bool CustomerDontUseMobileVersion() - { - return _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.DontUseMobileVersion, _storeContext.CurrentStore.Id); + return _userAgent.IsMobileDevice && !_userAgent.IsTablet; } - #endregion } } \ No newline at end of file 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/Common/UAParserUserAgent.cs b/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs index 060c127d3f..03f767cc9d 100644 --- a/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs +++ b/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs @@ -7,7 +7,6 @@ namespace SmartStore.Services.Common { - public class UAParserUserAgent : IUserAgent { private readonly static uap.Parser s_uap; @@ -205,7 +204,8 @@ public virtual bool IsBot { if (!_isBot.HasValue) { - _isBot = _httpContext.Request.Browser.Crawler || this.Device.IsBot || this.UserAgent.IsBot; + // empty useragent > bad bot! + _isBot = this.RawValue.IsEmpty() || _httpContext.Request.Browser.Crawler || this.Device.IsBot || this.UserAgent.IsBot; } return _isBot.Value; } diff --git a/src/Libraries/SmartStore.Services/CommonServices.cs b/src/Libraries/SmartStore.Services/CommonServices.cs index b40b5f4dc6..28b7c20ff2 100644 --- a/src/Libraries/SmartStore.Services/CommonServices.cs +++ b/src/Libraries/SmartStore.Services/CommonServices.cs @@ -12,12 +12,15 @@ using SmartStore.Services.Stores; using Autofac; using SmartStore.Services.Helpers; +using SmartStore.Services.Media; +using SmartStore.Services.Messages; namespace SmartStore.Services { public class CommonServices : ICommonServices { private readonly IComponentContext _container; + private readonly Lazy _env; private readonly Lazy _cacheManager; private readonly Lazy _requestCache; private readonly Lazy _dbContext; @@ -27,16 +30,20 @@ public class CommonServices : ICommonServices private readonly Lazy _eventPublisher; private readonly Lazy _localization; private readonly Lazy _customerActivity; + private readonly Lazy _pictureService; private readonly Lazy _notifier; private readonly Lazy _permissions; private readonly Lazy _settings; private readonly Lazy _storeService; private readonly Lazy _dateTimeHelper; private readonly Lazy _displayControl; + private readonly Lazy _chronometer; + private readonly Lazy _messageFactory; public CommonServices( IComponentContext container, - Lazy cacheManager, + Lazy env, + Lazy cacheManager, Lazy requestCache, Lazy dbContext, Lazy storeContext, @@ -45,14 +52,18 @@ public CommonServices( Lazy eventPublisher, Lazy localization, Lazy customerActivity, + Lazy pictureService, Lazy notifier, Lazy permissions, Lazy settings, Lazy storeService, Lazy dateTimeHelper, - Lazy displayControl) + Lazy displayControl, + Lazy chronometer, + Lazy messageFactory) { this._container = container; + this._env = env; this._cacheManager = cacheManager; this._requestCache = requestCache; this._dbContext = dbContext; @@ -62,141 +73,36 @@ public CommonServices( this._eventPublisher = eventPublisher; this._localization = localization; this._customerActivity = customerActivity; + this._pictureService = pictureService; this._notifier = notifier; this._permissions = permissions; this._settings = settings; this._storeService = storeService; this._dateTimeHelper = dateTimeHelper; this._displayControl = displayControl; - } - - public IComponentContext Container - { - get - { - return _container; - } - } - - public ICacheManager Cache - { - get - { - return _cacheManager.Value; - } - } - - public IRequestCache RequestCache - { - get - { - return _requestCache.Value; - } - } - - public IDbContext DbContext - { - get - { - return _dbContext.Value; - } - } - - public IStoreContext StoreContext - { - get - { - return _storeContext.Value; - } - } - - public IWebHelper WebHelper - { - get - { - return _webHelper.Value; - } - } - - public IWorkContext WorkContext - { - get - { - return _workContext.Value; - } - } - - public IEventPublisher EventPublisher - { - get - { - return _eventPublisher.Value; - } - } - - public ILocalizationService Localization - { - get - { - return _localization.Value; - } - } - - public ICustomerActivityService CustomerActivity - { - get - { - return _customerActivity.Value; - } - } - - public INotifier Notifier - { - get - { - return _notifier.Value; - } - } - - public IPermissionService Permissions - { - get - { - return _permissions.Value; - } - } - - public ISettingService Settings - { - get - { - return _settings.Value; - } - } - - - public IStoreService StoreService - { - get - { - return _storeService.Value; - } - } - - public IDateTimeHelper DateTimeHelper - { - get - { - return _dateTimeHelper.Value; - } - } - - public IDisplayControl DisplayControl - { - get - { - return _displayControl.Value; - } - } + this._chronometer = chronometer; + this._messageFactory = messageFactory; + } + + public IComponentContext Container => _container; + public IApplicationEnvironment ApplicationEnvironment => _env.Value; + public ICacheManager Cache => _cacheManager.Value; + public IRequestCache RequestCache => _requestCache.Value; + public IDbContext DbContext => _dbContext.Value; + public IStoreContext StoreContext => _storeContext.Value; + public IWebHelper WebHelper => _webHelper.Value; + public IWorkContext WorkContext => _workContext.Value; + public IEventPublisher EventPublisher => _eventPublisher.Value; + public ILocalizationService Localization => _localization.Value; + public ICustomerActivityService CustomerActivity => _customerActivity.Value; + public IPictureService PictureService => _pictureService.Value; + public INotifier Notifier => _notifier.Value; + public IPermissionService Permissions => _permissions.Value; + public ISettingService Settings => _settings.Value; + public IStoreService StoreService => _storeService.Value; + public IDateTimeHelper DateTimeHelper => _dateTimeHelper.Value; + public IDisplayControl DisplayControl => _displayControl.Value; + public IChronometer Chronometer => _chronometer.Value; + public IMessageFactory MessageFactory => _messageFactory.Value; } } diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index 8ca60a2e33..1caeae24d0 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -1,48 +1,46 @@ 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.Utilities; +using SmartStore.Core.Logging; namespace SmartStore.Services.Configuration { - public partial class SettingService : ISettingService + public partial class SettingService : ScopedServiceBase, ISettingService { - private const string SETTINGS_ALL_KEY = "SmartStore.setting.all"; + private const string SETTINGS_ALL_KEY = "setting:all"; private readonly IRepository _settingRepository; - private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; - private bool _isInBatch; - private bool _hasChanges; - - public SettingService(ICacheManager cacheManager, IEventPublisher eventPublisher, IRepository settingRepository) + public SettingService(ICacheManager cacheManager, IRepository settingRepository) { - this._cacheManager = cacheManager; - this._eventPublisher = eventPublisher; - this._settingRepository = settingRepository; + _cacheManager = cacheManager; + _settingRepository = settingRepository; + + Logger = NullLogger.Instance; } - protected virtual IDictionary GetAllCachedSettings() + public ILogger Logger { get; set; } + + protected virtual IDictionary GetAllCachedSettings() { - string key = string.Format(SETTINGS_ALL_KEY); - return _cacheManager.Get(key, () => + return _cacheManager.Get(SETTINGS_ALL_KEY, () => { var query = from s in _settingRepository.TableUntracked orderby s.Name, s.StoreId select s; + var settings = query.ToList(); - var dictionary = new Dictionary(); + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var s in settings) { var settingKey = CreateCacheKey(s.Name, s.StoreId); @@ -57,69 +55,57 @@ protected virtual IDictionary GetAllCachedSettings() dictionary[settingKey] = cachedSetting; } + return dictionary; - }); + }, independent: true); } - public IDisposable BeginBatch(bool clearCache = true) + protected virtual PropertyInfo GetPropertyInfo(Expression> keySelector) { - if (_isInBatch) + var member = keySelector.Body as MemberExpression; + if (member == null) { - // nested batches are not supported - return ActionDisposable.Empty; - } - - _isInBatch = true; + throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); + } - return new ActionDisposable(() => + var propInfo = member.Member as PropertyInfo; + if (propInfo == null) { - _isInBatch = false; - if (clearCache && _hasChanges) - { - ClearCache(); - } - }); - } + throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); + } - public bool HasChanges - { - get { return _hasChanges; } + return propInfo; } public virtual void InsertSetting(Setting setting, bool clearCache = true) { - Guard.ArgumentNotNull(() => setting); + Guard.NotNull(setting, nameof(setting)); _settingRepository.Insert(setting); - _hasChanges = true; + HasChanges = true; if (clearCache) ClearCache(); - - _eventPublisher.EntityInserted(setting); } public virtual void UpdateSetting(Setting setting, bool clearCache = true) { - Guard.ArgumentNotNull(() => setting); + Guard.NotNull(setting, nameof(setting)); _settingRepository.Update(setting); - _hasChanges = true; + HasChanges = true; if (clearCache) ClearCache(); - - _eventPublisher.EntityUpdated(setting); } - private T LoadSettingsJson(int storeId = 0) + private ISettings LoadSettingsJson(Type settingType, int storeId = 0) { - Type t = typeof(T); - string key = t.Namespace + "." + t.Name; + string key = settingType.Namespace + "." + settingType.Name; - T settings = Activator.CreateInstance(); + var settings = (ISettings)Activator.CreateInstance(settingType); var rawSetting = GetSettingByKey(key, storeId: storeId, loadSharedValueIfNotFound: true); if (rawSetting.HasValue()) @@ -130,9 +116,9 @@ private T LoadSettingsJson(int storeId = 0) return settings; } - private void SaveSettingsJson(T settings) + private void SaveSettingsJson(ISettings settings) { - Type t = typeof(T); + Type t = settings.GetType(); string key = t.Namespace + "." + t.Name; var storeId = 0; @@ -166,19 +152,17 @@ 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) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); var settings = GetAllCachedSettings(); var cacheKey = CreateCacheKey(key, storeId); - CachedSetting cachedSetting; - - if (settings.TryGetValue(cacheKey, out cachedSetting)) + if (settings.TryGetValue(cacheKey, out CachedSetting cachedSetting)) { return cachedSetting.Value.Convert(); } @@ -211,154 +195,183 @@ 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; } - public virtual T LoadSetting(int storeId = 0) where T : ISettings, new() + public T LoadSetting(int storeId = 0) where T : ISettings, new() { - if (typeof(T).HasAttribute(true)) + return (T)LoadSettingCore(typeof(T), storeId); + } + + public ISettings LoadSetting(Type settingType, int storeId = 0) + { + Guard.NotNull(settingType, nameof(settingType)); + Guard.HasDefaultConstructor(settingType); + + if (!typeof(ISettings).IsAssignableFrom(settingType)) { - return LoadSettingsJson(storeId); + throw new ArgumentException($"The type to load settings for must be a subclass of the '{typeof(ISettings).FullName}' interface", nameof(settingType)); } - var settings = Activator.CreateInstance(); + return LoadSettingCore(settingType, storeId); + } - var prefix = typeof(T).Name; + protected virtual ISettings LoadSettingCore(Type settingType, int storeId = 0) + { + var allSettings = GetAllCachedSettings(); + var instance = (ISettings)Activator.CreateInstance(settingType); + var prefix = settingType.Name; - foreach (var fastProp in FastProperty.GetProperties(typeof(T)).Values) + 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) - { - // 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. - continue; - } - } - else - { - #region Obsolete ('EnumerableConverter' can handle this case now) - //if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) - //{ - // // convenience: don't return null for simple list types - // var listArg = prop.PropertyType.GetGenericArguments()[0]; - // object list = null; - - // if (listArg == typeof(int)) - // list = new List(); - // else if (listArg == typeof(decimal)) - // list = new List(); - // else if (listArg == typeof(string)) - // list = new List(); - - // if (list != null) - // { - // fastProp.SetValue(settings, list); - // } - //} - #endregion - - continue; - } + { + 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. + continue; + } + } + else + { + #region Obsolete ('EnumerableConverter' can handle this case now) + //if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) + //{ + // // convenience: don't return null for simple list types + // var listArg = prop.PropertyType.GetGenericArguments()[0]; + // object list = null; + + // if (listArg == typeof(int)) + // list = new List(); + // else if (listArg == typeof(decimal)) + // list = new List(); + // else if (listArg == typeof(string)) + // list = new List(); + + // if (list != null) + // { + // fastProp.SetValue(settings, list); + // } + //} + #endregion + + continue; + } } var converter = TypeConverterFactory.GetConverter(prop.PropertyType); - if (converter == null || !converter.CanConvertFrom(typeof(string))) + if (converter == null || !converter.CanConvertFrom(typeof(string))) continue; - object value = converter.ConvertFrom(setting); + try + { + object value = converter.ConvertFrom(setting); - //set property - fastProp.SetValue(settings, value); + // Set property + fastProp.SetValue(instance, value); + } + catch (Exception ex) + { + var msg = "Could not convert setting '{0}' to type '{1}'".FormatInvariant(key, prop.PropertyType.Name); + Logger.Error(ex, msg); + } } - return settings; + return instance; } public virtual void SetSetting(string key, T value, int storeId = 0, bool clearCache = true) { - Guard.ArgumentNotEmpty(() => key); + Guard.NotEmpty(key, nameof(key)); 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)) { - // Update var setting = GetSettingById(cachedSetting.Id); - if (setting != null && setting.Value != str) + if (setting != null) + { + // Update + if (setting.Value != str) + { + setting.Value = str; + UpdateSetting(setting, clearCache); + } + } + else { - setting.Value = str; - UpdateSetting(setting, clearCache); - _hasChanges = true; + insert = true; } } else + { + insert = true; + } + + if (insert) { // Insert var setting = new Setting { - Name = cacheKey.Name, + Name = key.ToLowerInvariant(), Value = str, StoreId = storeId }; InsertSetting(setting, clearCache); - _hasChanges = true; } } - public virtual void SaveSetting(T settings, int storeId = 0) where T : ISettings, new() + public void SaveSetting(T settings, int storeId = 0) where T : ISettings, new() { - using (BeginBatch()) + SaveSettingCore(settings, storeId); + } + + public void SaveSetting(ISettings settings, int storeId = 0) + { + SaveSettingCore(settings, storeId); + } + + protected virtual void SaveSettingCore(ISettings settings, int storeId = 0) + { + Guard.NotNull(settings, nameof(settings)); + + using (BeginScope()) { - if (typeof(T).HasAttribute(true)) - { - SaveSettingsJson(settings); - return; - } + var settingType = settings.GetType(); + var prefix = settingType.Name; /* 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 */ - foreach (var prop in FastProperty.GetProperties(typeof(T)).Values) + foreach (var prop in FastProperty.GetProperties(settingType).Values) { // get properties we can read and write to if (!prop.IsPublicSettable) @@ -368,14 +381,14 @@ public virtual void SetSetting(string key, T value, int storeId = 0, bool cle if (converter == null || !converter.CanConvertFrom(typeof(string))) continue; - string key = typeof(T).Name + "." + prop.Name; + string key = prefix + "." + prop.Name; // Duck typing is not supported in C#. That's why we're using dynamic type dynamic value = prop.GetValue(settings); SetSetting(key, value ?? "", storeId, false); } } - } + } public virtual void SaveSetting( T settings, @@ -383,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); @@ -414,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) @@ -426,23 +429,15 @@ public virtual void DeleteSetting(Setting setting) _settingRepository.Delete(setting); - _hasChanges = true; + HasChanges = true; ClearCache(); - - _eventPublisher.EntityDeleted(setting); } public virtual void DeleteSetting() where T : ISettings, new() { - using (BeginBatch()) + using (BeginScope()) { - if (typeof(T).HasAttribute(true)) - { - DeleteSettingsJson(); - return; - } - var settingsToDelete = new List(); var allSettings = GetAllSettings(); foreach (var prop in typeof(T).GetProperties()) @@ -463,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); } @@ -512,7 +492,7 @@ public virtual int DeleteSettings(string rootKey) { result = _settingRepository.Context.ExecuteSqlCommand(sqlDelete); if (result > 0) - _hasChanges = true; + HasChanges = true; ClearCache(); } @@ -524,31 +504,17 @@ public virtual int DeleteSettings(string rootKey) { return result; } - public virtual void ClearCache() - { - if (!_isInBatch) - { - _cacheManager.RemoveByPattern(SETTINGS_ALL_KEY); - _hasChanges = false; - } + protected override void OnClearCache() + { + _cacheManager.Remove(SETTINGS_ALL_KEY); } - protected SettingKey CreateCacheKey(string name, int storeId) + protected string CreateCacheKey(string name, int storeId) { - return new SettingKey { Name = name.Trim().ToLowerInvariant(), StoreId = storeId }; + return name.Trim().ToLowerInvariant() + "/" + storeId.ToString(); } } - [Serializable] - public class SettingKey : ComparableObject - { - [ObjectSignature] - public string Name { get; set; } - - [ObjectSignature] - public int StoreId { get; set; } - } - [Serializable] public class CachedSetting { diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs index eea028fd65..8f83fb33a8 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs @@ -1,68 +1,30 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Events; namespace SmartStore.Services.Customers { - /// - /// Customer content service - /// public partial class CustomerContentService : ICustomerContentService { - #region Fields - private readonly IRepository _contentRepository; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - - #endregion - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Customer content repository - /// Event published - public CustomerContentService(IRequestCache requestCache, - IRepository contentRepository, - IEventPublisher eventPublisher) + public CustomerContentService(IRepository contentRepository, IEventPublisher eventPublisher) { - _requestCache = requestCache; _contentRepository = contentRepository; _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - /// - /// Deletes a customer content - /// - /// Customer content public virtual void DeleteCustomerContent(CustomerContent content) { - if (content == null) - throw new ArgumentNullException("content"); + Guard.NotNull(content, nameof(content)); _contentRepository.Delete(content); - - //event notification - _eventPublisher.EntityDeleted(content); } - /// - /// Gets all customer content - /// - /// Customer identifier; 0 to load all records - /// A value indicating whether to content is approved; null to load all records - /// Customer content public virtual IList GetAllCustomerContent(int customerId, bool? approved) { var query = from c in _contentRepository.Table @@ -70,23 +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; } - /// - /// Gets all customer content - /// - /// Type - /// Customer identifier; 0 to load all records - /// A value indicating whether to content is approved; null to load all records - /// Item creation from; null to load all records - /// Item item creation to; null to load all records - /// Customer 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) @@ -95,16 +49,13 @@ 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; } - /// - /// Gets a customer content - /// - /// Customer content identifier - /// Customer content public virtual CustomerContent GetCustomerContentById(int contentId) { if (contentId == 0) @@ -114,36 +65,18 @@ public virtual CustomerContent GetCustomerContentById(int contentId) } - /// - /// Inserts a customer content - /// - /// Customer content public virtual void InsertCustomerContent(CustomerContent content) { - if (content == null) - throw new ArgumentNullException("content"); + Guard.NotNull(content, nameof(content)); - _contentRepository.Insert(content); - - //event notification - _eventPublisher.EntityInserted(content); + _contentRepository.Insert(content); } - /// - /// Updates a customer content - /// - /// Customer content public virtual void UpdateCustomerContent(CustomerContent content) { - if (content == null) - throw new ArgumentNullException("content"); - - _contentRepository.Update(content); + Guard.NotNull(content, nameof(content)); - //event notification - _eventPublisher.EntityUpdated(content); + _contentRepository.Update(content); } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs new file mode 100644 index 0000000000..e4515e9241 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs @@ -0,0 +1,495 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +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 + /// + /// Customer + /// Customer role system name + /// A value indicating whether we should look only in active customer roles + /// Result + public static bool IsInCustomerRole(this Customer customer, string customerRoleSystemName, bool onlyActiveCustomerRoles = true) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotEmpty(customerRoleSystemName, nameof(customerRoleSystemName)); + + var result = customer.CustomerRoles + .Where(cr => !onlyActiveCustomerRoles || cr.Active) + .Where(cr => cr.SystemName == customerRoleSystemName) + .FirstOrDefault() != null; + + return result; + } + + /// + /// Gets a value indicating whether the customer is a built-in record for background tasks + /// + /// Customer + /// Result + public static bool IsBackgroundTaskAccount(this Customer customer) + { + Guard.NotNull(customer, nameof(customer)); + + if (!customer.IsSystemAccount || customer.SystemName.IsEmpty()) + return false; + + var result = customer.SystemName.Equals(SystemCustomerNames.BackgroundTask, StringComparison.InvariantCultureIgnoreCase); + return result; + } + + /// + /// Gets a value indicating whether customer is a search engine + /// + /// Customer + /// Result + public static bool IsSearchEngineAccount(this Customer customer) + { + Guard.NotNull(customer, nameof(customer)); + + if (!customer.IsSystemAccount || customer.SystemName.IsEmpty()) + return false; + + var result = customer.SystemName.Equals(SystemCustomerNames.SearchEngine, StringComparison.InvariantCultureIgnoreCase); + return result; + } + + /// + /// Gets a value indicating whether customer is the pdf converter + /// + /// Customer + /// Result + public static bool IsPdfConverter(this Customer customer) + { + Guard.NotNull(customer, nameof(customer)); + + if (!customer.IsSystemAccount || customer.SystemName.IsEmpty()) + return false; + + var result = customer.SystemName.Equals(SystemCustomerNames.PdfConverter, StringComparison.InvariantCultureIgnoreCase); + return result; + } + + /// + /// Gets a value indicating whether customer is administrator + /// + /// Customer + /// A value indicating whether we should look only in active customer roles + /// Result + public static bool IsAdmin(this Customer customer, bool onlyActiveCustomerRoles = true) + { + return IsInCustomerRole(customer, SystemCustomerRoleNames.Administrators, onlyActiveCustomerRoles); + } + + /// + /// Gets a value indicating whether customer is super administrator + /// + /// codehint: sm-add + /// Customer + /// A value indicating whether we should look only in active customer roles + /// Result + public static bool IsSuperAdmin(this Customer customer, bool onlyActiveCustomerRoles = true) { + return IsInCustomerRole(customer, SystemCustomerRoleNames.SuperAdministrators, onlyActiveCustomerRoles); + } + + /// + /// Gets a value indicating whether customer is a forum moderator + /// + /// Customer + /// A value indicating whether we should look only in active customer roles + /// Result + public static bool IsForumModerator(this Customer customer, bool onlyActiveCustomerRoles = true) + { + return IsInCustomerRole(customer, SystemCustomerRoleNames.ForumModerators, onlyActiveCustomerRoles); + } + + /// + /// Gets a value indicating whether customer is registered + /// + /// Customer + /// A value indicating whether we should look only in active customer roles + /// Result + public static bool IsRegistered(this Customer customer, bool onlyActiveCustomerRoles = true) + { + return IsInCustomerRole(customer, SystemCustomerRoleNames.Registered, onlyActiveCustomerRoles); + } + + /// + /// Gets a value indicating whether customer is guest + /// + /// Customer + /// A value indicating whether we should look only in active customer roles + /// Result + public static bool IsGuest(this Customer customer, bool onlyActiveCustomerRoles = true) + { + return IsInCustomerRole(customer, SystemCustomerRoleNames.Guests, onlyActiveCustomerRoles); + } + + public static string GetFullName(this Customer customer) + { + if (customer == null) + return string.Empty; + + if (customer.FullName.HasValue()) + { + return customer.FullName; + } + + string name = customer.BillingAddress?.GetFullName(); + if (name.IsEmpty()) + { + name = customer.ShippingAddress?.GetFullName(); + } + if (name.IsEmpty()) + { + name = customer.Addresses.FirstOrDefault()?.GetFullName(); + } + + return name.TrimSafe(); + } + + /// + /// Formats the customer name. + /// + /// Customer entity. + /// Formatted customer name. + public static string FormatUserName(this Customer customer) + { + return FormatUserName(customer, false); + } + + /// + /// Formats the customer name. + /// + /// 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 T("Customer.Guest"); + } + + var result = string.Empty; + + switch (customerSettings.CustomerNameFormat) + { + case CustomerNameFormat.ShowEmails: + result = customer.Email; + break; + case CustomerNameFormat.ShowFullNames: + result = customer.GetFullName(); + break; + case CustomerNameFormat.ShowUsernames: + result = customer.Username; + 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; + } + + var maxLength = customerSettings.CustomerNameFormatMaxLength; + if (stripTooLong && maxLength > 0 && result != null && result.Length > maxLength) + { + result = result.Truncate(maxLength, "..."); + } + + return result; + } + + /// + /// Find any email address of customer + /// + public static string FindEmail(this Customer customer) + { + if (customer != null) + { + return customer.Email.NullEmpty() + ?? customer.BillingAddress?.Email?.NullEmpty() + ?? customer.ShippingAddress?.Email?.NullEmpty(); + } + + 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 + + public static int CountProductsInCart(this Customer customer, ShoppingCartType cartType, int? storeId = null) + { + if (customer != null) + { + var cartService = EngineContext.Current.Resolve(); + var count = cartService.CountItems(customer, cartType, storeId); + + return count; + } + + return 0; + } + + public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null) + { + if (customer != null) + { + var cartService = EngineContext.Current.Resolve(); + var items = cartService.GetCartItems(customer, cartType, storeId); + + return items; + } + + return new List(); + } + + #endregion + + #region Gift cards + + /// + /// Gets coupon codes + /// + /// Customer + /// Coupon codes + public static string[] ParseAppliedGiftCardCouponCodes(this Customer customer) + { + var genericAttributeService = EngineContext.Current.Resolve(); + string existingGiftCartCouponCodes = customer.GetAttribute(SystemCustomerAttributeNames.GiftCardCouponCodes, + genericAttributeService); + + var couponCodes = new List(); + if (String.IsNullOrEmpty(existingGiftCartCouponCodes)) + return couponCodes.ToArray(); + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(existingGiftCartCouponCodes); + + var nodeList1 = xmlDoc.SelectNodes(@"//GiftCardCouponCodes/CouponCode"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["Code"] != null) + { + string code = node1.Attributes["Code"].InnerText.Trim(); + couponCodes.Add(code); + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return couponCodes.ToArray(); + } + + /// + /// Adds a coupon code + /// + /// Customer + /// Coupon code + /// New coupon codes document + public static void ApplyGiftCardCouponCode(this Customer customer, string couponCode) + { + var genericAttributeService = EngineContext.Current.Resolve(); + string result = string.Empty; + try + { + string existingGiftCartCouponCodes = customer.GetAttribute(SystemCustomerAttributeNames.GiftCardCouponCodes, + genericAttributeService); + + couponCode = couponCode.Trim().ToLower(); + + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(existingGiftCartCouponCodes)) + { + var element1 = xmlDoc.CreateElement("GiftCardCouponCodes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(existingGiftCartCouponCodes); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//GiftCardCouponCodes"); + + XmlElement gcElement = null; + //find existing + var nodeList1 = xmlDoc.SelectNodes(@"//GiftCardCouponCodes/CouponCode"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["Code"] != null) + { + string couponCodeAttribute = node1.Attributes["Code"].InnerText.Trim(); + if (couponCodeAttribute.ToLower() == couponCode.ToLower()) + { + gcElement = (XmlElement)node1; + break; + } + } + } + + //create new one if not found + if (gcElement == null) + { + gcElement = xmlDoc.CreateElement("CouponCode"); + gcElement.SetAttribute("Code", couponCode); + rootElement.AppendChild(gcElement); + } + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + + //apply new value + genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, result); + } + + /// + /// Removes a coupon code + /// + /// Customer + /// Coupon code to remove + /// New coupon codes document + public static void RemoveGiftCardCouponCode(this Customer customer, string couponCode) + { + //get applied coupon codes + var existingCouponCodes = customer.ParseAppliedGiftCardCouponCodes(); + + //clear them + var genericAttributeService = EngineContext.Current.Resolve(); + genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, null); + + //save again except removed one + foreach (string existingCouponCode in existingCouponCodes) + if (!existingCouponCode.Equals(couponCode, StringComparison.InvariantCultureIgnoreCase)) + customer.ApplyGiftCardCouponCode(existingCouponCode); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs deleted file mode 100644 index a3525e9cac..0000000000 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs +++ /dev/null @@ -1,431 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Xml; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Common; -using SmartStore.Services.Localization; -using SmartStore.Services.Orders; - -namespace SmartStore.Services.Customers -{ - public static class CustomerExtentions - { - /// - /// Gets a value indicating whether customer is in a certain customer role - /// - /// Customer - /// Customer role system name - /// A value indicating whether we should look only in active customer roles - /// Result - public static bool IsInCustomerRole(this Customer customer, string customerRoleSystemName, bool onlyActiveCustomerRoles = true) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (String.IsNullOrEmpty(customerRoleSystemName)) - throw new ArgumentNullException("customerRoleSystemName"); - - var result = customer.CustomerRoles - .Where(cr => !onlyActiveCustomerRoles || cr.Active) - .Where(cr => cr.SystemName == customerRoleSystemName) - .FirstOrDefault() != null; - return result; - } - - /// - /// Gets a value indicating whether the customer is a built-in record for background tasks - /// - /// Customer - /// Result - public static bool IsBackgroundTaskAccount(this Customer customer) - { - Guard.ArgumentNotNull(() => customer); - - if (!customer.IsSystemAccount || customer.SystemName.IsEmpty()) - return false; - - var result = customer.SystemName.Equals(SystemCustomerNames.BackgroundTask, StringComparison.InvariantCultureIgnoreCase); - return result; - } - - /// - /// Gets a value indicating whether customer a search engine - /// - /// Customer - /// Result - public static bool IsSearchEngineAccount(this Customer customer) - { - Guard.ArgumentNotNull(() => customer); - - if (!customer.IsSystemAccount || customer.SystemName.IsEmpty()) - return false; - - var result = customer.SystemName.Equals(SystemCustomerNames.SearchEngine, StringComparison.InvariantCultureIgnoreCase); - return result; - } - - /// - /// Gets a value indicating whether customer is the pdf converter - /// - /// Customer - /// Result - public static bool IsPdfConverter(this Customer customer) - { - Guard.ArgumentNotNull(() => customer); - - if (!customer.IsSystemAccount || customer.SystemName.IsEmpty()) - return false; - - var result = customer.SystemName.Equals(SystemCustomerNames.PdfConverter, StringComparison.InvariantCultureIgnoreCase); - return result; - } - - /// - /// Gets a value indicating whether customer is administrator - /// - /// Customer - /// A value indicating whether we should look only in active customer roles - /// Result - public static bool IsAdmin(this Customer customer, bool onlyActiveCustomerRoles = true) - { - return IsInCustomerRole(customer, SystemCustomerRoleNames.Administrators, onlyActiveCustomerRoles); - } - - /// - /// Gets a value indicating whether customer is super administrator - /// - /// codehint: sm-add - /// Customer - /// A value indicating whether we should look only in active customer roles - /// Result - public static bool IsSuperAdmin(this Customer customer, bool onlyActiveCustomerRoles = true) { - return IsInCustomerRole(customer, SystemCustomerRoleNames.SuperAdministrators, onlyActiveCustomerRoles); - } - - /// - /// Gets a value indicating whether customer is a forum moderator - /// - /// Customer - /// A value indicating whether we should look only in active customer roles - /// Result - public static bool IsForumModerator(this Customer customer, bool onlyActiveCustomerRoles = true) - { - return IsInCustomerRole(customer, SystemCustomerRoleNames.ForumModerators, onlyActiveCustomerRoles); - } - - /// - /// Gets a value indicating whether customer is registered - /// - /// Customer - /// A value indicating whether we should look only in active customer roles - /// Result - public static bool IsRegistered(this Customer customer, bool onlyActiveCustomerRoles = true) - { - return IsInCustomerRole(customer, SystemCustomerRoleNames.Registered, onlyActiveCustomerRoles); - } - - /// - /// Gets a value indicating whether customer is guest - /// - /// Customer - /// A value indicating whether we should look only in active customer roles - /// Result - public static bool IsGuest(this Customer customer, bool onlyActiveCustomerRoles = true) - { - return IsInCustomerRole(customer, SystemCustomerRoleNames.Guests, onlyActiveCustomerRoles); - } - - public static string GetFullName(this Customer customer) - { - if (customer == null) - throw new ArgumentNullException("customer"); - var firstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); - var lastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName); - - string fullName = ""; - if (!String.IsNullOrWhiteSpace(firstName) && !String.IsNullOrWhiteSpace(lastName)) - { - fullName = string.Format("{0} {1}", firstName, lastName); - } - else - { - if (!String.IsNullOrWhiteSpace(firstName)) - fullName = firstName; - - if (!String.IsNullOrWhiteSpace(lastName)) - fullName = lastName; - - if (String.IsNullOrWhiteSpace(firstName) && String.IsNullOrWhiteSpace(lastName)) - { - var address = customer.Addresses.FirstOrDefault(); - if (address != null) - fullName = string.Format("{0} {1}", address.FirstName, address.LastName); - } - } - return fullName; - } - - /// - /// Formats the customer name - /// - /// Source - /// Formatted text - public static string FormatUserName(this Customer customer) - { - return FormatUserName(customer, false); - } - - /// - /// Formats the customer name - /// - /// Source - /// Strip too long customer name - /// Formatted text - public static string FormatUserName(this Customer customer, bool stripTooLong) - { - if (customer == null) - return string.Empty; - - if (customer.IsGuest()) - { - return EngineContext.Current.Resolve().GetResource("Customer.Guest"); - } - - string result = string.Empty; - switch (EngineContext.Current.Resolve().CustomerNameFormat) - { - case CustomerNameFormat.ShowEmails: - result = customer.Email; - break; - case CustomerNameFormat.ShowFullNames: - result = customer.GetFullName(); - break; - 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; - default: - break; - } - - if (stripTooLong && result.HasValue()) - { - int maxLength = EngineContext.Current.Resolve().CustomerNameFormatMaxLength; - if (maxLength > 0 && result.Length > maxLength) - { - result = result.Truncate(maxLength, "..."); - } - } - - return result; - } - - /// - /// Find any email address of customer - /// - public static string FindEmail(this Customer customer) - { - if (customer != null) - { - if (customer.Email.HasValue()) - return customer.Email; - - if (customer.BillingAddress != null && customer.BillingAddress.Email.HasValue()) - return customer.BillingAddress.Email; - - if (customer.ShippingAddress != null && customer.ShippingAddress.Email.HasValue()) - return customer.ShippingAddress.Email; - } - return null; - } - - - #region Shopping cart - - public static int CountProductsInCart(this Customer customer, ShoppingCartType cartType, int? storeId = null) - { - int count = customer.ShoppingCartItems - .Filter(cartType, storeId) - .Where(x => x.ParentItemId == null) - .Sum(x => x.Quantity); - - return count; - } - - public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null) - { - var rawItems = customer.ShoppingCartItems.Filter(cartType, storeId); - - var organizedItems = rawItems - .OrderByDescending(x => x.Id) - .ToList() - .Organize(); - - return organizedItems.ToList(); - } - - #endregion - - #region Gift cards - - /// - /// Gets coupon codes - /// - /// Customer - /// Coupon codes - public static string[] ParseAppliedGiftCardCouponCodes(this Customer customer) - { - var genericAttributeService = EngineContext.Current.Resolve(); - string existingGiftCartCouponCodes = customer.GetAttribute(SystemCustomerAttributeNames.GiftCardCouponCodes, - genericAttributeService); - - var couponCodes = new List(); - if (String.IsNullOrEmpty(existingGiftCartCouponCodes)) - return couponCodes.ToArray(); - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(existingGiftCartCouponCodes); - - var nodeList1 = xmlDoc.SelectNodes(@"//GiftCardCouponCodes/CouponCode"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["Code"] != null) - { - string code = node1.Attributes["Code"].InnerText.Trim(); - couponCodes.Add(code); - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return couponCodes.ToArray(); - } - - /// - /// Adds a coupon code - /// - /// Customer - /// Coupon code - /// New coupon codes document - public static void ApplyGiftCardCouponCode(this Customer customer, string couponCode) - { - var genericAttributeService = EngineContext.Current.Resolve(); - string result = string.Empty; - try - { - string existingGiftCartCouponCodes = customer.GetAttribute(SystemCustomerAttributeNames.GiftCardCouponCodes, - genericAttributeService); - - couponCode = couponCode.Trim().ToLower(); - - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(existingGiftCartCouponCodes)) - { - var element1 = xmlDoc.CreateElement("GiftCardCouponCodes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(existingGiftCartCouponCodes); - } - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//GiftCardCouponCodes"); - - XmlElement gcElement = null; - //find existing - var nodeList1 = xmlDoc.SelectNodes(@"//GiftCardCouponCodes/CouponCode"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["Code"] != null) - { - string couponCodeAttribute = node1.Attributes["Code"].InnerText.Trim(); - if (couponCodeAttribute.ToLower() == couponCode.ToLower()) - { - gcElement = (XmlElement)node1; - break; - } - } - } - - //create new one if not found - if (gcElement == null) - { - gcElement = xmlDoc.CreateElement("CouponCode"); - gcElement.SetAttribute("Code", couponCode); - rootElement.AppendChild(gcElement); - } - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - - //apply new value - genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, result); - } - - /// - /// Removes a coupon code - /// - /// Customer - /// Coupon code to remove - /// New coupon codes document - public static void RemoveGiftCardCouponCode(this Customer customer, string couponCode) - { - //get applied coupon codes - var existingCouponCodes = customer.ParseAppliedGiftCardCouponCodes(); - - //clear them - var genericAttributeService = EngineContext.Current.Resolve(); - genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, null); - - //save again except removed one - foreach (string existingCouponCode in existingCouponCodes) - if (!existingCouponCode.Equals(couponCode, StringComparison.InvariantCultureIgnoreCase)) - customer.ApplyGiftCardCouponCode(existingCouponCode); - } - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerMessageFactoryExtensions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerMessageFactoryExtensions.cs new file mode 100644 index 0000000000..398e45d2a8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/CustomerMessageFactoryExtensions.cs @@ -0,0 +1,80 @@ +using System; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Messages; +using SmartStore.Services.Messages; + +namespace SmartStore.Services.Customers +{ + public static class CustomerMessageFactoryExtensions + { + /// + /// Sends 'New customer' notification message to a store owner + /// + public static CreateMessageResult SendCustomerRegisteredNotificationMessage(this IMessageFactory factory, Customer customer, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.CustomerRegistered, languageId, customer: customer), true); + } + + /// + /// Sends a welcome message to a customer + /// + public static CreateMessageResult SendCustomerWelcomeMessage(this IMessageFactory factory, Customer customer, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.CustomerWelcome, languageId, customer: customer), true); + } + + /// + /// Sends an email validation message to a customer + /// + public static CreateMessageResult SendCustomerEmailValidationMessage(this IMessageFactory factory, Customer customer, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.CustomerEmailValidation, languageId, customer: customer), true); + } + + /// + /// Sends password recovery message to a customer + /// + public static CreateMessageResult SendCustomerPasswordRecoveryMessage(this IMessageFactory factory, Customer customer, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.CustomerPasswordRecovery, languageId, customer: customer), true); + } + + /// + /// Sends wishlist "email a friend" message + /// + public static CreateMessageResult SendShareWishlistMessage(this IMessageFactory factory, Customer customer, + string fromEmail, string toEmail, string personalMessage, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + + var model = new NamedModelPart("Wishlist") + { + ["PersonalMessage"] = personalMessage, + ["From"] = fromEmail, + ["To"] = toEmail + }; + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ShareWishlist, languageId, customer: customer), true, model); + } + + /// + /// Sends a "new VAT sumitted" notification to a store owner + /// + public static CreateMessageResult SendNewVatSubmittedStoreOwnerNotification(this IMessageFactory factory, Customer customer, string vatName, string vatAddress, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + + var model = new NamedModelPart("VatValidationResult") + { + ["Name"] = vatName, + ["Address"] = vatAddress + }; + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewVatSubmittedStoreOwner, languageId, customer: customer), true, model); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs index 49fb4587aa..6343755061 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs @@ -9,13 +9,8 @@ namespace SmartStore.Services.Customers { - /// - /// Customer registration service - /// public partial class CustomerRegistrationService : ICustomerRegistrationService { - #region Fields - private readonly ICustomerService _customerService; private readonly IEncryptionService _encryptionService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; @@ -24,49 +19,41 @@ public partial class CustomerRegistrationService : ICustomerRegistrationService private readonly IStoreContext _storeContext; private readonly IEventPublisher _eventPublisher; - #endregion - - #region Ctor - - /// - /// Ctor - /// public CustomerRegistrationService(ICustomerService customerService, IEncryptionService encryptionService, INewsLetterSubscriptionService newsLetterSubscriptionService, 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; } - #endregion - public Localizer T { get; set; } - #region Methods - - /// - /// Validate customer - /// - /// Username or email - /// Password - /// Result 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; @@ -106,15 +93,10 @@ public virtual bool ValidateCustomer(string usernameOrEmail, string password) return isValid; } - /// - /// Register customer - /// - /// Request - /// Result public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationRequest request) { - Guard.ArgumentNotNull(() => request); - Guard.ArgumentNotNull(() => request.Customer); + Guard.NotNull(request, nameof(request)); + Guard.NotNull(request.Customer, nameof(request.Customer)); var result = new CustomerRegistrationResult(); @@ -123,56 +105,57 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR result.AddError(T("Account.Register.Errors.CannotRegisterSearchEngine")); return result; } + if (request.Customer.IsBackgroundTaskAccount()) { result.AddError(T("Account.Register.Errors.CannotRegisterTaskAccount")); return result; } + if (request.Customer.IsRegistered()) { result.AddError(T("Account.Register.Errors.AlreadyRegistered")); return result; } - if (String.IsNullOrEmpty(request.Email)) + + if (!request.Email.HasValue()) { result.AddError(T("Account.Register.Errors.EmailIsNotProvided")); return result; } + if (!request.Email.IsEmail()) { result.AddError(T("Common.WrongEmail")); 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 + // Validate unique user if (_customerService.GetCustomerByEmail(request.Email) != null) { result.AddError(T("Account.Register.Errors.EmailAlreadyExists")); 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 + // At this point request is valid request.Customer.Username = request.Username; request.Customer.Email = request.Email; request.Customer.PasswordFormat = request.PasswordFormat; @@ -200,7 +183,7 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR request.Customer.CustomerRoles.Add(customerRole); } - //add to 'Registered' role + // Add to 'Registered' role var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); if (registeredRole == null) { @@ -209,14 +192,14 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR request.Customer.CustomerRoles.Add(registeredRole); - //remove from 'Guests' role + // Remove from 'Guests' role var guestRole = request.Customer.CustomerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Guests); if (guestRole != null) { request.Customer.CustomerRoles.Remove(guestRole); } - //Add reward points for customer registration (if enabled) + // Add reward points for customer registration (if enabled) if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0) { request.Customer.AddRewardPointsHistoryEntry(_rewardPointsSettings.PointsForRegistration, T("RewardPoints.Message.RegisteredAsCustomer")); @@ -228,23 +211,17 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR return result; } - /// - /// Change password - /// - /// Request - /// Result public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request) { - if (request == null) - throw new ArgumentNullException("request"); + Guard.NotNull(request, nameof(request)); - var result = new PasswordChangeResult(); - if (String.IsNullOrWhiteSpace(request.Email)) + var result = new PasswordChangeResult(); + 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; @@ -257,7 +234,6 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request return result; } - var requestIsValid = false; if (request.ValidateRequest) { @@ -319,15 +295,9 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request return result; } - /// - /// Sets a user email - /// - /// Customer - /// New email public virtual void SetEmail(Customer customer, string newEmail) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); newEmail = newEmail.Trim(); string oldEmail = customer.Email; @@ -338,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) @@ -357,17 +327,11 @@ public virtual void SetEmail(Customer customer, string newEmail) } } - /// - /// Sets a customer username - /// - /// Customer - /// New Username public virtual void SetUsername(Customer customer, string newUsername) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); - if (!_customerSettings.UsernamesEnabled) + if (_customerSettings.CustomerLoginType == CustomerLoginType.Email) throw new SmartException("Usernames are disabled"); if (!_customerSettings.AllowUsersToChangeUsernames) @@ -378,14 +342,12 @@ 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; _customerService.UpdateCustomer(customer); } - - #endregion } } \ No newline at end of file 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 d6163f738c..b6890718c9 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -1,12 +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; @@ -14,252 +14,282 @@ using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Events; +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 System.Threading.Tasks; namespace SmartStore.Services.Customers { - /// - /// Customer service - /// - public partial class CustomerService : ICustomerService + public partial class CustomerService : ICustomerService { - #region Constants - - private const string CUSTOMERROLES_ALL_KEY = "SmartStore.customerrole.all-{0}"; - private const string CUSTOMERROLES_BY_SYSTEMNAME_KEY = "SmartStore.customerrole.systemname-{0}"; - private const string CUSTOMERROLES_PATTERN_KEY = "SmartStore.customerrole."; - - #endregion - - #region Fields - 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 IRequestCache _requestCache; - private readonly IEventPublisher _eventPublisher; private readonly RewardPointsSettings _rewardPointsSettings; + private readonly ICommonServices _services; + private readonly HttpContextBase _httpContext; + private readonly IUserAgent _userAgent; + private readonly CustomerSettings _customerSettings; + private readonly Lazy _gdprTool; - #endregion - - #region Ctor - - public CustomerService(IRequestCache requestCache, + public CustomerService( IRepository customerRepository, IRepository customerRoleRepository, IRepository gaRepository, IRepository rewardPointsHistoryRepository, + IRepository shoppingCartItemRepository, IGenericAttributeService genericAttributeService, - IEventPublisher eventPublisher, - RewardPointsSettings rewardPointsSettings) + RewardPointsSettings rewardPointsSettings, + ICommonServices services, + HttpContextBase httpContext, + IUserAgent userAgent, + CustomerSettings customerSettings, + Lazy gdprTool) { - this._requestCache = requestCache; - this._customerRepository = customerRepository; - this._customerRoleRepository = customerRoleRepository; - this._gaRepository = gaRepository; - this._rewardPointsHistoryRepository = rewardPointsHistoryRepository; - this._genericAttributeService = genericAttributeService; - this._eventPublisher = eventPublisher; - this._rewardPointsSettings = rewardPointsSettings; + _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; } - #endregion + public Localizer T { get; set; } - #region Properties + public ILogger Logger { get; set; } - public Localizer T { get; set; } + #region Customers - #endregion + public virtual IPagedList SearchCustomers(CustomerSearchQuery q) + { + Guard.NotNull(q, nameof(q)); - #region Methods + var isOrdered = false; + IQueryable query = null; - #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)) + 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)); + } - query = sct.HasValue ? - query.Where(c => c.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == sctId).Count() > 0) : - query.Where(c => c.ShoppingCartItems.Count() > 0); + if (q.PasswordFormat.HasValue) + { + int passwordFormatId = (int)q.PasswordFormat.Value; + query = query.Where(c => c.PasswordFormatId == passwordFormatId); + } + + // Search by phone + if (q.Phone.HasValue()) + { + query = query + .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) + .Where(z => z.Attribute.KeyGroup == "Customer" && + z.Attribute.Key == SystemCustomerAttributeNames.Phone && + z.Attribute.Value.Contains(q.Phone)) + .Select(z => z.Customer); + } + + // Search by zip + if (q.ZipPostalCode.HasValue()) + { + query = query + .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) + .Where(z => z.Attribute.KeyGroup == "Customer" && + z.Attribute.Key == SystemCustomerAttributeNames.ZipPostalCode && + z.Attribute.Value.Contains(q.ZipPostalCode)) + .Select(z => z.Customer); + } + + if (!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) @@ -267,10 +297,19 @@ public virtual Customer GetCustomerById(int customerId) if (customerId == 0) return null; - var customer = _customerRepository.GetById(customerId); - return customer; + // var customer = _customerRepository.GetById(customerId); + var customer = IncludeShoppingCart(_customerRepository.Table).SingleOrDefault(x => x.Id == customerId); + + return customer; } + private IQueryable IncludeShoppingCart(IQueryable query) + { + return query + .Expand(x => x.ShoppingCartItems.Select(y => y.BundleItem)) + .Expand(x => x.ShoppingCartItems.Select(y => y.Product.AppliedDiscounts.Select(z => z.DiscountRequirements))); + } + public virtual IList GetCustomersByIds(int[] customerIds) { if (customerIds == null || customerIds.Length == 0) @@ -279,16 +318,11 @@ public virtual IList GetCustomersByIds(int[] customerIds) var query = from c in _customerRepository.Table where customerIds.Contains(c.Id) select c; + var customers = query.ToList(); - //sort by passed identifiers - var sortedCustomers = new List(); - foreach (int id in customerIds) - { - var customer = customers.Find(x => x.Id == id); - if (customer != null) - sortedCustomers.Add(customer); - } - return sortedCustomers; + + // sort by passed identifier sequence + return customers.OrderBySequence(customerIds).ToList(); } public virtual IList GetSystemAccountCustomers() @@ -301,10 +335,11 @@ public virtual Customer GetCustomerByGuid(Guid customerGuid) if (customerGuid == Guid.Empty) return null; - var query = from c in _customerRepository.Table - where c.CustomerGuid == customerGuid + var query = from c in IncludeShoppingCart(_customerRepository.Table) + where c.CustomerGuid == customerGuid orderby c.Id select c; + var customer = query.FirstOrDefault(); return customer; } @@ -314,10 +349,11 @@ public virtual Customer GetCustomerByEmail(string email) if (string.IsNullOrWhiteSpace(email)) return null; - var query = from c in _customerRepository.Table - orderby c.Id + var query = from c in IncludeShoppingCart(_customerRepository.Table) + orderby c.Id where c.Email == email select c; + var customer = query.FirstOrDefault(); return customer; } @@ -331,6 +367,7 @@ public virtual Customer GetCustomerBySystemName(string systemName) orderby c.Id where c.SystemName == systemName select c; + var customer = query.FirstOrDefault(); return customer; } @@ -340,8 +377,8 @@ public virtual Customer GetCustomerByUsername(string username) if (string.IsNullOrWhiteSpace(username)) return null; - var query = from c in _customerRepository.Table - orderby c.Id + var query = from c in IncludeShoppingCart(_customerRepository.Table) + orderby c.Id where c.Username == username select c; @@ -349,85 +386,141 @@ orderby c.Id return customer; } - public virtual Customer InsertGuestCustomer() + public virtual Customer InsertGuestCustomer(Guid? customerGuid = null) { var customer = new Customer { - CustomerGuid = Guid.NewGuid(), + CustomerGuid = customerGuid ?? Guid.NewGuid(), Active = true, CreatedOnUtc = DateTime.UtcNow, LastActivityDateUtc = DateTime.UtcNow, }; - - //add to 'Guests' role + + // Add to 'Guests' role var guestRole = GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); if (guestRole == null) throw new SmartException("'Guests' role could not be loaded"); - customer.CustomerRoles.Add(guestRole); + using (new DbContextScope(autoCommit: true)) + { + // Ensure that entities are saved to db in any case + customer.CustomerRoles.Add(guestRole); + _customerRepository.Insert(customer); - _customerRepository.Insert(customer); + var clientIdent = _services.WebHelper.GetClientIdent(); + if (clientIdent.HasValue()) + { + _genericAttributeService.SaveAttribute(customer, "ClientIdent", clientIdent); + } + } - return customer; + //Logger.DebugFormat("Guest account created for anonymous visitor. Id: {0}, ClientIdent: {1}", customer.CustomerGuid, clientIdent ?? "n/a"); + + return customer; } - - public virtual void InsertCustomer(Customer customer) - { - if (customer == null) - throw new ArgumentNullException("customer"); - _customerRepository.Insert(customer); + public virtual Customer FindGuestCustomerByClientIdent(string clientIdent = null, int maxAgeSeconds = 60) + { + if (_httpContext.IsFakeContext() || _userAgent.IsBot || _userAgent.IsPdfConverter) + { + return null; + } + + using (_services.Chronometer.Step("FindGuestCustomerByClientIdent")) + { + clientIdent = clientIdent.NullEmpty() ?? _services.WebHelper.GetClientIdent(); + if (clientIdent.IsEmpty()) + { + return null; + } + + var dateFrom = DateTime.UtcNow.AddSeconds(maxAgeSeconds * -1); + + IQueryable query; + if (DataSettings.Current.IsSqlServer) + { + query = from a in _gaRepository.TableUntracked + join c in _customerRepository.Table on a.EntityId equals c.Id into Customers + from c in Customers.DefaultIfEmpty() + where c.LastActivityDateUtc >= dateFrom + && c.Username == null + && c.Email == null + && a.KeyGroup == "Customer" + && a.Key == "ClientIdent" + && a.Value == clientIdent + select c; + } + else + { + query = from a in _gaRepository.TableUntracked + join c in _customerRepository.Table on a.EntityId equals c.Id into Customers + from c in Customers.DefaultIfEmpty() + where c.LastActivityDateUtc >= dateFrom + && c.Username == null + && c.Email == null + && a.KeyGroup == "Customer" + && a.Key == "ClientIdent" + && a.Value.Contains(clientIdent) // SQLCE doesn't like ntext in WHERE clauses + select c; + } + + return query.FirstOrDefault(); + } + } + + public virtual void InsertCustomer(Customer customer) + { + Guard.NotNull(customer, nameof(customer)); - //event notification - _eventPublisher.EntityInserted(customer); + _customerRepository.Insert(customer); } public virtual void UpdateCustomer(Customer customer) { - if (customer == null) - throw new ArgumentNullException("customer"); - - _customerRepository.Update(customer); + Guard.NotNull(customer, nameof(customer)); - //event notification - _eventPublisher.EntityUpdated(customer); + _customerRepository.Update(customer); } - public virtual void ResetCheckoutData(Customer customer, int storeId, - bool clearCouponCodes = false, bool clearCheckoutAttributes = false, - bool clearRewardPoints = true, 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); @@ -436,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; @@ -464,13 +561,16 @@ public virtual int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? re // no customer content query = JoinWith(query, x => x.CustomerId); + // no private messages (guests can only receive but not send messages) + query = JoinWith(query, x => x.ToCustomerId); + // no forum posts query = JoinWith(query, x => x.CustomerId); // 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 @@ -481,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) @@ -490,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); @@ -506,7 +605,7 @@ orderby cGroup.Key // save changes all 1000th item try { - scope.Commit(); + await ctx.SaveChangesAsync(); } catch (Exception ex) { @@ -521,7 +620,7 @@ orderby cGroup.Key } // save the rest - scope.Commit(); + await ctx.SaveChangesAsync(); return numberOfDeletedCustomers; } @@ -559,28 +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); - - _requestCache.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(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) @@ -588,56 +681,38 @@ public virtual CustomerRole GetCustomerRoleBySystemName(string systemName) if (String.IsNullOrWhiteSpace(systemName)) return null; - string key = string.Format(CUSTOMERROLES_BY_SYSTEMNAME_KEY, systemName); - return _requestCache.Get(key, () => - { - var query = from cr in _customerRoleRepository.Table - orderby cr.Id - where cr.SystemName == systemName - select cr; - var customerRole = query.FirstOrDefault(); - return customerRole; - }); - } + var query = from cr in _customerRoleRepository.Table + orderby cr.Id + where cr.SystemName == systemName + select cr; + + var customerRole = query.FirstOrDefaultCached(); + return customerRole; + } public virtual IList GetAllCustomerRoles(bool showHidden = false) { - string key = string.Format(CUSTOMERROLES_ALL_KEY, showHidden); - return _requestCache.Get(key, () => - { - var query = from cr in _customerRoleRepository.Table - orderby cr.Name - where (showHidden || cr.Active) - select cr; - var customerRoles = query.ToList(); - return customerRoles; - }); - } + var query = from cr in _customerRoleRepository.Table + orderby cr.Name + where (showHidden || cr.Active) + select cr; + + var customerRoles = query.ToListCached(); + 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); - - _requestCache.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(customerRole); + _customerRoleRepository.Insert(role); } - public virtual void UpdateCustomerRole(CustomerRole customerRole) + public virtual void UpdateCustomerRole(CustomerRole role) { - if (customerRole == null) - throw new ArgumentNullException("customerRole"); - - _customerRoleRepository.Update(customerRole); + Guard.NotNull(role, nameof(role)); - _requestCache.RemoveByPattern(CUSTOMERROLES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(customerRole); + _customerRoleRepository.Update(role); } #endregion @@ -658,7 +733,7 @@ public virtual void RewardPointsForProductReview(Customer customer, Product prod public virtual Multimap GetRewardPointsHistoriesByCustomerIds(int[] customerIds) { - Guard.ArgumentNotNull(() => customerIds); + Guard.NotNull(customerIds, nameof(customerIds)); var query = from x in _rewardPointsHistoryRepository.TableUntracked @@ -676,7 +751,5 @@ where customerIds.Contains(x.CustomerId) } #endregion Reward points - - #endregion } } \ No newline at end of file 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 0f7a914a77..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 @@ -122,17 +94,29 @@ IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, /// Customer Customer GetCustomerByUsername(string username); - /// - /// Insert a guest customer - /// - /// Customer - Customer InsertGuestCustomer(); + /// + /// Insert a guest customer + /// + /// The customer GUID. Pass null to create a random one. + /// Customer + Customer InsertGuestCustomer(Guid? customerGuid = null); - /// - /// Insert a customer - /// - /// Customer - void InsertCustomer(Customer customer); + /// + /// Tries to find a guest/anonymous customer record by client ident. This method should be called when an + /// anonymous visitor rejects cookies and therefore cannot be identified automatically. + /// + /// + /// The client ident string, which is a hashed combination of client IP address and user agent. + /// Call to obtain an ident string, or pass null to let this method obtain it automatically. + /// The max age of the newly created guest customer record. The shorter, the better (default is 1 min.) + /// The identified customer or null + Customer FindGuestCustomerByClientIdent(string clientIdent = null, int maxAgeSeconds = 60); + + /// + /// Insert a customer + /// + /// Customer + void InsertCustomer(Customer customer); /// /// Updates the customer @@ -140,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 = true, bool clearShippingMethod = true, - bool clearPaymentMethod = true); + bool clearRewardPoints = false, bool clearShippingMethod = 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 452f281fb8..f806f232fe 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; @@ -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,15 +21,15 @@ namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : EntityImporterBase + public class CustomerImporter : EntityImporterBase { private const string _attributeKeyGroup = "Customer"; private readonly IRepository _customerRepository; + private readonly IRepository _customerRoleRepository; private readonly IRepository _pictureRepository; private readonly ICommonServices _services; private readonly IGenericAttributeService _genericAttributeService; - private readonly ICustomerService _customerService; private readonly IPictureService _pictureService; private readonly IAffiliateService _affiliateService; private readonly ICountryService _countryService; @@ -42,10 +41,10 @@ public class CustomerImporter : EntityImporterBase public CustomerImporter( IRepository customerRepository, + IRepository customerRoleRepository, IRepository pictureRepository, ICommonServices services, IGenericAttributeService genericAttributeService, - ICustomerService customerService, IPictureService pictureService, IAffiliateService affiliateService, ICountryService countryService, @@ -56,10 +55,10 @@ public CustomerImporter( ForumSettings forumSettings) { _customerRepository = customerRepository; + _customerRoleRepository = customerRoleRepository; _pictureRepository = pictureRepository; _services = services; _genericAttributeService = genericAttributeService; - _customerService = customerService; _pictureService = pictureService; _affiliateService = affiliateService; _countryService = countryService; @@ -70,7 +69,6 @@ public CustomerImporter( _forumSettings = forumSettings; } - protected override void Import(ImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; @@ -92,22 +90,27 @@ 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); - using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + var allCustomerRoles = _customerRoleRepository.Table + .Where(x => !string.IsNullOrEmpty(x.SystemName)) + .ToDictionarySafe(x => x.SystemName, StringComparer.OrdinalIgnoreCase); + + using (var scope = new DbContextScope(ctx: _services.DbContext, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { var segmenter = context.DataSegmenter; Initialize(context); + AddInfoForDeprecatedFields(context); while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); - _customerRepository.Context.DetachAll(false); + _customerRepository.Context.DetachAll(true); context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); @@ -116,7 +119,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessCustomers(context, batch, allAffiliateIds); + ProcessCustomers(context, batch, allAffiliateIds, allCustomerNumbers); } catch (Exception exception) { @@ -131,12 +134,29 @@ protected override void Import(ImportExecuteContext context) context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + // =========================================================================== + // Process customer roles + // =========================================================================== + try + { + _customerRepository.Context.AutoDetectChangesEnabled = true; + ProcessCustomerRoles(context, batch, allCustomerRoles); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCustomerRoles"); + } + finally + { + _customerRepository.Context.AutoDetectChangesEnabled = false; + } + // =========================================================================== // Process generic attributes // =========================================================================== try { - ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); + ProcessGenericAttributes(context, batch, allCountries, allStateProvinces); } catch (Exception exception) { @@ -174,26 +194,23 @@ 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; - Customer lastInserted = null; - Customer lastUpdated = null; var currentCustomer = _services.WorkContext.CurrentCustomer; - - var guestRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); - var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); - var forumModeratorRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.ForumModerators); - var customerQuery = _customerRepository.Table.Expand(x => x.Addresses); + var hasCustomerRoleSystemNames = context.DataSegmenter.HasColumn("CustomerRoleSystemNames"); foreach (var row in batch) { @@ -258,10 +275,6 @@ protected virtual int ProcessCustomers( _customerRepository.Context.LoadCollection(customer, (Customer x) => x.CustomerRoles); } - var isGuest = row.GetDataValue("IsGuest"); - var isRegistered = row.GetDataValue("IsRegistered"); - var isAdmin = row.GetDataValue("IsAdministrator"); - var isForumModerator = row.GetDataValue("IsForumModerator"); var affiliateId = row.GetDataValue("AffiliateId"); row.Initialize(customer, email ?? id.ToString()); @@ -269,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)) { @@ -293,40 +317,104 @@ protected virtual int ProcessCustomers( customer.AffiliateId = affiliateId; } - if (isAdmin) + 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)) { - context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "IsAdministrator"); - } + row.Entity.CustomerNumber = customerNumber; - UpsertRole(row, guestRole, isGuest); - UpsertRole(row, registeredRole, isRegistered); - UpsertRole(row, forumModeratorRole, isForumModerator); + if (!customerNumber.IsEmpty()) + allCustomerNumbers.Add(customerNumber); + } if (row.IsTransient) { _customerRepository.Insert(customer); - lastInserted = customer; } else { _customerRepository.Update(customer); - lastUpdated = customer; } } var num = _customerRepository.Context.SaveChanges(); + return num; + } - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } + protected virtual int ProcessCustomerRoles( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary allCustomerRoles) + { + CustomerRole role = null; + var hasCustomerRoleSystemNames = context.DataSegmenter.HasColumn("CustomerRoleSystemNames"); + + // Deprecated fields. Still processed for backward compatibility. Use "CustomerRoleSystemNames" instead. + var hasIsGuest = context.DataSegmenter.HasColumn("IsGuest"); + var hasIsRegistered = context.DataSegmenter.HasColumn("IsRegistered"); + var hasIsForumModerator = context.DataSegmenter.HasColumn("IsForumModerator"); - if (lastUpdated != null) + foreach (var row in batch) { - _services.EventPublisher.EntityUpdated(lastUpdated); + var customer = row.Entity; + + // Deprecated customer role fields. + if (hasIsGuest) + { + UpsertRole(customer, row.GetDataValue("IsGuest"), allCustomerRoles, SystemCustomerRoleNames.Guests); + } + if (hasIsRegistered) + { + UpsertRole(customer, row.GetDataValue("IsRegistered"), allCustomerRoles, SystemCustomerRoleNames.Registered); + } + if (hasIsForumModerator) + { + UpsertRole(customer, row.GetDataValue("IsForumModerator"), allCustomerRoles, SystemCustomerRoleNames.ForumModerators); + } + + // New customer role field. + if (hasCustomerRoleSystemNames) + { + var importRoleSystemNames = row.GetDataValue>("CustomerRoleSystemNames"); + var assignedRoles = customer.CustomerRoles.ToDictionarySafe(x => x.SystemName, StringComparer.OrdinalIgnoreCase); + + // Roles to remove. + foreach (var customerRole in assignedRoles) + { + var systemName = customerRole.Key; + if (!systemName.IsCaseInsensitiveEqual(SystemCustomerRoleNames.Administrators) && + !systemName.IsCaseInsensitiveEqual(SystemCustomerRoleNames.SuperAdministrators) && + !importRoleSystemNames.Contains(systemName)) + { + customer.CustomerRoles.Remove(customerRole.Value); + } + } + + // Roles to add. + foreach (var systemName in importRoleSystemNames) + { + if (systemName.IsCaseInsensitiveEqual(SystemCustomerRoleNames.Administrators) || + systemName.IsCaseInsensitiveEqual(SystemCustomerRoleNames.SuperAdministrators)) + { + context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "CustomerRoleSystemNames"); + } + else if (!assignedRoles.ContainsKey(systemName) && allCustomerRoles.TryGetValue(systemName, out role)) + { + // Never insert roles! + context.Services.DbContext.SetToUnchanged(role); + + customer.CustomerRoles.Add(role); + } + } + } } - return num; + return context.Services.DbContext.SaveChanges(); } protected virtual int ProcessAddresses( @@ -423,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); @@ -485,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(); @@ -515,12 +578,11 @@ protected virtual int ProcessAvatars( if (urlOrPath.IsEmpty()) continue; - Picture picture = null; var equalPictureId = 0; 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; @@ -536,20 +598,24 @@ protected virtual int ProcessAvatars( if (pictureBinary != null && pictureBinary.Length > 0) { - var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); - if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) + var pictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + if (pictureId != 0) { - currentPictures.Add(picture); + var picture = _pictureRepository.TableUntracked.Expand(x => x.MediaStorage).FirstOrDefault(x => x.Id == pictureId); + if (picture != null) + { + currentPictures.Add(picture); + } } - - pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); if (pictureBinary != null && pictureBinary.Length > 0) { - if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) + var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + + if (picture != null) { - _pictureRepository.Context.SaveChanges(); SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); } } @@ -618,7 +684,6 @@ private void SaveAttribute(ImportRow row, string key) private void SaveAttribute(ImportRow row, string key) { - SaveAttribute(row, key, row.GetDataValue(key)); } @@ -633,20 +698,44 @@ private void SaveAttribute(ImportRow row, string key, TProp } } - private void UpsertRole(ImportRow row, CustomerRole role, bool value) + private void UpsertRole(Customer customer, bool value, Dictionary allRoles, string customerRoleSystemName) { - if (role == null) - return; + CustomerRole role = null; + if (allRoles.TryGetValue(customerRoleSystemName, out role)) + { + var hasRole = customer.CustomerRoles.Any(x => x.SystemName == role.SystemName); - var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + if (value && !hasRole) + { + // Never insert roles! + _services.DbContext.SetToUnchanged(role); - if (value && !hasRole) + customer.CustomerRoles.Add(role); + } + else if (!value && hasRole) + { + customer.CustomerRoles.Remove(role); + } + } + } + + private void AddInfoForDeprecatedFields(ImportExecuteContext context) + { + if (context.DataSegmenter.HasColumn("IsGuest")) + { + context.Result.AddInfo("Deprecated field. Use CustomerRoleSystemNames instead.", null, "IsGuest"); + } + if (context.DataSegmenter.HasColumn("IsRegistered")) + { + context.Result.AddInfo("Deprecated field. Use CustomerRoleSystemNames instead.", null, "IsRegistered"); + } + if (context.DataSegmenter.HasColumn("IsAdministrator")) { - row.Entity.CustomerRoles.Add(role); + context.Result.AddInfo("Deprecated field. Use CustomerRoleSystemNames instead.", null, "IsAdministrator"); } - else if (!value && hasRole) + if (context.DataSegmenter.HasColumn("IsForumModerator")) { - row.Entity.CustomerRoles.Remove(role); + context.Result.AddInfo("Deprecated field. Use CustomerRoleSystemNames instead.", null, "IsForumModerator"); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs index 9fbe870a2b..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 @@ -41,26 +38,28 @@ public CsvDataReader(TextReader reader) /// A pointing to the CSV file. public CsvDataReader(TextReader reader, CsvConfiguration configuration) { - Guard.ArgumentNotNull(() => reader); - Guard.ArgumentNotNull(() => configuration); + Guard.NotNull(reader, nameof(reader)); + Guard.NotNull(configuration, nameof(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/Csv/CsvWriter.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs index 8bb9942052..f2f360556b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs @@ -30,8 +30,8 @@ public CsvWriter(TextWriter writer) public CsvWriter(TextWriter writer, CsvConfiguration configuration) { - Guard.ArgumentNotNull(() => writer); - Guard.ArgumentNotNull(() => configuration); + Guard.NotNull(writer, nameof(writer)); + Guard.NotNull(configuration, nameof(configuration)); _writer = writer; this.Configuration = configuration; @@ -53,7 +53,7 @@ public CsvConfiguration Configuration /// The fields to write. public virtual void WriteFields(IEnumerable fields) { - Guard.ArgumentNotNull(() => fields); + Guard.NotNull(fields, nameof(fields)); fields.Each(x => WriteField(x)); } @@ -71,7 +71,7 @@ public virtual void WriteFields(IEnumerable fields) /// True to quote the fields, otherwise false. public virtual void WriteFields(IEnumerable fields, bool shouldQuote) { - Guard.ArgumentNotNull(() => fields); + Guard.NotNull(fields, nameof(fields)); fields.Each(x => WriteField(x, shouldQuote)); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs index d46879193c..709c6deff9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs @@ -27,7 +27,7 @@ public class ExcelDataReader : DisposableObject, IDataReader public ExcelDataReader(Stream source, bool hasHeaders) { - Guard.ArgumentNotNull(() => source); + Guard.NotNull(source, nameof(source)); _package = new ExcelPackage(source); @@ -130,7 +130,7 @@ public string GetFormatted(int i) public int GetColumnIndex(string name) { - Guard.ArgumentNotEmpty(name, "name"); + Guard.NotEmpty(name, nameof(name)); EnsureInitialize(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs index c9d3fa26f2..57c85801ab 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs @@ -1,15 +1,16 @@ 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() { - Files = new List(); + Files = new List(); } /// @@ -46,9 +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. + /// + public string Label { 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 f32f06c625..0d935149e8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs @@ -4,7 +4,7 @@ namespace SmartStore.Services.DataExchange.Export { - // note: namespace persisted in ScheduleTask.Type + // Note, namespace persisted in ScheduleTask.Type! public partial class DataExportTask : ITask { private readonly IDataExporter _exporter; @@ -22,15 +22,15 @@ 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 + // Load provider. var provider = _exportProfileService.LoadProvider(profile.ProviderSystemName); if (provider == null) throw new SmartException(T("Admin.Common.ProviderNotLoaded", profile.ProviderSystemName.NaIfEmpty())); - // build export request + // Build export request. var request = new DataExportRequest(profile, provider); request.ProgressValueSetter = delegate (int val, int max, string msg) @@ -46,7 +46,12 @@ public void Execute(TaskExecutionContext ctx) .ToList(); } - // process! + if (ctx.Parameters.ContainsKey("ActionOrigin")) + { + request.ActionOrigin = ctx.Parameters["ActionOrigin"]; + } + + // Process! _exporter.Export(request, ctx.CancellationToken); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 41cb53a69f..6d1b16defc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -6,40 +6,49 @@ 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; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Tax; using SmartStore.Core.Email; using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Services.Catalog; +using SmartStore.Services.Catalog.Extensions; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Services.DataExchange.Export.Internal; using SmartStore.Services.Directory; -using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Orders; +using SmartStore.Services.Search; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Shipping; 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(); @@ -47,8 +56,8 @@ public partial class DataExporter : IDataExporter private readonly ICommonServices _services; private readonly IDbContext _dbContext; + private readonly HttpContextBase _httpContext; private readonly Lazy _priceFormatter; - private readonly Lazy _dateTimeHelper; private readonly Lazy _exportProfileService; private readonly Lazy _localizedEntityService; private readonly Lazy _languageService; @@ -60,7 +69,8 @@ public partial class DataExporter : IDataExporter private readonly Lazy _categoryService; private readonly Lazy _productAttributeParser; private readonly Lazy _productAttributeService; - private readonly Lazy _productTemplateService; + private readonly Lazy _specificationAttributeService; + private readonly Lazy _productTemplateService; private readonly Lazy _categoryTemplateService; private readonly Lazy _productService; private readonly Lazy _orderService; @@ -75,23 +85,30 @@ public partial class DataExporter : IDataExporter private readonly Lazy _emailSender; private readonly Lazy _deliveryTimeService; private readonly Lazy _quantityUnitService; + private readonly Lazy _catalogSearchService; + private readonly Lazy _downloadService; + private readonly Lazy _productUrlHelper; private readonly Lazy>_customerRepository; private readonly Lazy> _subscriptionRepository; private readonly Lazy> _orderRepository; + private readonly Lazy> _shoppingCartItemRepository; private readonly Lazy _mediaSettings; private readonly Lazy _contactDataSettings; private readonly Lazy _customerSettings; private readonly Lazy _catalogSettings; + private readonly Lazy _localizationSettings; + private readonly Lazy _taxSettings; + private readonly Lazy _seoSettings; - public DataExporter( + public DataExporter( ICommonServices services, IDbContext dbContext, - Lazy priceFormatter, - Lazy dateTimeHelper, + HttpContextBase httpContext, + Lazy priceFormatter, Lazy exportProfileService, - Lazy localizedEntityService, + Lazy localizedEntityService, Lazy languageService, Lazy urlRecordService, Lazy pictureService, @@ -101,7 +118,8 @@ public DataExporter( Lazy categoryService, Lazy productAttributeParser, Lazy productAttributeService, - Lazy productTemplateService, + Lazy specificationAttributeService, + Lazy productTemplateService, Lazy categoryTemplateService, Lazy productService, Lazy orderService, @@ -116,18 +134,25 @@ public DataExporter( Lazy emailSender, Lazy deliveryTimeService, Lazy quantityUnitService, - Lazy> customerRepository, + Lazy catalogSearchService, + Lazy downloadService, + Lazy productUrlHelper, + Lazy> customerRepository, Lazy> subscriptionRepository, Lazy> orderRepository, + Lazy> shoppingCartItemRepository, Lazy mediaSettings, Lazy contactDataSettings, Lazy customerSettings, - Lazy catalogSettings) + Lazy catalogSettings, + Lazy localizationSettings, + Lazy taxSettings, + Lazy seoSettings) { _services = services; _dbContext = dbContext; + _httpContext = httpContext; _priceFormatter = priceFormatter; - _dateTimeHelper = dateTimeHelper; _exportProfileService = exportProfileService; _localizedEntityService = localizedEntityService; _languageService = languageService; @@ -139,6 +164,7 @@ public DataExporter( _categoryService = categoryService; _productAttributeParser = productAttributeParser; _productAttributeService = productAttributeService; + _specificationAttributeService = specificationAttributeService; _productTemplateService = productTemplateService; _categoryTemplateService = categoryTemplateService; _productService = productService; @@ -154,38 +180,69 @@ public DataExporter( _emailSender = emailSender; _deliveryTimeService = deliveryTimeService; _quantityUnitService = quantityUnitService; + _catalogSearchService = catalogSearchService; + _downloadService = downloadService; + _productUrlHelper = productUrlHelper; _customerRepository = customerRepository; _subscriptionRepository = subscriptionRepository; _orderRepository = orderRepository; + _shoppingCartItemRepository = shoppingCartItemRepository; _mediaSettings = mediaSettings; _contactDataSettings = contactDataSettings; _customerSettings = customerSettings; _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; + } + + private UrlRecordCollection CreateUrlRecordCollection(string entityName, IEnumerable entities) + { + if (entities == null || !entities.Any()) + { + return new UrlRecordCollection(entityName, null, Enumerable.Empty()); + } - #region Utilities + var collection = _urlRecordService.Value.GetUrlRecordCollection(entityName, null, entities.Select(x => x.Id).Distinct().ToArray()); + return collection; + } - private void SetProgress(DataExporterContext ctx, int loadedRecords) + 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); } } @@ -207,129 +264,250 @@ private void SetProgress(DataExporterContext ctx, string message) private bool HasPermission(DataExporterContext ctx) { if (ctx.Request.HasPermission) + { return true; + } var customer = _services.WorkContext.CurrentCustomer; if (customer.SystemName == SystemCustomerNames.BackgroundTask) + { return true; + } - if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Product || - ctx.Request.Provider.Value.EntityType == ExportEntityType.Category || - ctx.Request.Provider.Value.EntityType == ExportEntityType.Manufacturer) - return _services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog, customer); + switch (ctx.Request.Provider.Value.EntityType) + { + case ExportEntityType.Product: + case ExportEntityType.Category: + case ExportEntityType.Manufacturer: + return _services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog, customer); - if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Customer) - return _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomers, customer); + case ExportEntityType.Customer: + return _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomers, customer); - if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Order) - return _services.Permissions.Authorize(StandardPermissionProvider.ManageOrders, customer); + case ExportEntityType.Order: + case ExportEntityType.ShoppingCartItem: + return _services.Permissions.Authorize(StandardPermissionProvider.ManageOrders, customer); - if (ctx.Request.Provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) - return _services.Permissions.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers, customer); + case ExportEntityType.NewsLetterSubscription: + return _services.Permissions.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers, customer); + } return true; } - private void DetachAllEntitiesAndClear(DataExporterContext ctx) - { - try - { - _dbContext.DetachAll(); - } - catch (Exception exception) - { - ctx.Log.Warning("Detaching all entities failed.", exception); - } + 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); + } + } - try - { - // now again attach what is globally required - _dbContext.Attach(ctx.Request.Profile); - _dbContext.AttachRange(ctx.Stores.Values); + if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) + { + FileSystemHelper.DeleteFile(path); } - catch (Exception exception) - { - ctx.Log.Warning("Re-attaching entities failed.", exception); - } + } + 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 => + { + return x is Product || x is Discount || x is ProductVariantAttributeCombination || x is ProductVariantAttribute || + x is Picture || x is ProductBundleItem || x is ProductCategory || x is ProductManufacturer || + x is ProductPicture || x is ProductTag || x is ProductSpecificationAttribute || x is TierPrice; + }); + ctx.ProductExportContext.Clear(); + } if (ctx.OrderExportContext != null) - ctx.OrderExportContext.Clear(); + { + _dbContext.DetachEntities(x => + { + return x is Order || x is Address || x is GenericAttribute || x is Customer || + x is OrderItem || x is RewardPointsHistory || x is Shipment; + }); - if (ctx.ManufacturerExportContext != null) - ctx.ManufacturerExportContext.Clear(); + ctx.OrderExportContext.Clear(); + } if (ctx.CategoryExportContext != null) + { + _dbContext.DetachEntities(x => + { + return x is Category || x is Picture || x is ProductCategory; + }); + ctx.CategoryExportContext.Clear(); + } + + if (ctx.ManufacturerExportContext != null) + { + _dbContext.DetachEntities(x => + { + return x is Manufacturer || x is Picture || x is ProductManufacturer; + }); + + ctx.ManufacturerExportContext.Clear(); + } if (ctx.CustomerExportContext != null) + { + _dbContext.DetachEntities(x => + { + return x is Customer || x is GenericAttribute || x is CustomerContent; + }); + ctx.CustomerExportContext.Clear(); + } + + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.ShoppingCartItem) + { + _dbContext.DetachEntities(x => + { + return x is ShoppingCartItem || x is Customer || x is Product; + }); + } + } + catch (Exception ex) + { + ctx.Log.Warn(ex, "Detaching entities failed."); } - catch { } } - 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 limit = (ctx.IsPreview ? PageSize : 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; + 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 totalRecords = offset + stats.TotalRecords; - switch (ctx.Request.Provider.Value.EntityType) + 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 = new ProductExportContext(entities, - x => _productAttributeService.Value.GetProductVariantAttributesByProductIds(x, null), - x => _productAttributeService.Value.GetProductVariantAttributeCombinations(x), - x => _productService.Value.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), - x => _categoryService.Value.GetProductCategoriesByProductIds(x), - x => _manufacturerService.Value.GetProductManufacturersByProductIds(x), - x => _productService.Value.GetProductPicturesByProductIds(x), - x => _productService.Value.GetProductTagsByProductIds(x), - x => _productService.Value.GetAppliedDiscountsByProductIds(x), - x => _productService.Value.GetProductSpecificationAttributesByProductIds(x), - x => _productService.Value.GetBundleItemsByProductIds(x, true) - ); - }, + // 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, x => _customerService.GetCustomersByIds(x), + x => _genericAttributeService.Value.GetAttributesForEntity(x, "Customer"), x => _customerService.GetRewardPointsHistoriesByCustomerIds(x), x => _addressService.Value.GetAddressByIds(x), 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, @@ -338,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, @@ -354,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, @@ -369,17 +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 + ( + () => 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; @@ -391,65 +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) - { - if (method != "Execute" && method != "OnExecuted") - throw new SmartException("Unknown export method {0}.".FormatInvariant(method.NaIfEmpty())); + 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(); + } - try - { - ctx.ExecuteContext.DataStreamId = streamId; + 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() + }); + } - 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); - } + return result; + } - if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) - { - if (!ctx.ExecuteContext.DataStream.CanSeek) - { - ctx.Log.Warning("Data stream seems to be closed!"); - } + private bool CallProvider(DataExporterContext ctx, string method, string path) + { + if (method != "Execute" && method != "OnExecuted") + { + throw new SmartException($"Unknown export method {method.NaIfEmpty()}."); + } - ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); + var context = ctx.ExecuteContext; + var provider = ctx.Request.Provider.Value; - using (_rwLock.GetWriteLock()) - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - { - ctx.Log.Information("Creating file {0}.".FormatInvariant(path)); - ctx.ExecuteContext.DataStream.CopyTo(fileStream); - } - } - } - } - catch (Exception exception) + try { - ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; - ctx.Log.Error("The provider failed at the {0} method: {1}.".FormatInvariant(method, exception.ToAllMessages()), exception); - ctx.Result.LastError = exception.ToString(); + context.DataStream = new MemoryStream(); + + if (method == "Execute") + { + provider.Execute(context); + } + else if (method == "OnExecuted") + { + provider.OnExecuted(context); + } + } + catch (Exception ex) + { + 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) @@ -507,17 +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.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( - deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); + ctx.Log.Error(ex, $"Deployment \"{deployment.Name}\" of type {deployment.DeploymentType.ToString()} failed."); } deployment.ResultInfo = XmlHelper.Serialize(context.Result); @@ -530,13 +763,13 @@ private bool Deploy(DataExporterContext ctx, string zipPath) private void SendCompletionEmail(DataExporterContext ctx, string zipPath) { - var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Request.Profile.EmailAccountId); - + var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Request.Profile.EmailAccountId); if (emailAccount == null) - emailAccount = _emailAccountService.Value.GetDefaultEmailAccount(); + { + return; + } var downloadUrl = "{0}Admin/Export/DownloadExportFile/{1}?name=".FormatInvariant(_services.WebHelper.GetStoreLocation(ctx.Store.SslEnabled), ctx.Request.Profile.Id); - var languageId = ctx.Projection.LanguageId ?? 0; var smtpContext = new SmtpContext(emailAccount); var message = new EmailMessage(); @@ -602,126 +835,190 @@ private void SendCompletionEmail(DataExporterContext ctx, string zipPath) #region Getting data - private IQueryable GetProductQuery(DataExporterContext ctx, int skip, int take) + public virtual ProductExportContext CreateProductExportContext( + IEnumerable products = null, + Customer customer = null, + int? storeId = null, + int? maxPicturesPerProduct = null, + bool showHidden = true) + { + if (customer == null) + customer = _services.WorkContext.CurrentCustomer; + + if (!storeId.HasValue) + storeId = _services.StoreContext.CurrentStore.Id; + + var context = new ProductExportContext(products, + x => _productAttributeService.Value.GetProductVariantAttributesByProductIds(x, null), + x => _productAttributeService.Value.GetProductVariantAttributeCombinations(x), + x => _specificationAttributeService.Value.GetProductSpecificationAttributesByProductIds(x), + x => _productService.Value.GetTierPricesByProductIds(x, customer, storeId.GetValueOrDefault()), + x => _categoryService.Value.GetProductCategoriesByProductIds(x, null, showHidden), + 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 => _downloadService.Value.GetDownloadsByEntityIds(x, nameof(Product)) + ); + + return context; + } + + private IQueryable GetProductQuery(DataExporterContext ctx, int? skip, int take) { IQueryable query = null; if (ctx.Request.ProductQuery == null) { - var searchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.CreatedOn, - ProductIds = ctx.Request.EntitiesToExport, - StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), - VisibleIndividuallyOnly = true, - PriceMin = ctx.Filter.PriceMinimum, - PriceMax = ctx.Filter.PriceMaximum, - IsPublished = ctx.Filter.IsPublished, - WithoutCategories = ctx.Filter.WithoutCategories, - WithoutManufacturers = ctx.Filter.WithoutManufacturers, - ManufacturerId = ctx.Filter.ManufacturerId ?? 0, - FeaturedProducts = ctx.Filter.FeaturedProducts, - ProductType = ctx.Filter.ProductType, - ProductTagId = ctx.Filter.ProductTagId ?? 0, - IdMin = ctx.Filter.IdMinimum ?? 0, - IdMax = ctx.Filter.IdMaximum ?? 0, - AvailabilityMinimum = ctx.Filter.AvailabilityMinimum, - AvailabilityMaximum = ctx.Filter.AvailabilityMaximum - }; - - if (!ctx.Filter.IsPublished.HasValue) - searchContext.ShowHidden = true; - - if (ctx.Filter.CategoryIds != null && ctx.Filter.CategoryIds.Length > 0) - searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); - - if (ctx.Filter.CreatedFrom.HasValue) - searchContext.CreatedFromUtc = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.CurrentTimeZone); - - if (ctx.Filter.CreatedTo.HasValue) - searchContext.CreatedToUtc = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.Value.CurrentTimeZone); - - query = _productService.Value.PrepareProductSearchQuery(searchContext); + var f = ctx.Filter; + var createdFrom = f.CreatedFrom.HasValue ? (DateTime?)_services.DateTimeHelper.ConvertToUtcTime(f.CreatedFrom.Value, _services.DateTimeHelper.CurrentTimeZone) : null; + var createdTo = f.CreatedTo.HasValue ? (DateTime?)_services.DateTimeHelper.ConvertToUtcTime(f.CreatedTo.Value, _services.DateTimeHelper.CurrentTimeZone) : null; + + var searchQuery = new CatalogSearchQuery() + .WithCurrency(ctx.ContextCurrency) + .WithLanguage(ctx.ContextLanguage) + .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); + + if (f.IsPublished.HasValue) + searchQuery = searchQuery.PublishedOnly(f.IsPublished.Value); + + if (f.ProductType.HasValue) + searchQuery = searchQuery.IsProductType(f.ProductType.Value); + + if (f.ProductTagId.HasValue) + searchQuery = searchQuery.WithProductTagIds(f.ProductTagId.Value); + + if (f.WithoutManufacturers.HasValue) + searchQuery = searchQuery.HasAnyManufacturer(!f.WithoutManufacturers.Value); + else if (f.ManufacturerId.HasValue) + searchQuery = searchQuery.WithManufacturerIds(f.FeaturedProducts, f.ManufacturerId.Value); + + if (f.WithoutCategories.HasValue) + searchQuery = searchQuery.HasAnyCategory(!f.WithoutCategories.Value); + else if (f.CategoryIds != null && f.CategoryIds.Length > 0) + searchQuery = searchQuery.WithCategoryIds(f.FeaturedProducts, f.CategoryIds); + + if (ctx.Request.EntitiesToExport.Count > 0) + searchQuery = searchQuery.WithProductIds(ctx.Request.EntitiesToExport.ToArray()); + else + searchQuery = searchQuery.WithProductId(f.IdMinimum, f.IdMaximum); - query = query.OrderByDescending(x => x.CreatedOnUtc); + query = _catalogSearchService.Value.PrepareQuery(searchQuery); } 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 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(); + var result = new List(); + Multimap associatedProducts = null; - var products = GetProductQuery(ctx, skip, PageSize).ToList(); + 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 && !ctx.IsPreview) + if (ctx.Projection.NoGroupedProducts) { - var associatedSearchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.Position, - PageSize = int.MaxValue, - StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), - VisibleIndividuallyOnly = false, - ParentGroupedProductId = product.Id - }; - - foreach (var associatedProduct in _productService.Value.SearchProducts(associatedSearchContext)) - { - 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, ctx.Projection.CustomerId ?? 0, - ctx.Filter.CreatedFrom.HasValue ? (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.CurrentTimeZone) : null, - ctx.Filter.CreatedTo.HasValue ? (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.Value.CurrentTimeZone) : null, + ctx.Filter.CreatedFrom.HasValue ? (DateTime?)_services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _services.DateTimeHelper.CurrentTimeZone) : null, + ctx.Filter.CreatedTo.HasValue ? (DateTime?)_services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _services.DateTimeHelper.CurrentTimeZone) : null, ctx.Filter.OrderStatusIds, ctx.Filter.PaymentStatusIds, ctx.Filter.ShippingStatusIds, @@ -729,97 +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 showHidden = !ctx.Filter.IsPublished.HasValue; - var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); - - var query = _manufacturerService.Value.GetManufacturers(showHidden, storeId); + 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(); + 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; + } - SetProgress(ctx, manus.Count); + 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 showHidden = !ctx.Filter.IsPublished.HasValue; - var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); - - var query = _categoryService.Value.GetCategories(null, showHidden, null, true, storeId); + 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(); + 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; + } - SetProgress(ctx, categories.Count); + 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) @@ -829,30 +1189,40 @@ 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) { - var activityFrom = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.LastActivityFrom.Value, _dateTimeHelper.Value.CurrentTimeZone); + var activityFrom = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.LastActivityFrom.Value, _services.DateTimeHelper.CurrentTimeZone); query = query.Where(x => activityFrom <= x.LastActivityDateUtc); } if (ctx.Filter.LastActivityTo.HasValue) { - var activityTo = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.LastActivityTo.Value, _dateTimeHelper.Value.CurrentTimeZone); + var activityTo = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.LastActivityTo.Value, _services.DateTimeHelper.CurrentTimeZone); query = query.Where(x => activityTo >= x.LastActivityDateUtc); } @@ -884,289 +1254,540 @@ 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(); + 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; + } - SetProgress(ctx, customers.Count); + 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 (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.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) + if (ctx.Filter.CreatedFrom.HasValue) { - var createdFrom = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.CurrentTimeZone); + var createdFrom = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _services.DateTimeHelper.CurrentTimeZone); query = query.Where(x => createdFrom <= x.CreatedOnUtc); } if (ctx.Filter.CreatedTo.HasValue) { - var createdTo = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.Value.CurrentTimeZone); + var createdTo = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _services.DateTimeHelper.CurrentTimeZone); 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(); + 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; + } - SetProgress(ctx, subscriptions.Count); + ctx.LastId = subscriptions.Last().Id; + SetProgress(ctx, subscriptions.Count); return subscriptions; } - #endregion - - private List Init(DataExporterContext ctx, int? totalRecords = null) + private IQueryable GetShoppingCartItemQuery(DataExporterContext ctx, int? skip, int take) { - // Init base things that are even required for preview. Init all other things (regular export) in ExportCoreOuter. - List result = null; + var storeId = ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId; - if (ctx.Projection.CurrencyId.HasValue) - ctx.ContextCurrency = _currencyService.Value.GetCurrencyById(ctx.Projection.CurrencyId.Value); - else - ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; + var query = _shoppingCartItemRepository.Value.TableUntracked + .Expand(x => x.Customer) + .Expand(x => x.Customer.CustomerRoles) + .Expand(x => x.Product) + .Where(x => !x.Customer.Deleted); - if (ctx.Projection.CustomerId.HasValue) - ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - else - ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; + if (storeId > 0) + { + query = query.Where(x => x.StoreId == storeId); + } + + if (ctx.Request.ActionOrigin.IsCaseInsensitiveEqual("CurrentCarts")) + { + query = query.Where(x => x.ShoppingCartTypeId == (int)ShoppingCartType.ShoppingCart); + } + else if (ctx.Request.ActionOrigin.IsCaseInsensitiveEqual("CurrentWishlists")) + { + query = query.Where(x => x.ShoppingCartTypeId == (int)ShoppingCartType.Wishlist); + } + else if (ctx.Filter.ShoppingCartTypeId.HasValue) + { + 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.IsTaxExempt.HasValue) + { + query = query.Where(x => x.Customer.IsTaxExempt == ctx.Filter.IsTaxExempt.Value); + } + + 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) + { + var activityFrom = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.LastActivityFrom.Value, _services.DateTimeHelper.CurrentTimeZone); + query = query.Where(x => activityFrom <= x.Customer.LastActivityDateUtc); + } + + if (ctx.Filter.LastActivityTo.HasValue) + { + var activityTo = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.LastActivityTo.Value, _services.DateTimeHelper.CurrentTimeZone); + query = query.Where(x => activityTo >= x.Customer.LastActivityDateUtc); + } + + if (ctx.Filter.CreatedFrom.HasValue) + { + var createdFrom = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _services.DateTimeHelper.CurrentTimeZone); + query = query.Where(x => createdFrom <= x.CreatedOnUtc); + } + + if (ctx.Filter.CreatedTo.HasValue) + { + var createdTo = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _services.DateTimeHelper.CurrentTimeZone); + query = query.Where(x => createdTo >= x.CreatedOnUtc); + } - if (ctx.Projection.LanguageId.HasValue) - ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); + if (ctx.Projection.NoBundleProducts) + { + query = query.Where(x => x.Product.ProductTypeId != (int)ProductType.BundledProduct); + } else - ctx.ContextLanguage = _services.WorkContext.WorkingLanguage; + { + query = query.Where(x => x.BundleItemId == null); + } + + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } + + query = query.OrderBy(x => x.Id); + + 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 GetShoppingCartItems(DataExporterContext ctx) + { + 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) + { + List result = null; + + 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; - } - } - - 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.Information(logHead.ToString()); + 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); + 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) { - ctx.Log.Information("There are no records to export."); + 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.Information("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 - }); - } + FileName = context.FileName + }); + } } - ctx.EntityIdsPerSegment.Clear(); - - if (ctx.ExecuteContext.IsMaxFailures) - ctx.Log.Warning("Export aborted. The maximum number of failures has been reached."); - - if (ctx.CancellationToken.IsCancellationRequested) - ctx.Log.Warning("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); - CallProvider(ctx, x.Id, "OnExecuted", path); - }); - - 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)) @@ -1188,31 +1809,25 @@ 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) - { - var allCategories = _categoryService.Value.GetAllCategories(showHidden: true, applyNavigationFilters: false); - ctx.Categories = allCategories.ToDictionary(x => x.Id); - } - - if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Order) + 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); } @@ -1233,6 +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, profile); stores.ForEach(x => ExportCoreInner(ctx, x)); } @@ -1241,60 +1857,57 @@ 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.Information("Cleaning up export folder"); + logger.Info("Cleaning up export folder"); FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } } } - if (ctx.Request.Profile.EmailAccountId != 0 && ctx.Request.Profile.CompletedEmailAddresses.HasValue()) - { - SendCompletionEmail(ctx, zipPath); - } - else if (ctx.Request.Profile.IsSystemProfile && !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(); @@ -1303,9 +1916,9 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.Languages.Clear(); ctx.QuantityUnits.Clear(); ctx.DeliveryTimes.Clear(); - ctx.CategoryPathes.Clear(); - ctx.Categories.Clear(); ctx.Stores.Clear(); + ctx.Translations.Clear(); + ctx.UrlRecords.Clear(); ctx.Request.CustomData.Clear(); @@ -1313,17 +1926,19 @@ 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 + // Post process order entities. if (ctx.EntityIdsLoaded.Any() && ctx.Request.Provider.Value.EntityType == ExportEntityType.Order && ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) { using (var logger = new TraceLogger(logPath)) @@ -1332,14 +1947,18 @@ 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)) { - foreach (var chunk in ctx.EntityIdsLoaded.Chunk()) + foreach (var chunk in ctx.EntityIdsLoaded.Slice(128)) { var entities = _orderRepository.Value.Table.Where(x => chunk.Contains(x.Id)).ToList(); @@ -1349,12 +1968,12 @@ private void ExportCoreOuter(DataExporterContext ctx) } } - logger.Information("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); } } } @@ -1363,78 +1982,83 @@ private void ExportCoreOuter(DataExporterContext ctx) /// /// The name of the public export folder /// - public static string PublicFolder - { - get { return "Exchange"; } - } + public static string PublicFolder => "Exchange"; - public static int PageSize - { - get { return 100; } - } + public static int PageSize => 100; public DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken) { 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 resultData = 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 unused = Init(ctx); + var skip = Math.Max(ctx.Request.Profile.Offset, 0) + (pageIndex * PageSize); if (!HasPermission(ctx)) - throw new SmartException(T("Admin.AccessDenied")); - - using (var segmenter = CreateSegmenter(ctx, pageIndex)) { - if (segmenter == null) - { - throw new SmartException(T("Admin.Common.UnsupportedEntityType", ctx.Request.Provider.Value.EntityType.ToString())); - } + throw new SmartException(T("Admin.AccessDenied")); + } - while (segmenter.HasData) - { - segmenter.RecordPerSegmentCount = 0; + result.TotalRecords = ctx.StatsPerStore.First().Value.TotalRecords; - while (segmenter.ReadNextSegment()) + switch (request.Provider.Value.EntityType) + { + case ExportEntityType.Product: { - resultData.AddRange(segmenter.CurrentSegment); + var items = GetProductQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } - } - - DetachAllEntitiesAndClear(ctx); - } - - if (ctx.Result.LastError.HasValue()) - { - _services.Notifier.Error(ctx.Result.LastError); + break; + case ExportEntityType.Order: + { + var items = GetOrderQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); + } + break; + case ExportEntityType.Category: + { + var items = GetCategoryQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); + } + break; + case ExportEntityType.Manufacturer: + { + var items = GetManufacturerQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); + } + break; + case ExportEntityType.Customer: + { + var items = GetCustomerQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); + } + break; + case ExportEntityType.NewsLetterSubscription: + { + var items = GetNewsLetterSubscriptionQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); + } + break; + case ExportEntityType.ShoppingCartItem: + { + var items = GetShoppingCartItemQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); + } + break; } - return resultData; - } - - 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 298d476c41..3415948ae2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Email; using SmartStore.Core.IO; @@ -33,11 +34,11 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de { var queuedEmail = new QueuedEmail { - From = emailAccount.Email, - FromName = emailAccount.DisplayName, + From = emailAccount.ToEmailAddress(), SendManually = false, To = email, Subject = deployment.EmailSubject.NaIfEmpty(), + Body = deployment.EmailSubject.NaIfEmpty(), CreatedOnUtc = DateTime.UtcNow, EmailAccountId = deployment.EmailAccountId }; @@ -49,7 +50,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de queuedEmail.Attachments.Add(new QueuedEmailAttachment { StorageLocation = EmailAttachmentStorageLocation.Blob, - Data = File.ReadAllBytes(path), + MediaStorage = new MediaStorage { Data = File.ReadAllBytes(path) }, Name = name, MimeType = MimeTypes.MapNameToMimeType(name) }); @@ -59,7 +60,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de ++count; } - context.Log.Information("{0} email(s) created and queued for deployment.".FormatInvariant(count)); + context.Log.Info("{0} email(s) created and queued for deployment.".FormatInvariant(count)); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 6bb56682e1..346cf02176 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -11,12 +11,15 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de { var targetFolder = deployment.GetDeploymentFolder(true); + if (targetFolder.IsEmpty()) + return; + if (!FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(targetFolder))) { context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); } - context.Log.Information("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/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs index 7a0954a1c7..64bf43b067 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Net; using SmartStore.Core.Domain; using SmartStore.Core.Logging; @@ -9,66 +8,168 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FtpFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + private ExportDeploymentContext _context; + private ExportDeployment _deployment; + private int _succeededFiles; + private string _ftpRootUrl; + + private string GetRelativePath(string path) { + var relativePath = path.Substring(_context.FolderContent.Length).Replace("\\", "/"); + if (relativePath.StartsWith("/")) + { + return relativePath.Substring(1); + } + + return relativePath; + } + + private FtpWebRequest CreateRequest(string url, bool keepAlive = true, long? contentLength = null) + { + var request = (FtpWebRequest)WebRequest.Create(url); + request.Method = WebRequestMethods.Ftp.UploadFile; + request.KeepAlive = keepAlive; + request.UseBinary = true; + request.Proxy = null; + request.UsePassive = _deployment.PassiveMode; + request.EnableSsl = _deployment.UseSsl; + + if (_deployment.Username.HasValue()) + { + request.Credentials = new NetworkCredential(_deployment.Username, _deployment.Password); + } + + if (contentLength.HasValue) + { + request.ContentLength = contentLength.Value; + } + + return request; + } + + private bool UploadFile(string path, string fileUrl, bool keepAlive = true) + { + var succeeded = false; var bytesRead = 0; - var succeededFiles = 0; - var url = deployment.Url; var buffLength = 32768; byte[] buff = new byte[buffLength]; - var deploymentFiles = context.GetDeploymentFiles().ToList(); - var lastIndex = (deploymentFiles.Count - 1); - if (!url.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase)) - url = "ftp://" + url; + var request = CreateRequest(fileUrl, keepAlive, (new FileInfo(path)).Length); + request.Method = WebRequestMethods.Ftp.UploadFile; + + var requestStream = request.GetRequestStream(); + using (var stream = new FileStream(path, FileMode.Open)) + { + while ((bytesRead = stream.Read(buff, 0, buffLength)) != 0) + { + requestStream.Write(buff, 0, bytesRead); + } + } + + requestStream.Close(); - foreach (var path in deploymentFiles) + using (var response = (FtpWebResponse)request.GetResponse()) { - var fileUrl = url.EnsureEndsWith("/") + Path.GetFileName(path); + var statusCode = (int)response.StatusCode; + succeeded = statusCode >= 200 && statusCode <= 299; - var request = (FtpWebRequest)WebRequest.Create(fileUrl); - request.Method = WebRequestMethods.Ftp.UploadFile; - request.KeepAlive = (deploymentFiles.IndexOf(path) != lastIndex); - request.UseBinary = true; - request.Proxy = null; - request.UsePassive = deployment.PassiveMode; - request.EnableSsl = deployment.UseSsl; + if (succeeded) + { + ++_succeededFiles; + } + else + { + _context.Result.LastError = _context.T("Admin.Common.FtpStatus", statusCode, response.StatusCode.ToString()); + _context.Log.Error("The FTP transfer failed. FTP status {0} ({1}). File {3}".FormatInvariant(statusCode, response.StatusCode.ToString(), path)); + } + } - if (deployment.Username.HasValue()) - request.Credentials = new NetworkCredential(deployment.Username, deployment.Password); + return succeeded; + } - request.ContentLength = (new FileInfo(path)).Length; + private bool DirectoryExists(string directoryUrl) + { + var result = false; - var requestStream = request.GetRequestStream(); + try + { + var request = CreateRequest(directoryUrl.EnsureEndsWith("/")); + request.Method = WebRequestMethods.Ftp.ListDirectory; - using (var stream = new FileStream(path, FileMode.Open)) + using (request.GetResponse()) { - while ((bytesRead = stream.Read(buff, 0, buffLength)) != 0) - { - requestStream.Write(buff, 0, bytesRead); - } + result = true; } + } + catch (WebException) + { + result = false; + } - requestStream.Close(); + return result; + } - using (var response = (FtpWebResponse)request.GetResponse()) - { - var statusCode = (int)response.StatusCode; + private bool FtpCopyDirectory(DirectoryInfo source) + { + var files = source.GetFiles(); + var len = files.Length; + + for (var i = 0; i < len; ++i) + { + var path = files[i].FullName; + var relativePath = GetRelativePath(path); + + UploadFile(path, _ftpRootUrl + relativePath, i != (len - 1)); + } + + foreach (var sourceSubDir in source.GetDirectories()) + { + var relativePath = GetRelativePath(sourceSubDir.FullName); - if (statusCode >= 200 && statusCode <= 299) + if (!DirectoryExists(_ftpRootUrl + relativePath)) + { + var request = CreateRequest(_ftpRootUrl + relativePath, true); + request.Method = WebRequestMethods.Ftp.MakeDirectory; + using (var response = (FtpWebResponse)request.GetResponse()) { - ++succeededFiles; + response.Close(); } - else - { - context.Result.LastError = context.T("Admin.Common.FtpStatus", statusCode, response.StatusCode.ToString()); + } - context.Log.Error("The FTP transfer failed. FTP status {0} ({1}). File {3}".FormatInvariant(statusCode, response.StatusCode.ToString(), path)); - } + FtpCopyDirectory(sourceSubDir); + } + + return true; + } + + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + { + _context = context; + _deployment = deployment; + _succeededFiles = 0; + _ftpRootUrl = deployment.Url; + + if (!_ftpRootUrl.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase)) + { + _ftpRootUrl = "ftp://" + _ftpRootUrl; + } + + _ftpRootUrl = _ftpRootUrl.EnsureEndsWith("/"); + + if (context.CreateZipArchive) + { + if (File.Exists(context.ZipPath)) + { + var fileUrl = _ftpRootUrl + Path.GetFileName(context.ZipPath); + UploadFile(context.ZipPath, fileUrl, false); } } + else + { + FtpCopyDirectory(new DirectoryInfo(context.FolderContent)); + } - context.Log.Information("{0} file(s) successfully uploaded via FTP.".FormatInvariant(succeededFiles)); + context.Log.Info("{0} file(s) successfully uploaded via FTP.".FormatInvariant(_succeededFiles)); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs index 4f0d9df8d8..9bcdf2f5fd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -71,7 +71,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } } - context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeededFiles, deployment.HttpTransmissionType.ToString())); + context.Log.Info("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeededFiles, deployment.HttpTransmissionType.ToString())); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index d438e808a2..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.Information("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.Information("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 e925553e9f..8c89087b72 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -5,8 +5,10 @@ using System.Linq.Expressions; using System.Reflection; using System.Web; +using SmartStore.Collections; using SmartStore.ComponentModel; using SmartStore.Core; +using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; @@ -20,28 +22,42 @@ 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.VatNumber, + SystemCustomerAttributeNames.VatNumberStatusId, + SystemCustomerAttributeNames.TimeZoneId, + SystemCustomerAttributeNames.ImpersonatedCustomerId + }; + private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObject, Product product) { 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) { @@ -71,39 +87,40 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndDescription) { - var productManus = ctx.ProductExportContext.ProductManufacturers.Load(product.Id); + 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); @@ -125,7 +142,8 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec if (ctx.Projection.ConvertNetToGrossPrices) { decimal taxRate; - price = _taxService.Value.GetProductPrice(product, price.Value, true, ctx.ContextCustomer, out taxRate); + price = _taxService.Value.GetProductPrice(product, product.TaxCategoryId, price.Value, true, ctx.ContextCustomer, ctx.ContextCurrency, + _taxSettings.Value.PricesIncludeTax, out taxRate); } if (price != decimal.Zero) @@ -133,107 +151,170 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec price = _currencyService.Value.ConvertFromPrimaryStoreCurrency(price.Value, ctx.ContextCurrency, ctx.Store); } } + return price; } - private decimal CalculatePrice(DataExporterContext ctx, Product product, bool forAttributeCombination) + private decimal CalculatePrice( + DataExporterContext ctx, + Product product, + ProductVariantAttributeCombination combination, + ICollection attributeValues) { - decimal price = product.Price; + var price = product.Price; + var productContext = ctx.ProductExportContext as PriceCalculationContext; + var associatedProductContext = ctx.AssociatedProductContext as PriceCalculationContext; - // price type - if (ctx.Projection.PriceType.HasValue && !forAttributeCombination) + if (combination != null) { - var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; + // price for attribute combination + var attributesTotalPriceBase = decimal.Zero; - if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) + if (attributeValues != null) { - bool displayFromMessage; - price = _priceCalculationService.Value.GetLowestPrice(product, priceCalculationContext, out displayFromMessage); - } - else if (ctx.Projection.PriceType.Value == PriceDisplayType.PreSelectedPrice) - { - price = _priceCalculationService.Value.GetPreselectedPrice(product, priceCalculationContext); - } - else if (ctx.Projection.PriceType.Value == PriceDisplayType.PriceWithoutDiscountsAndAttributes) - { - price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, 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, productContext); + } + else if (ctx.Projection.PriceType.HasValue) + { + 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 + 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; + } + + var localized = new List(); + var localeKeyGroup = entity.GetEntityName(); + //var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); + + foreach (var language in ctx.Languages) + { + var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); + + // 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); + } + } + + // 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.Any() ? localized : null; + } + + private dynamic ToDynamic(DataExporterContext ctx, ExportProfile profile) { - if (ctx.Languages.Count <= 1) + if (profile == null) 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; - - localized.Add(exp); - } - } - - 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); - - // 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; - - localized.Add(exp); - } - } - } - - return (localized.Count == 0 ? null : localized); + dynamic result = new DynamicEntity(profile); + return result; } 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; @@ -241,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; } @@ -280,25 +367,27 @@ 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); result.BillingAddress = null; result.ShippingAddress = null; result.Addresses = null; - result.CustomerRoles = null; result.RewardPointsHistory = null; result._RewardPointsBalance = 0; @@ -309,13 +398,23 @@ private dynamic ToDynamic(DataExporterContext ctx, Customer customer) result._FullName = null; result._AvatarPictureUrl = null; + result.CustomerRoles = customer.CustomerRoles + .Select(x => + { + dynamic dyn = new DynamicEntity(x); + return dyn; + }) + .ToList(); + return result; } private dynamic ToDynamic(DataExporterContext ctx, Store store) { - if (store == null) - return null; + if (store == null) + { + return null; + } dynamic result = new DynamicEntity(store); @@ -327,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; } @@ -342,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; @@ -370,62 +476,72 @@ 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 relativeUrl = _pictureService.Value.GetPictureUrl(picture, 0, false); - - result._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); - result._RelativeUrl = relativeUrl; - result._ThumbImageUrl = _pictureService.Value.GetPictureUrl(picture, thumbPictureSize, false, ctx.Store.Url); - result._ImageUrl = _pictureService.Value.GetPictureUrl(picture, detailsPictureSize, false, ctx.Store.Url); - result._FullSizeImageUrl = _pictureService.Value.GetPictureUrl(picture, 0, false, ctx.Store.Url); + var pictureInfo = _pictureService.Value.GetPictureInfo(picture); + var host = _services.StoreService.GetHost(ctx.Store); - result._ThumbLocalPath = _pictureService.Value.GetThumbLocalPath(picture); + if (pictureInfo != null) + { + result._FileName = System.IO.Path.GetFileName(pictureInfo.Path); + result._RelativeUrl = _pictureService.Value.GetUrl(pictureInfo, 0, FallbackPictureType.NoFallback); + 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); + } return result; } 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; } @@ -445,82 +561,92 @@ 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.Name = manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - 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.Picture = null; + result.Picture = null; + result.Name = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.Name)) ?? manufacturer.Name; - result._Localized = GetLocalized(ctx, manufacturer, - x => x.Name, - x => x.Description, - x => x.MetaKeywords, - x => x.MetaDescription, - x => x.MetaTitle); + if (!ctx.IsPreview) + { + 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, translations, urlRecords, manufacturer, + x => x.Name, + x => x.Description, + x => x.MetaKeywords, + x => x.MetaDescription, + x => x.MetaTitle); + } return result; } 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.Name = category.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result.SeName = category.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); - result.FullName = category.GetLocalized(x => x.FullName, 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.Picture = null; - - if (ctx.CategoryTemplates.ContainsKey(category.CategoryTemplateId)) - result._CategoryTemplateViewPath = ctx.CategoryTemplates[category.CategoryTemplateId]; - else - result._CategoryTemplateViewPath = ""; + 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; - result._Localized = GetLocalized(ctx, category, - x => x.Name, - x => x.FullName, - x => x.Description, - x => x.BottomDescription, - x => x.MetaKeywords, - x => x.MetaDescription, - x => x.MetaTitle); + if (!ctx.IsPreview) + { + 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, translations, urlRecords, category, + x => x.Name, + x => x.FullName, + x => x.Description, + x => x.BottomDescription, + x => x.MetaKeywords, + x => x.MetaDescription, + x => x.MetaTitle); + } return result; } - private dynamic ToDynamic(DataExporterContext ctx, Product product) + 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.Name = product.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result.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); - 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.AppliedDiscounts = null; + result.AppliedDiscounts = null; + result.Downloads = null; result.TierPrices = null; result.ProductAttributes = null; result.ProductAttributeCombinations = null; @@ -530,121 +656,167 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product) result.ProductTags = null; result.ProductSpecificationAttributes = null; result.ProductBundleItems = null; + result.Name = localizedName; - result._Localized = GetLocalized(ctx, product, - x => x.Name, - x => x.ShortDescription, - x => x.FullDescription, - x => x.MetaKeywords, - x => x.MetaDescription, - x => x.MetaTitle, - x => x.BundleTitleText); + if (!ctx.IsPreview) + { + 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); + + basePriceInfo = priceTemplate.FormatInvariant(formattedPrice, product.BasePriceBaseAmount, product.BasePriceMeasureUnit); + } + + result._BasePriceInfo = basePriceInfo; + + ToDeliveryTime(ctx, result, product.DeliveryTimeId); + ToQuantityUnit(ctx, result, product.QuantityUnitId); + + result._Localized = GetLocalized(ctx, translations, urlRecords, product, + x => x.Name, + x => x.ShortDescription, + x => x.FullDescription, + x => x.MetaKeywords, + x => x.MetaDescription, + x => x.MetaTitle, + x => x.BundleTitleText); + } return result; } - private dynamic ToDynamic( - DataExporterContext ctx, - Product product, - ICollection combinations, - ProductVariantAttributeCombination combination) + private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParent, DynamicProductContext productContext) { - product.MergeWithCombination(combination); - - var languageId = (ctx.Projection.LanguageId ?? 0); - var pictureSize = _mediaSettings.Value.ProductDetailsPictureSize; - var numberOfPictures = (ctx.Projection.NumberOfPictures ?? int.MaxValue); - int[] pictureIds = (combination == null ? new int[0] : combination.GetAssignedPictureIds()); - - if (ctx.Supports(ExportFeatures.CanIncludeMainPicture) && ctx.Projection.PictureSize > 0) - pictureSize = ctx.Projection.PictureSize; - - var perfLoadId = (ctx.IsPreview ? 0 : product.Id); // perf preview (it's a compromise) - IEnumerable productPictures = ctx.ProductExportContext.ProductPictures.Load(perfLoadId); - var productManufacturers = ctx.ProductExportContext.ProductManufacturers.Load(perfLoadId); - var productCategories = ctx.ProductExportContext.ProductCategories.Load(product.Id); - var productAttributes = ctx.ProductExportContext.Attributes.Load(product.Id); - var productTags = ctx.ProductExportContext.ProductTags.Load(product.Id); - var specificationAttributes = ctx.ProductExportContext.ProductSpecificationAttributes.Load(product.Id); + product.MergeWithCombination(productContext.Combination); + + var numberOfPictures = ctx.Projection.NumberOfPictures ?? int.MaxValue; + var productDetailsPictureSize = ctx.Projection.PictureSize > 0 ? ctx.Projection.PictureSize : _mediaSettings.Value.ProductDetailsPictureSize; + + IEnumerable productPictures = ctx.ProductExportContext.ProductPictures.GetOrLoad(product.Id); + var productManufacturers = ctx.ProductExportContext.ProductManufacturers.GetOrLoad(product.Id); + var productCategories = ctx.ProductExportContext.ProductCategories.GetOrLoad(product.Id); + var productAttributes = ctx.ProductExportContext.Attributes.GetOrLoad(product.Id); + var productTags = ctx.ProductExportContext.ProductTags.GetOrLoad(product.Id); + var specificationAttributes = ctx.ProductExportContext.SpecificationAttributes.GetOrLoad(product.Id); + Multimap variantAttributes = null; + ICollection variantAttributeValues = null; + string attributesXml = null; + + dynamic dynObject = ToDynamic(ctx, product, productContext.SeName); + dynObject._IsParent = isParent; + dynObject._CategoryName = null; + dynObject._CategoryPath = null; + dynObject._AttributeCombination = null; + dynObject._AttributeCombinationValues = null; + dynObject._AttributeCombinationId = 0; + + if (productContext.Combination != null) + { + var pictureIds = productContext.Combination.GetAssignedPictureIds(); + if (pictureIds.Any()) + { + productPictures = productPictures.Where(x => pictureIds.Contains(x.PictureId)); + } - if (pictureIds.Length > 0) + attributesXml = productContext.Combination.AttributesXml; + variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(attributesXml); + variantAttributeValues = _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes); + + dynObject._AttributeCombinationId = productContext.Combination.Id; + dynObject._UniqueId = string.Concat(product.Id, "-", productContext.Combination.Id); + + if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) + { + dynObject._AttributeCombination = variantAttributes; + dynObject._AttributeCombinationValues = variantAttributeValues; + } + + if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var translations = ctx.TranslationsPerPage[nameof(ProductVariantAttributeValue)]; + var valueNames = variantAttributeValues + .Select(x => translations.GetValue(ctx.LanguageId, x.Id, nameof(x.Name)) ?? x.Name) + .ToList(); + + dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", valueNames), " "); + } + } + else + { + dynObject._UniqueId = product.Id.ToString(); + } + + productPictures = productPictures.Take(numberOfPictures); + + #region Gerneral data + + if (attributesXml.HasValue()) + { + var query = new ProductVariantQuery(); + _productUrlHelper.Value.DeserializeQuery(query, product.Id, attributesXml, 0, productAttributes); + + dynObject._DetailUrl = productContext.AbsoluteProductUrl + _productUrlHelper.Value.ToQueryString(query); + } + else + { + dynObject._DetailUrl = productContext.AbsoluteProductUrl; + } + + dynObject.Price = CalculatePrice(ctx, product, productContext.Combination, variantAttributeValues); + + // Category path { - productPictures = productPictures.Where(x => pictureIds.Contains(x.PictureId)); - } - - productPictures = productPictures.Take(numberOfPictures); - - dynamic dynObject = ToDynamic(ctx, product); - - #region gerneral data - - dynObject._CategoryName = null; - dynObject._CategoryPath = null; - dynObject._AttributeCombination = null; - dynObject._AttributeCombinationValues = null; - dynObject._AttributeCombinationId = (combination == null ? 0 : combination.Id); - dynObject._DetailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; + var categoryPath = string.Empty; + var pc = productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault(); - dynObject.Price = CalculatePrice(ctx, product, combination != null); - - dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, - _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); - - if (ctx.ProductTemplates.ContainsKey(product.ProductTemplateId)) - dynObject._ProductTemplateViewPath = ctx.ProductTemplates[product.ProductTemplateId]; - else - dynObject._ProductTemplateViewPath = ""; - - if (combination != null) - { - if (ctx.Supports(ExportFeatures.UsesAttributeCombination) || - ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + if (pc != null) { - var variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml); - var variantAttributeValues = _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes, languageId); - - if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) + var node = _categoryService.Value.GetCategoryTree(pc.CategoryId, true, ctx.Store.Id); + if (node != null) { - dynObject._AttributeCombination = variantAttributes; - dynObject._AttributeCombinationValues = variantAttributeValues; - } - - if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) - { - dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", variantAttributeValues), " "); + categoryPath = _categoryService.Value.GetCategoryPath(node, ctx.Projection.LanguageId, null, " > "); } } - var attributeQueryString = _productAttributeParser.Value.SerializeQueryData(combination.AttributesXml, product.Id); - if (attributeQueryString.HasValue()) - { - var url = (string)dynObject._DetailUrl; - dynObject._DetailUrl = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", attributeQueryString); - } + dynObject._CategoryPath = categoryPath; } - if (ctx.Categories.Count > 0) + if (ctx.Countries != null) { - dynObject._CategoryPath = _categoryService.Value.GetCategoryPath( - product, - null, - x => ctx.CategoryPathes.ContainsKey(x) ? ctx.CategoryPathes[x] : null, - (id, value) => ctx.CategoryPathes[id] = value, - x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.Value.GetCategoryById(x), - productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault() - ); + dynObject.CountryOfOrigin = product.CountryOfOriginId.HasValue && ctx.Countries.ContainsKey(product.CountryOfOriginId.Value) + ? ToDynamic(ctx, ctx.Countries[product.CountryOfOriginId.Value]) + : null; } - ToDeliveryTime(ctx, dynObject, product.DeliveryTimeId); - ToQuantityUnit(ctx, dynObject, product.QuantityUnitId); - dynObject.ProductPictures = productPictures .OrderBy(x => x.DisplayOrder) .Select(x => { dynamic dyn = new DynamicEntity(x); - dyn.Picture = ToDynamic(ctx, x.Picture, _mediaSettings.Value.ProductThumbPictureSize, pictureSize); + dyn.Picture = ToDynamic(ctx, x.Picture, _mediaSettings.Value.ProductThumbPictureSize, productDetailsPictureSize); return dyn; }) @@ -658,10 +830,9 @@ private dynamic ToDynamic( dyn.Manufacturer = ToDynamic(ctx, x.Manufacturer); - if (x.Manufacturer != null && x.Manufacturer.PictureId.HasValue) - dyn.Manufacturer.Picture = ToDynamic(ctx, x.Manufacturer.Picture, _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize); - else - dyn.Manufacturer.Picture = null; + dyn.Manufacturer.Picture = x.Manufacturer != null && x.Manufacturer.PictureId.HasValue + ? ToDynamic(ctx, x.Manufacturer.Picture, _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize) + : null; return dyn; }) @@ -690,37 +861,44 @@ private dynamic ToDynamic( .Select(x => ToDynamic(ctx, x)) .ToList(); - dynObject.ProductAttributeCombinations = (combinations ?? Enumerable.Empty()) - .Select(x => - { - dynamic dyn = ToDynamic(ctx, x); - var assignedPictures = new List(); - - foreach (int pictureId in x.GetAssignedPictureIds().Take(numberOfPictures)) + // Do not export combinations if a combination is exported as a product. + if (productContext.Combinations != null && productContext.Combination == null) + { + dynObject.ProductAttributeCombinations = productContext.Combinations + .Select(x => { - var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); - if (assignedPicture != null && assignedPicture.Picture != null) + dynamic dyn = ToDynamic(ctx, x); + var assignedPictures = new List(); + + foreach (int pictureId in x.GetAssignedPictureIds().Take(numberOfPictures)) { - assignedPictures.Add(ToDynamic(ctx, assignedPicture.Picture, _mediaSettings.Value.ProductThumbPictureSize, pictureSize)); + var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); + if (assignedPicture != null && assignedPicture.Picture != null) + { + assignedPictures.Add(ToDynamic(ctx, assignedPicture.Picture, _mediaSettings.Value.ProductThumbPictureSize, productDetailsPictureSize)); + } } - } - dyn.Pictures = assignedPictures; + dyn.Pictures = assignedPictures; - return dyn; - }) - .ToList(); + return dyn; + }) + .ToList(); + } + else + { + dynObject.ProductAttributeCombinations = Enumerable.Empty(); + } if (product.HasTierPrices) { - var tierPrices = ctx.ProductExportContext.TierPrices.Load(product.Id) + var tierPrices = ctx.ProductExportContext.TierPrices.GetOrLoad(product.Id) .RemoveDuplicatedQuantities(); dynObject.TierPrices = tierPrices .Select(x => { dynamic dyn = new DynamicEntity(x); - return dyn; }) .ToList(); @@ -728,21 +906,32 @@ private dynamic ToDynamic( if (product.HasDiscountsApplied) { - var appliedDiscounts = ctx.ProductExportContext.AppliedDiscounts.Load(product.Id); + var appliedDiscounts = ctx.ProductExportContext.AppliedDiscounts.GetOrLoad(product.Id); dynObject.AppliedDiscounts = appliedDiscounts .Select(x => ToDynamic(ctx, x)) .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; }) @@ -754,16 +943,17 @@ private dynamic ToDynamic( if (product.ProductType == ProductType.BundledProduct) { - var bundleItems = ctx.ProductExportContext.ProductBundleItems.Load(perfLoadId); + var bundleItems = ctx.ProductExportContext.ProductBundleItems.GetOrLoad(product.Id); dynObject.ProductBundleItems = bundleItems .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; }) @@ -772,7 +962,7 @@ private dynamic ToDynamic( #endregion - #region more attribute controlled data + #region More data based on export features if (ctx.Supports(ExportFeatures.CanProjectDescription)) { @@ -782,29 +972,34 @@ private dynamic ToDynamic( if (ctx.Supports(ExportFeatures.OffersBrandFallback)) { string brand = null; - var productManus = ctx.ProductExportContext.ProductManufacturers.Load(perfLoadId); - - if (productManus != null && productManus.Any()) - brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); - - if (brand.IsEmpty()) - brand = ctx.Projection.Brand; + var productManus = ctx.ProductExportContext.ProductManufacturers.GetOrLoad(product.Id); + + 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; } - + if (ctx.Supports(ExportFeatures.CanIncludeMainPicture)) { if (productPictures != null && productPictures.Any()) { var firstPicture = productPictures.First().Picture; - dynObject._MainPictureUrl = _pictureService.Value.GetPictureUrl(firstPicture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); - dynObject._MainPictureRelativeUrl = _pictureService.Value.GetPictureUrl(firstPicture, ctx.Projection.PictureSize); + dynObject._MainPictureUrl = _pictureService.Value.GetUrl(firstPicture, ctx.Projection.PictureSize, host: _services.StoreService.GetHost(ctx.Store)); + dynObject._MainPictureRelativeUrl = _pictureService.Value.GetUrl(firstPicture, ctx.Projection.PictureSize); } else if (!_catalogSettings.Value.HideProductDefaultPictures) { - dynObject._MainPictureUrl = _pictureService.Value.GetDefaultPictureUrl(ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); - dynObject._MainPictureRelativeUrl = _pictureService.Value.GetDefaultPictureUrl(ctx.Projection.PictureSize); + dynObject._MainPictureUrl = _pictureService.Value.GetFallbackUrl(ctx.Projection.PictureSize, host: _services.StoreService.GetHost(ctx.Store)); + dynObject._MainPictureRelativeUrl = _pictureService.Value.GetFallbackUrl(ctx.Projection.PictureSize); } else { @@ -821,7 +1016,7 @@ private dynamic ToDynamic( 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)) @@ -841,7 +1036,8 @@ private dynamic ToDynamic( if (ctx.Projection.ConvertNetToGrossPrices) { decimal taxRate; - dynObject._OldPrice = _taxService.Value.GetProductPrice(product, product.OldPrice, true, ctx.ContextCustomer, out taxRate); + dynObject._OldPrice = _taxService.Value.GetProductPrice(product, product.TaxCategoryId, product.OldPrice, true, ctx.ContextCustomer, + ctx.ContextCurrency, _taxSettings.Value.PricesIncludeTax, out taxRate); } else { @@ -879,7 +1075,9 @@ private dynamic ToDynamic( { decimal tmpSpecialPrice = product.SpecialPrice.Value; product.SpecialPrice = null; - dynObject._RegularPrice = CalculatePrice(ctx, product, combination != null); + + dynObject._RegularPrice = CalculatePrice(ctx, product, productContext.Combination, variantAttributeValues); + product.SpecialPrice = tmpSpecialPrice; } } @@ -892,34 +1090,45 @@ private dynamic ToDynamic( 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; result.ShippingAddress = null; - result.Store = null; result.Shipments = null; - result.RedeemedRewardPointsEntry = ToDynamic(ctx, order.RedeemedRewardPointsEntry); + result.Store = ctx.Stores.ContainsKey(order.StoreId) + ? ToDynamic(ctx, ctx.Stores[order.StoreId]) + : null; + + if (!ctx.IsPreview) + { + result.RedeemedRewardPointsEntry = ToDynamic(ctx, order.RedeemedRewardPointsEntry); + } return result; } 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; @@ -927,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); @@ -936,7 +1147,6 @@ private dynamic ToDynamic(DataExporterContext ctx, Shipment shipment) .Select(x => { dynamic exp = new DynamicEntity(x); - return exp; }) .ToList(); @@ -946,35 +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) + private dynamic ToDynamic(DataExporterContext ctx, Download download) + { + if (download == null) + { + return null; + } + + dynamic result = new DynamicEntity(download); + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) { - if (psa == null) - return null; + if (psa == null) + { + return null; + } - var option = psa.SpecificationAttributeOption; + var option = psa.SpecificationAttributeOption; + var attribute = option.SpecificationAttribute; - dynamic result = new DynamicEntity(psa); + dynamic result = new DynamicEntity(psa); + dynamic dynAttribute = new DynamicEntity(attribute); + var saTranslations = ctx.TranslationsPerPage[nameof(SpecificationAttribute)]; + var saoTranslations = ctx.TranslationsPerPage[nameof(SpecificationAttributeOption)]; - dynamic dynAttribute = new DynamicEntity(option.SpecificationAttribute); + dynAttribute.Name = saTranslations.GetValue(ctx.LanguageId, attribute.Id, nameof(attribute.Name)) ?? attribute.Name; + dynAttribute._Localized = GetLocalized(ctx, saTranslations, null, attribute, x => x.Name); - dynAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - dynAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Name); + dynAttribute.Alias = saTranslations.GetValue(ctx.LanguageId, attribute.Id, nameof(attribute.Alias)) ?? attribute.Alias; + dynAttribute._Localized = GetLocalized(ctx, saTranslations, null, attribute, x => x.Alias); - dynamic dynOption = new DynamicEntity(option); + dynamic dynOption = new DynamicEntity(option); - dynOption.Name = option.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - dynOption._Localized = GetLocalized(ctx, option, x => x.Name); + 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; @@ -982,57 +1213,98 @@ 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); + result.Store = ctx.Stores.ContainsKey(subscription.StoreId) + ? ToDynamic(ctx, ctx.Stores[subscription.StoreId]) + : null; + + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ShoppingCartItem shoppingCartItem) + { + if (shoppingCartItem == null) + { + return null; + } + + dynamic result = new DynamicEntity(shoppingCartItem); + + shoppingCartItem.Product.MergeWithCombination(shoppingCartItem.AttributesXml, _productAttributeParser.Value); + + result.Store = ctx.Stores.ContainsKey(shoppingCartItem.StoreId) + ? ToDynamic(ctx, ctx.Stores[shoppingCartItem.StoreId]) + : null; + + result.Customer = ToDynamic(ctx, shoppingCartItem.Customer); + result.Product = ToDynamic(ctx, shoppingCartItem.Product); + return result; } private List Convert(DataExporterContext ctx, Product product) { - var result = new List(); + var result = new List(); + var productContext = new DynamicProductContext(); - var combinations = ctx.ProductExportContext.AttributeCombinations.Load(product.Id); + productContext.SeName = ctx.UrlRecordsPerPage[nameof(Product)].GetSlug(ctx.LanguageId, product.Id); + productContext.Combinations = ctx.ProductExportContext.AttributeCombinations.GetOrLoad(product.Id); - if (!ctx.IsPreview && ctx.Projection.AttributeCombinationAsProduct && combinations.Where(x => x.IsActive).Count() > 0) + productContext.AbsoluteProductUrl = _productUrlHelper.Value.GetAbsoluteProductUrl( + product.Id, + productContext.SeName, + null, + ctx.Store, + ctx.ContextLanguage); + + if (ctx.Projection.AttributeCombinationAsProduct && productContext.Combinations.Where(x => x.IsActive).Any()) { - //var productType = typeof(Product); - //var productValues = new Dictionary(); - var dbContext = _dbContext as DbContext; + if (ctx.Supports(ExportFeatures.UsesAttributeCombinationParent)) + { + var dynObject = ToDynamic(ctx, product, true, productContext); + result.Add(dynObject); + } - product = _dbContext.Attach(product); + var dbContext = _dbContext as DbContext; - var entry = dbContext.Entry(product); + foreach (var combination in productContext.Combinations.Where(x => x.IsActive)) + { + product = _dbContext.Attach(product); + var entry = dbContext.Entry(product); - // the returned object is not the entity and is not being tracked by the context. - // it also does not have any relationships set to other objects. - // CurrentValues only includes database (thus primitive) values. - var productClone = entry.CurrentValues.ToObject() as Product; + // The returned object is not the entity and is not being tracked by the context. + // It also does not have any relationships set to other objects. + // CurrentValues only includes database (thus primitive) values. + var productClone = entry.CurrentValues.ToObject() as Product; + _dbContext.DetachEntity(product); - _dbContext.DetachEntity(product); + productContext.Combination = combination; - foreach (var combination in combinations.Where(x => x.IsActive)) - { - var dynObject = ToDynamic(ctx, productClone, combinations, combination); + var dynObject = ToDynamic(ctx, productClone, false, productContext); result.Add(dynObject); } } else { - var dynObject = ToDynamic(ctx, product, combinations, null); + var dynObject = ToDynamic(ctx, product, false, productContext); result.Add(dynObject); } @@ -1054,24 +1326,25 @@ private List Convert(DataExporterContext ctx, Order order) { var result = new List(); - ctx.OrderExportContext.Addresses.Collect(order.ShippingAddressId ?? 0); + ctx.OrderExportContext.Addresses.Collect(order.ShippingAddressId.HasValue ? order.ShippingAddressId.Value : 0); + ctx.OrderExportContext.Addresses.GetOrLoad(order.BillingAddressId); - var perfLoadId = (ctx.IsPreview ? 0 : order.Id); - var addresses = ctx.OrderExportContext.Addresses.Load(ctx.IsPreview ? 0 : order.BillingAddressId); - var customers = ctx.OrderExportContext.Customers.Load(order.CustomerId); - var rewardPointsHistories = ctx.OrderExportContext.RewardPointsHistories.Load(ctx.IsPreview ? 0 : order.CustomerId); - var orderItems = ctx.OrderExportContext.OrderItems.Load(perfLoadId); - var shipments = ctx.OrderExportContext.Shipments.Load(perfLoadId); + var customers = ctx.OrderExportContext.Customers.GetOrLoad(order.CustomerId); + var genericAttributes = ctx.OrderExportContext.CustomerGenericAttributes.GetOrLoad(order.CustomerId); + var rewardPointsHistories = ctx.OrderExportContext.RewardPointsHistories.GetOrLoad(order.CustomerId); + var orderItems = ctx.OrderExportContext.OrderItems.GetOrLoad(order.Id); + var shipments = ctx.OrderExportContext.Shipments.GetOrLoad(order.Id); dynamic dynObject = ToDynamic(ctx, order); - if (ctx.Stores.ContainsKey(order.StoreId)) - { - dynObject.Store = ToDynamic(ctx, ctx.Stores[order.StoreId]); - } - dynObject.Customer = ToDynamic(ctx, customers.FirstOrDefault(x => x.Id == order.CustomerId)); + // We do not export all customer generic attributes because otherwise the export file gets too large. + dynObject.Customer._GenericAttributes = genericAttributes + .Where(x => x.Value.HasValue() && _orderCustomerAttributes.Contains(x.Key)) + .Select(x => ToDynamic(ctx, x)) + .ToList(); + dynObject.Customer.RewardPointsHistory = rewardPointsHistories .Select(x => ToDynamic(ctx, x)) .ToList(); @@ -1085,31 +1358,18 @@ private List Convert(DataExporterContext ctx, Order order) .PointsBalance; } - dynObject.BillingAddress = ToDynamic(ctx, addresses.FirstOrDefault(x => x.Id == order.BillingAddressId)); + if (ctx.OrderExportContext.Addresses.ContainsKey(order.BillingAddressId)) + { + dynObject.BillingAddress = ToDynamic(ctx, ctx.OrderExportContext.Addresses[order.BillingAddressId].FirstOrDefault()); + } - if (order.ShippingAddressId.HasValue) + if (order.ShippingAddressId.HasValue && ctx.OrderExportContext.Addresses.ContainsKey(order.ShippingAddressId.Value)) { - dynObject.ShippingAddress = ToDynamic(ctx, addresses.FirstOrDefault(x => x.Id == order.ShippingAddressId.Value)); + dynObject.ShippingAddress = ToDynamic(ctx, ctx.OrderExportContext.Addresses[order.ShippingAddressId.Value].FirstOrDefault()); } dynObject.OrderItems = orderItems - .Select(e => - { - dynamic dyn = ToDynamic(ctx, e); - - if (ctx.ProductTemplates.ContainsKey(e.Product.ProductTemplateId)) - dyn.Product._ProductTemplateViewPath = ctx.ProductTemplates[e.Product.ProductTemplateId]; - else - dyn.Product._ProductTemplateViewPath = ""; - - dyn.Product._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, - _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); - - ToDeliveryTime(ctx, dyn.Product, e.Product.DeliveryTimeId); - ToQuantityUnit(ctx, dyn.Product, e.Product.QuantityUnitId); - - return dyn; - }) + .Select(x => ToDynamic(ctx, x)) .ToList(); dynObject.Shipments = shipments @@ -1133,14 +1393,14 @@ private List Convert(DataExporterContext ctx, Manufacturer manufacturer { var result = new List(); - var productManufacturers = ctx.ManufacturerExportContext.ProductManufacturers.Load(manufacturer.Id); + var productManufacturers = ctx.ManufacturerExportContext.ProductManufacturers.GetOrLoad(manufacturer.Id); dynamic dynObject = ToDynamic(ctx, manufacturer); - if (!ctx.IsPreview && manufacturer.PictureId.HasValue) + if (manufacturer.PictureId.HasValue) { var numberOfPictures = (ctx.Projection.NumberOfPictures ?? int.MaxValue); - var pictures = ctx.ManufacturerExportContext.Pictures.Load(manufacturer.PictureId.Value).Take(numberOfPictures); + var pictures = ctx.ManufacturerExportContext.Pictures.GetOrLoad(manufacturer.PictureId.Value).Take(numberOfPictures); if (pictures.Any()) dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize); @@ -1173,14 +1433,14 @@ private List Convert(DataExporterContext ctx, Category category) { var result = new List(); - var productCategories = ctx.CategoryExportContext.ProductCategories.Load(category.Id); + var productCategories = ctx.CategoryExportContext.ProductCategories.GetOrLoad(category.Id); dynamic dynObject = ToDynamic(ctx, category); - if (!ctx.IsPreview && category.PictureId.HasValue) + if (category.PictureId.HasValue) { var numberOfPictures = (ctx.Projection.NumberOfPictures ?? int.MaxValue); - var pictures = ctx.CategoryExportContext.Pictures.Load(category.PictureId.Value).Take(numberOfPictures); + var pictures = ctx.CategoryExportContext.Pictures.GetOrLoad(category.PictureId.Value).Take(numberOfPictures); if (pictures.Any()) dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.Value.CategoryThumbPictureSize, _mediaSettings.Value.CategoryThumbPictureSize); @@ -1213,8 +1473,7 @@ private List Convert(DataExporterContext ctx, Customer customer) { var result = new List(); - var perfLoadId = (ctx.IsPreview ? 0 : customer.Id); - var genericAttributes = ctx.CustomerExportContext.GenericAttributes.Load(perfLoadId); + var genericAttributes = ctx.CustomerExportContext.GenericAttributes.GetOrLoad(customer.Id); dynamic dynObject = ToDynamic(ctx, customer); @@ -1225,38 +1484,12 @@ private List Convert(DataExporterContext ctx, Customer customer) .Select(x => ToDynamic(ctx, x)) .ToList(); - dynObject.CustomerRoles = customer.CustomerRoles - .Select(x => - { - dynamic dyn = new DynamicEntity(x); - - return dyn; - }) - .ToList(); - dynObject._GenericAttributes = genericAttributes .Select(x => ToDynamic(ctx, x)) .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) { @@ -1264,7 +1497,7 @@ private List Convert(DataExporterContext ctx, Customer customer) if (pictureId != null) { // reduce traffic and do not export default avatar - dynObject._AvatarPictureUrl = _pictureService.Value.GetPictureUrl(pictureId.Value.ToInt(), _mediaSettings.Value.AvatarPictureSize, false, ctx.Store.Url); + dynObject._AvatarPictureUrl = _pictureService.Value.GetUrl(pictureId.Value.ToInt(), _mediaSettings.Value.AvatarPictureSize, false, _services.StoreService.GetHost(ctx.Store)); } } @@ -1295,8 +1528,34 @@ private List Convert(DataExporterContext ctx, NewsLetterSubscription su ExecuteContext = ctx.ExecuteContext }); + return result; + } + + private List Convert(DataExporterContext ctx, ShoppingCartItem shoppingCartItem) + { + var result = new List(); + dynamic dynObject = ToDynamic(ctx, shoppingCartItem); + + result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.ShoppingCartItem, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); return result; } } + + + internal class DynamicProductContext + { + public string SeName { get; set; } + public string AbsoluteProductUrl { get; set; } + public ICollection Combinations { get; set; } + public ProductVariantAttributeCombination Combination { get; set; } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs index c84182c6e0..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,71 +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 (_loaded != null) - { - _loaded(_data.AsReadOnly()); - } - - return (_data.Count > 0); + 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; } /// - /// 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 d5f2860ff8..f28ec5b11f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -3,11 +3,13 @@ using System.IO; using System.Threading; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; +using SmartStore.Utilities; namespace SmartStore.Services.DataExchange.Export { - public class ExportExecuteContext + public class ExportExecuteContext { private DataExportResult _result; private CancellationToken _cancellation; @@ -20,7 +22,17 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell Folder = folder; ExtraDataUnits = new List(); CustomProperties = new Dictionary(); - } + } + + /// + /// Identifier of the export profile + /// + public int ProfileId { get; internal set; } + + /// + /// The export profile + /// + public dynamic Profile { get; internal set; } /// /// Provides the data to be exported @@ -48,7 +60,12 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell public dynamic Language { get; internal set; } /// - /// Projection data + /// Filter settings + /// + public ExportFilter Filter { get; internal set; } + + /// + /// Projection settings /// public ExportProjection Projection { get; internal set; } @@ -100,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 @@ -120,6 +142,11 @@ public bool IsMaxFailures /// public string PublicFolderPath { get; internal set; } + /// + /// The URL of the public export folder "Exchange". null if the profile has no public deployment. + /// + public string PublicFolderUrl { get; internal set; } + /// /// Provider specific configuration data /// @@ -143,18 +170,37 @@ public bool IsMaxFailures /// /// Processes an exception that occurred while exporting a record /// + /// Identifier of the current entity /// Exception public void RecordException(Exception exception, int entityId) { ++RecordsFailed; - Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exception.ToAllMessages()), exception); + Log.ErrorFormat("Error while processing record with id {0}. {1}".FormatInvariant(entityId, exception.ToString())); if (IsMaxFailures) _result.LastError = exception.ToString(); } - public ProgressValueSetter ProgressValueSetter { get; internal set; } + /// + /// Processes an out-of-memory exception and hard aborts the export + /// + /// Out-of-memory exception + /// Identifier of the current entity + /// Localizer + public void RecordOutOfMemoryException(OutOfMemoryException exception, int entityId, Localizer localizer) + { + Abort = DataExchangeAbortion.Hard; + + var fileLength = Prettifier.BytesToString(DataStream.Length); + var batchSizeString = localizer("Admin.DataExchange.Export.BatchSize").Text; + + Log.Fatal($"No more memory could be allocated. Probably the export file is getting too large ({fileLength}). Please use profile setting \"{batchSizeString}\" to split the export into smaller files."); + + RecordException(exception, entityId); + } + + public ProgressValueSetter ProgressValueSetter { get; internal set; } /// /// Allows to set a progress message @@ -175,19 +221,39 @@ 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 related entity type. + /// + public RelatedEntityType? RelatedType { get; set; } + + /// + /// The name of the file to be created + /// + public string FileName { get; set; } + /// - /// The name of the file to be created + /// Short optional text that describes the content of the file /// - public string FileName { get; set; } - } + public string Label { get; set; } + + /// + /// 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 1814ae56ae..e3a4f541b6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Events; using SmartStore.Core.Plugins; @@ -54,17 +55,19 @@ public virtual ExportProfile InsertExportProfile( string profileSystemName = null, int cloneFromProfileId = 0) { - Guard.ArgumentNotEmpty(() => providerSystemName); - - var profileCount = _exportProfileRepository.Table.Count(x => x.ProviderSystemName == providerSystemName); + Guard.NotEmpty(providerSystemName, nameof(providerSystemName)); if (name.IsEmpty()) + { name = providerSystemName; + } if (!isSystemProfile) - name = string.Concat(_localizationService.GetResource("Common.My"), " ", name); + { + var profileCount = _exportProfileRepository.Table.Count(x => x.ProviderSystemName == providerSystemName); - name = string.Concat(name, " ", profileCount + 1); + name = string.Concat(_localizationService.GetResource("Common.My"), " ", name, " ", profileCount + 1); + } var cloneProfile = GetExportProfileById(cloneFromProfileId); @@ -75,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, @@ -85,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"); @@ -108,20 +110,22 @@ 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 }; var filter = new ExportFilter { - IsPublished = true + IsPublished = true, + ShoppingCartTypeId = (int)ShoppingCartType.ShoppingCart }; profile.Projection = XmlHelper.Serialize(projection); @@ -144,16 +148,16 @@ 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); - profile.FolderName = "~/App_Data/ExportProfiles/" + FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath("~/App_Data/ExportProfiles"), folderName); + var path = DataSettings.Current.TenantPath + "/ExportProfiles"; + profile.FolderName = path + "/" + FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath(path), folderName); - if (profileSystemName.IsEmpty() && isSystemProfile) - profile.SystemName = cleanedSystemName; - else - profile.SystemName = profileSystemName; + profile.SystemName = profileSystemName.IsEmpty() && isSystemProfile + ? cleanedSystemName + : profileSystemName; _exportProfileRepository.Insert(profile); @@ -192,8 +196,6 @@ public virtual ExportProfile InsertExportProfile( } } - _eventPublisher.EntityInserted(profile); - return profile; } @@ -203,7 +205,7 @@ public virtual ExportProfile InsertExportProfile( string profileSystemName = null, int cloneFromProfileId = 0) { - Guard.ArgumentNotNull(() => provider); + Guard.NotNull(provider, nameof(provider)); var profile = InsertExportProfile( provider.Metadata.SystemName, @@ -219,20 +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); - _exportProfileRepository.Update(profile); + if (!PathHelper.IsSafeAppRootPath(profile.FolderName)) + { + throw new SmartException(_localizationService.GetResource("Admin.DataExchange.Export.FolderName.Validate")); + } - _eventPublisher.EntityUpdated(profile); + _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")); @@ -240,13 +243,18 @@ 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); - _eventPublisher.EntityDeleted(profile); - if (System.IO.Directory.Exists(folder)) { FileSystemHelper.ClearDirectory(folder, true); @@ -347,9 +355,12 @@ public virtual void UpdateExportDeployment(ExportDeployment deployment) if (deployment == null) throw new ArgumentNullException("deployment"); - _exportDeploymentRepository.Update(deployment); + if (deployment.DeploymentType == ExportDeploymentType.FileSystem && deployment.FileSystemPath == "~/") + { + throw new SmartException("Invalid deployment path."); + } - _eventPublisher.EntityUpdated(deployment); + _exportDeploymentRepository.Update(deployment); } public virtual void DeleteExportDeployment(ExportDeployment deployment) @@ -358,8 +369,6 @@ public virtual void DeleteExportDeployment(ExportDeployment deployment) throw new ArgumentNullException("deployment"); _exportDeploymentRepository.Delete(deployment); - - _eventPublisher.EntityDeleted(deployment); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs index 68c8b78d55..4af3ac1836 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs @@ -9,14 +9,15 @@ using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.DataExchange.Export { public class ExportXmlHelper : IDisposable { - private XmlWriter _writer; - private CultureInfo _culture; - private bool _doNotDispose; + protected XmlWriter _writer; + protected CultureInfo _culture; + protected bool _doNotDispose; public ExportXmlHelper(XmlWriter writer, bool doNotDispose = false, CultureInfo culture = null) { @@ -51,10 +52,7 @@ public static XmlWriterSettings DefaultSettings public ExportXmlExclude Exclude { get; set; } - public XmlWriter Writer - { - get { return _writer; } - } + public XmlWriter Writer => _writer; public void Dispose() { @@ -91,7 +89,7 @@ public void WriteGenericAttributes(dynamic parentNode) GenericAttribute entity = genericAttribute.Entity; _writer.WriteStartElement("GenericAttribute"); - _writer.Write("Id", entity.ToString()); + _writer.Write("Id", entity.Id.ToString()); _writer.Write("EntityId", entity.EntityId.ToString()); _writer.Write("KeyGroup", entity.KeyGroup); _writer.Write("Key", entity.Key); @@ -115,6 +113,8 @@ public void WriteAddress(dynamic address, string node) } _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Salutation", entity.Salutation); + _writer.Write("Title", entity.Title); _writer.Write("FirstName", entity.FirstName); _writer.Write("LastName", entity.LastName); _writer.Write("Email", entity.Email); @@ -200,8 +200,45 @@ public void WriteCurrency(dynamic currency, string node) _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); _writer.Write("DomainEndings", entity.DomainEndings); + _writer.Write("RoundOrderItemsEnabled", entity.RoundOrderItemsEnabled.ToString()); + _writer.Write("RoundNumDecimals", entity.RoundNumDecimals.ToString()); + _writer.Write("RoundOrderTotalEnabled", entity.RoundOrderTotalEnabled.ToString()); + _writer.Write("RoundOrderTotalDenominator", entity.RoundOrderTotalDenominator.ToString(_culture)); + _writer.Write("RoundOrderTotalRule", ((int)entity.RoundOrderTotalRule).ToString()); + + WriteLocalized(currency); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteCountry(dynamic country, string node) + { + if (country == null) + return; + + Country entity = country.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("Name", entity.Name); + _writer.Write("AllowsBilling", entity.AllowsBilling.ToString()); + _writer.Write("AllowsShipping", entity.AllowsShipping.ToString()); + _writer.Write("TwoLetterIsoCode", entity.TwoLetterIsoCode); + _writer.Write("ThreeLetterIsoCode", entity.ThreeLetterIsoCode); + _writer.Write("NumericIsoCode", entity.NumericIsoCode.ToString()); + _writer.Write("SubjectToVat", entity.SubjectToVat.ToString()); + _writer.Write("Published", entity.Published.ToString()); + _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + _writer.Write("LimitedToStores", entity.LimitedToStores.ToString()); - WriteLocalized(currency); + WriteLocalized(country); if (node.HasValue()) { @@ -257,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); @@ -280,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()); @@ -346,11 +385,10 @@ public void WriteCategory(dynamic category, string node) _writer.Write("MetaTitle", (string)category.MetaTitle); _writer.Write("SeName", (string)category.SeName); _writer.Write("ParentCategoryId", entity.ParentCategoryId.ToString()); - _writer.Write("PictureId", entity.PictureId.HasValue ? entity.PictureId.Value.ToString() : ""); + _writer.Write("PictureId", entity.PictureId.ToString()); _writer.Write("PageSize", entity.PageSize.ToString()); _writer.Write("AllowCustomersToSelectPageSize", entity.AllowCustomersToSelectPageSize.ToString()); _writer.Write("PageSizeOptions", entity.PageSizeOptions); - _writer.Write("PriceRanges", entity.PriceRanges); _writer.Write("ShowOnHomePage", entity.ShowOnHomePage.ToString()); _writer.Write("HasDiscountsApplied", entity.HasDiscountsApplied.ToString()); _writer.Write("Published", entity.Published.ToString()); @@ -394,16 +432,16 @@ public void WriteManufacturer(dynamic manufacturer, string node) _writer.Write("MetaKeywords", (string)manufacturer.MetaKeywords); _writer.Write("MetaDescription", (string)manufacturer.MetaDescription); _writer.Write("MetaTitle", (string)manufacturer.MetaTitle); - _writer.Write("PictureId", entity.PictureId.HasValue ? entity.PictureId.Value.ToString() : ""); + _writer.Write("PictureId", entity.PictureId.ToString()); _writer.Write("PageSize", entity.PageSize.ToString()); _writer.Write("AllowCustomersToSelectPageSize", entity.AllowCustomersToSelectPageSize.ToString()); _writer.Write("PageSizeOptions", entity.PageSizeOptions); - _writer.Write("PriceRanges", entity.PriceRanges); _writer.Write("Published", entity.Published.ToString()); _writer.Write("Deleted", entity.Deleted.ToString()); _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); + _writer.Write("HasDiscountsApplied", entity.HasDiscountsApplied.ToString()); WritePicture(manufacturer.Picture, "Picture"); @@ -465,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() : ""); @@ -494,7 +531,10 @@ 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("AllowedQuantities", entity.AllowedQuantities); + _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()); _writer.Write("AvailableForPreOrder", entity.AvailableForPreOrder.ToString()); @@ -510,6 +550,7 @@ public void WriteProduct(dynamic product, string node) _writer.Write("MaximumCustomerEnteredPrice", entity.MaximumCustomerEnteredPrice.ToString(_culture)); _writer.Write("HasTierPrices", entity.HasTierPrices.ToString()); _writer.Write("HasDiscountsApplied", entity.HasDiscountsApplied.ToString()); + _writer.Write("MainPictureId", entity.MainPictureId.HasValue ? entity.MainPictureId.Value.ToString() : ""); _writer.Write("Weight", ((decimal)product.Weight).ToString(_culture)); _writer.Write("Length", ((decimal)product.Length).ToString(_culture)); _writer.Write("Width", ((decimal)product.Width).ToString(_culture)); @@ -524,12 +565,14 @@ 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()); _writer.Write("BundlePerItemShoppingCart", entity.BundlePerItemShoppingCart.ToString()); _writer.Write("LowestAttributeCombinationPrice", lowestAttributeCombinationPrice.HasValue ? lowestAttributeCombinationPrice.Value.ToString(_culture) : ""); _writer.Write("IsEsd", entity.IsEsd.ToString()); + _writer.Write("CustomsTariffNumber", entity.CustomsTariffNumber); WriteLocalized(product); @@ -537,6 +580,8 @@ public void WriteProduct(dynamic product, string node) WriteQuantityUnit(product.QuantityUnit, "QuantityUnit"); + WriteCountry(product.CountryOfOrigin, "CountryOfOrigin"); + if (product.AppliedDiscounts != null) { _writer.WriteStartElement("AppliedDiscounts"); @@ -562,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"); @@ -576,7 +650,8 @@ public void WriteProduct(dynamic product, string node) _writer.Write("CustomerRoleId", entityTierPrice.CustomerRoleId.HasValue ? entityTierPrice.CustomerRoleId.Value.ToString() : ""); _writer.Write("Quantity", entityTierPrice.Quantity.ToString()); _writer.Write("Price", entityTierPrice.Price.ToString(_culture)); - _writer.WriteEndElement(); // TierPrice + _writer.Write("CalculationMethod", ((int)entityTierPrice.CalculationMethod).ToString()); + _writer.WriteEndElement(); // TierPrice } _writer.WriteEndElement(); // TierPrices } @@ -604,8 +679,9 @@ public void WriteProduct(dynamic product, string node) foreach (dynamic pva in product.ProductAttributes) { ProductVariantAttribute entityPva = pva.Entity; + ProductAttribute entityPa = pva.Attribute.Entity; - _writer.WriteStartElement("ProductAttribute"); + _writer.WriteStartElement("ProductAttribute"); _writer.Write("Id", entityPva.Id.ToString()); _writer.Write("TextPrompt", (string)pva.TextPrompt); _writer.Write("IsRequired", entityPva.IsRequired.ToString()); @@ -613,12 +689,16 @@ public void WriteProduct(dynamic product, string node) _writer.Write("DisplayOrder", entityPva.DisplayOrder.ToString()); _writer.WriteStartElement("Attribute"); - _writer.Write("Id", ((int)pva.Attribute.Id).ToString()); - _writer.Write("Alias", (string)pva.Attribute.Alias); - _writer.Write("Name", (string)pva.Attribute.Name); - _writer.Write("Description", (string)pva.Attribute.Description); + _writer.Write("Id", entityPa.Id.ToString()); + _writer.Write("Alias", entityPa.Alias); + _writer.Write("Name", entityPa.Name); + _writer.Write("Description", entityPa.Description); + _writer.Write("AllowFiltering", entityPa.AllowFiltering.ToString()); + _writer.Write("DisplayOrder", entityPa.DisplayOrder.ToString()); + _writer.Write("FacetTemplateHint", ((int)entityPa.FacetTemplateHint).ToString()); + _writer.Write("IndexOptionNames", entityPa.IndexOptionNames.ToString()); - WriteLocalized(pva.Attribute); + WriteLocalized(pva.Attribute); _writer.WriteEndElement(); // Attribute @@ -631,7 +711,7 @@ public void WriteProduct(dynamic product, string node) _writer.Write("Id", entityPvav.Id.ToString()); _writer.Write("Alias", (string)value.Alias); _writer.Write("Name", (string)value.Name); - _writer.Write("ColorSquaresRgb", (string)value.ColorSquaresRgb); + _writer.Write("Color", (string)value.Color); _writer.Write("PriceAdjustment", ((decimal)value.PriceAdjustment).ToString(_culture)); _writer.Write("WeightAdjustment", ((decimal)value.WeightAdjustment).ToString(_culture)); _writer.Write("IsPreSelected", entityPvav.IsPreSelected.ToString()); @@ -673,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"); @@ -774,13 +853,20 @@ public void WriteProduct(dynamic product, string node) _writer.Write("SpecificationAttributeId", entitySao.SpecificationAttributeId.ToString()); _writer.Write("DisplayOrder", entitySao.DisplayOrder.ToString()); _writer.Write("Name", (string)option.Name); + _writer.Write("Alias", (string)option.Alias); WriteLocalized(option); _writer.WriteStartElement("SpecificationAttribute"); _writer.Write("Id", entitySa.Id.ToString()); _writer.Write("Name", (string)option.SpecificationAttribute.Name); + _writer.Write("Alias", (string)option.SpecificationAttribute.Alias); _writer.Write("DisplayOrder", entitySa.DisplayOrder.ToString()); + _writer.Write("AllowFiltering", entitySa.AllowFiltering.ToString()); + _writer.Write("ShowOnProductPage", entitySa.ShowOnProductPage.ToString()); + _writer.Write("FacetSorting", ((int)entitySa.FacetSorting).ToString()); + _writer.Write("FacetTemplateHint", ((int)entitySa.FacetTemplateHint).ToString()); + _writer.Write("IndexOptionNames", entitySa.IndexOptionNames.ToString()); WriteLocalized(option.SpecificationAttribute); @@ -845,9 +931,6 @@ public void WriteCustomer(dynamic customer, string node) _writer.Write("CustomerGuid", entity.CustomerGuid.ToString()); _writer.Write("Username", entity.Username); _writer.Write("Email", entity.Email); - _writer.Write("Password", entity.Password); - _writer.Write("PasswordFormatId", entity.PasswordFormatId.ToString()); - _writer.Write("PasswordSalt", entity.PasswordSalt); _writer.Write("AdminComment", entity.AdminComment); _writer.Write("IsTaxExempt", entity.IsTaxExempt.ToString()); _writer.Write("AffiliateId", entity.AffiliateId.ToString()); @@ -859,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) { @@ -903,6 +994,40 @@ public void WriteCustomer(dynamic customer, string node) _writer.WriteEndElement(); } } + + public void WriteShoppingCartItem(dynamic shoppingCartItem, string node) + { + if (shoppingCartItem == null) + return; + + ShoppingCartItem entity = shoppingCartItem.Entity; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", entity.Id.ToString()); + _writer.Write("StoreId", entity.StoreId.ToString()); + _writer.Write("ParentItemId", entity.ParentItemId.HasValue ? entity.ParentItemId.Value.ToString() : ""); + _writer.Write("BundleItemId", entity.BundleItemId.HasValue ? entity.BundleItemId.Value.ToString() : ""); + _writer.Write("ShoppingCartTypeId", entity.ShoppingCartTypeId.ToString()); + _writer.Write("CustomerId", entity.CustomerId.ToString()); + _writer.Write("ProductId", entity.ProductId.ToString()); + _writer.Write("AttributesXml", entity.AttributesXml, null, true); + _writer.Write("CustomerEnteredPrice", entity.CustomerEnteredPrice.ToString(_culture)); + _writer.Write("Quantity", entity.Quantity.ToString()); + _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); + _writer.Write("UpdatedOnUtc", entity.UpdatedOnUtc.ToString(_culture)); + + WriteCustomer(shoppingCartItem.Customer, "Customer"); + WriteProduct(shoppingCartItem.Product, "Product"); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs index 34b96faf24..b014d487f5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs @@ -4,6 +4,7 @@ using System.Threading; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Plugins; @@ -13,9 +14,23 @@ 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 + /// + /// Products. null to lazy load data if required. + /// Customer, null to use current customer. + /// Store identifier, null to use current store. + /// Pictures per product, null to load all pictures per product. + /// A value indicating whether to show hidden records + /// Product export context + ProductExportContext CreateProductExportContext( + IEnumerable products = null, + Customer customer = null, + int? storeId = null, + int? maxPicturesPerProduct = null, + bool showHidden = true); } @@ -25,8 +40,8 @@ public class DataExportRequest public DataExportRequest(ExportProfile profile, Provider provider) { - Guard.ArgumentNotNull(() => profile); - Guard.ArgumentNotNull(() => provider); + Guard.NotNull(profile, nameof(profile)); + Guard.NotNull(provider, nameof(provider)); Profile = profile; Provider = provider; @@ -46,6 +61,8 @@ public DataExportRequest(ExportProfile profile, Provider provid public IList EntitiesToExport { get; set; } + public string ActionOrigin { get; set; } + public IDictionary CustomData { get; private set; } public IQueryable ProductQuery { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index 5c6c1315dd..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, @@ -25,10 +25,13 @@ public DataExporterContext( Projection = XmlHelper.Deserialize(request.Profile.Projection); IsPreview = isPreview; + if (request.Profile.Projection.IsEmpty()) + { + Projection.DescriptionMergingId = (int)ExportDescriptionMerging.Description; + } + FolderContent = request.Profile.GetExportFolder(true, true); - Categories = new Dictionary(); - CategoryPathes = new Dictionary(); DeliveryTimes = new Dictionary(); QuantityUnits = new Dictionary(); Stores = new Dictionary(); @@ -37,18 +40,24 @@ 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); - ExecuteContext.Projection = XmlHelper.Deserialize(request.Profile.Projection); + ExecuteContext.Filter = Filter; + ExecuteContext.Projection = Projection; + ExecuteContext.ProfileId = request.Profile.Id; if (!IsPreview) { @@ -57,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) @@ -69,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; } @@ -91,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; } @@ -102,9 +113,7 @@ public bool IsFileBasedExport get { return Request.Provider == null || Request.Provider.Value == null || Request.Provider.Value.FileExtension.HasValue(); } } - // data loaded once per export - public Dictionary Categories { get; set; } - public Dictionary CategoryPathes { get; set; } + // Data loaded once per export. public Dictionary DeliveryTimes { get; set; } public Dictionary QuantityUnits { get; set; } public Dictionary Stores { get; set; } @@ -114,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/Internal/OrderExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs index 9ebe5c25a5..02f56914fb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs @@ -16,12 +16,14 @@ internal class OrderExportContext protected List _addressIds; private Func> _funcCustomers; + private Func> _funcCustomerGenericAttributes; private Func> _funcRewardPointsHistories; private Func> _funcAddresses; private Func> _funcOrderItems; private Func> _funcShipments; private LazyMultimap _customers; + private LazyMultimap _customerGenericAttributes; private LazyMultimap _rewardPointsHistories; private LazyMultimap
_addresses; private LazyMultimap _orderItems; @@ -29,6 +31,7 @@ internal class OrderExportContext public OrderExportContext(IEnumerable orders, Func> customers, + Func> customerGenericAttributes, Func> rewardPointsHistory, Func> addresses, Func> orderItems, @@ -53,6 +56,7 @@ public OrderExportContext(IEnumerable orders, } _funcCustomers = customers; + _funcCustomerGenericAttributes = customerGenericAttributes; _funcRewardPointsHistories = rewardPointsHistory; _funcAddresses = addresses; _funcOrderItems = orderItems; @@ -63,6 +67,8 @@ public void Clear() { if (_customers != null) _customers.Clear(); + if (_customerGenericAttributes != null) + _customerGenericAttributes.Clear(); if (_rewardPointsHistories != null) _rewardPointsHistories.Clear(); if (_addresses != null) @@ -89,6 +95,18 @@ public LazyMultimap Customers } } + public LazyMultimap CustomerGenericAttributes + { + get + { + if (_customerGenericAttributes == null) + { + _customerGenericAttributes = new LazyMultimap(keys => _funcCustomerGenericAttributes(keys), _customerIds); + } + return _customerGenericAttributes; + } + } + public LazyMultimap RewardPointsHistories { get diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs deleted file mode 100644 index 6a76e1ffef..0000000000 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using SmartStore.Collections; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Services.Catalog; - -namespace SmartStore.Services.DataExchange.Export.Internal -{ - internal class ProductExportContext : PriceCalculationContext - { - private List _productIdsBundleItems; - - private Func> _funcProductManufacturers; - private Func> _funcProductPictures; - private Func> _funcProductTags; - private Func> _funcProductSpecificationAttributes; - private Func> _funcProductBundleItems; - - private LazyMultimap _productManufacturers; - private LazyMultimap _productPictures; - private LazyMultimap _productTags; - private LazyMultimap _productSpecificationAttributes; - private LazyMultimap _productBundleItems; - - public ProductExportContext( - IEnumerable products, - Func> attributes, - Func> attributeCombinations, - Func> tierPrices, - Func> productCategories, - Func> productManufacturers, - Func> productPictures, - Func> productTags, - Func> productAppliedDiscounts, - Func> productSpecificationAttributes, - Func> productBundleItems) - : base(products, - attributes, - attributeCombinations, - tierPrices, - productCategories, - productAppliedDiscounts) - { - if (products == null) - { - _productIdsBundleItems = new List(); - } - else - { - _productIdsBundleItems = new List(products.Where(x => x.ProductType == ProductType.BundledProduct).Select(x => x.Id)); - } - - _funcProductManufacturers = productManufacturers; - _funcProductPictures = productPictures; - _funcProductTags = productTags; - _funcProductSpecificationAttributes = productSpecificationAttributes; - _funcProductBundleItems = productBundleItems; - } - - public new void Clear() - { - if (_productManufacturers != null) - _productManufacturers.Clear(); - if (_productPictures != null) - _productPictures.Clear(); - if (_productTags != null) - _productTags.Clear(); - if (_productSpecificationAttributes != null) - _productSpecificationAttributes.Clear(); - if (_productBundleItems != null) - _productBundleItems.Clear(); - - _productIdsBundleItems.Clear(); - - base.Clear(); - } - - //public new void Collect(IEnumerable productIds) - //{ - // ProductManufacturers.Collect(productIds); - // ProductPictures.Collect(productIds); - - // base.Collect(productIds); - //} - - public LazyMultimap ProductManufacturers - { - get - { - if (_productManufacturers == null) - { - _productManufacturers = new LazyMultimap(keys => _funcProductManufacturers(keys), _productIds); - } - return _productManufacturers; - } - } - - public LazyMultimap ProductPictures - { - get - { - if (_productPictures == null) - { - _productPictures = new LazyMultimap(keys => _funcProductPictures(keys), _productIds); - } - return _productPictures; - } - } - - public LazyMultimap ProductTags - { - get - { - if (_productTags == null) - { - _productTags = new LazyMultimap(keys => _funcProductTags(keys), _productIds); - } - return _productTags; - } - } - - public LazyMultimap ProductSpecificationAttributes - { - get - { - if (_productSpecificationAttributes == null) - { - _productSpecificationAttributes = new LazyMultimap(keys => _funcProductSpecificationAttributes(keys), _productIds); - } - return _productSpecificationAttributes; - } - } - - public LazyMultimap ProductBundleItems - { - get - { - if (_productBundleItems == null) - { - _productBundleItems = new LazyMultimap(keys => _funcProductBundleItems(keys), _productIdsBundleItems); - } - return _productBundleItems; - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs new file mode 100644 index 0000000000..5a3b22a4bd --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Domain.Media; +using SmartStore.Services.Catalog; + +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 + { + private Func> _funcProductPictures; + private Func> _funcProductTags; + private Func> _funcSpecificationAttributes; + private Func> _funcPictures; + private Func> _funcDownloads; + + private LazyMultimap _productPictures; + private LazyMultimap _productTags; + private LazyMultimap _specificationAttributes; + private LazyMultimap _pictures; + private LazyMultimap _downloads; + + public ProductExportContext( + IEnumerable products, + Func> attributes, + Func> attributeCombinations, + Func> specificationAttributes, + Func> tierPrices, + Func> productCategories, + Func> productManufacturers, + Func> appliedDiscounts, + Func> productBundleItems, + Func> associatedProducts, + Func> pictures, + Func> productPictures, + Func> productTags, + Func> downloads) + : base(products, + attributes, + attributeCombinations, + tierPrices, + productCategories, + productManufacturers, + appliedDiscounts, + productBundleItems, + associatedProducts) + { + _funcPictures = pictures; + _funcProductPictures = productPictures; + _funcProductTags = productTags; + _funcSpecificationAttributes = specificationAttributes; + _funcDownloads = downloads; + } + + public new void Clear() + { + _productPictures?.Clear(); + _productTags?.Clear(); + _specificationAttributes?.Clear(); + _pictures?.Clear(); + _downloads?.Clear(); + + base.Clear(); + } + + public LazyMultimap Pictures + { + get + { + if (_pictures == null) + { + _pictures = new LazyMultimap(keys => _funcPictures(keys), _productIds); + } + return _pictures; + } + } + + public LazyMultimap ProductPictures + { + get + { + if (_productPictures == null) + { + _productPictures = new LazyMultimap(keys => _funcProductPictures(keys), _productIds); + } + return _productPictures; + } + } + + public LazyMultimap ProductTags + { + get + { + if (_productTags == null) + { + _productTags = new LazyMultimap(keys => _funcProductTags(keys), _productIds); + } + return _productTags; + } + } + + public LazyMultimap SpecificationAttributes + { + get + { + if (_specificationAttributes == null) + { + _specificationAttributes = new LazyMultimap(keys => _funcSpecificationAttributes(keys), _productIds); + } + 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 eb72d07e19..9bdbc6d790 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; @@ -25,8 +24,9 @@ public partial interface ISyncMappingService ///
/// The context (external application) name. Leave null to load all records regardless of context. /// The entity name. Leave null to load all records regardless of entity name. + /// Array of entity identifiers /// SyncMappings - IList GetAllSyncMappings(string contextName = null, string entityName = null); + IList GetAllSyncMappings(string contextName = null, string entityName = null, int[] entityIds = null); /// /// Gets a sync mapping record by (target) entity id, name and context name. @@ -85,9 +85,9 @@ public static class ISyncMappingServiceExtensions public static SyncMapping InsertSyncMapping(this ISyncMappingService svc, T entity, string contextName, string sourceKey) where T : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotEmpty(() => contextName); - Guard.ArgumentNotEmpty(() => sourceKey); + Guard.NotNull(entity, nameof(entity)); + Guard.NotEmpty(contextName, nameof(contextName)); + Guard.NotEmpty(sourceKey, nameof(sourceKey)); if (entity is SyncMapping) { @@ -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 }; @@ -112,8 +112,8 @@ public static SyncMapping InsertSyncMapping(this ISyncMappingService svc, T e public static SyncMapping GetSyncMappingByEntity(this ISyncMappingService svc, T entity, string contextName) where T : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotEmpty(() => contextName); + Guard.NotNull(entity, nameof(entity)); + Guard.NotEmpty(contextName, nameof(contextName)); if (entity is SyncMapping) { @@ -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); } /// @@ -139,10 +139,10 @@ public static SyncMapping GetSyncMappingByEntity(this ISyncMappingService svc /// Both sequences must contain at least one element and must be of equal size. public static IList InsertSyncMappings(this ISyncMappingService svc, string contextName, string entityName, IEnumerable entityIds, IEnumerable sourceKeys) { - Guard.ArgumentNotEmpty(() => contextName); - Guard.ArgumentNotEmpty(() => entityName); - Guard.ArgumentNotNull(() => entityIds); - Guard.ArgumentNotNull(() => sourceKeys); + Guard.NotEmpty(contextName, nameof(contextName)); + Guard.NotEmpty(entityName, nameof(entityName)); + Guard.NotNull(entityIds, nameof(entityIds)); + Guard.NotNull(sourceKeys, nameof(sourceKeys)); if (!entityIds.Any() || !sourceKeys.Any() || entityIds.Count() != sourceKeys.Count()) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 14a0ac77e9..ec1ae4335b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -62,8 +62,8 @@ public void AddMapping(string sourceName, string mappedName, string defaultValue public void AddMapping(string sourceName, string index, string mappedName, string defaultValue = null) { - Guard.ArgumentNotEmpty(() => sourceName); - Guard.ArgumentNotEmpty(() => mappedName); + Guard.NotEmpty(sourceName, nameof(sourceName)); + Guard.NotEmpty(mappedName, nameof(mappedName)); var key = CreateSourceName(sourceName, index); 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 aeb4b81897..e413acdde9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -16,6 +16,10 @@ using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Utilities; +using SmartStore.Data.Caching; +using SmartStore.Services.Seo; +using System.Collections.Generic; +using SmartStore.Services.DataExchange.Import.Events; namespace SmartStore.Services.DataExchange.Import { @@ -29,6 +33,9 @@ public partial class DataImporter : IDataImporter private readonly Lazy _emailSender; private readonly Lazy _contactDataSettings; private readonly Lazy _dataExchangeSettings; + private readonly IDbCache _dbCache; + private readonly IUrlRecordService _urlRecordService; + private readonly ILocalizedEntityService _localizedEntityService; public DataImporter( ICommonServices services, @@ -38,7 +45,10 @@ public DataImporter( Lazy emailAccountService, Lazy emailSender, Lazy contactDataSettings, - Lazy dataExchangeSettings) + Lazy dataExchangeSettings, + IDbCache dbCache, + IUrlRecordService urlRecordService, + ILocalizedEntityService localizedEntityService) { _services = services; _importProfileService = importProfileService; @@ -48,6 +58,9 @@ public DataImporter( _emailSender = emailSender; _contactDataSettings = contactDataSettings; _dataExchangeSettings = dataExchangeSettings; + _dbCache = dbCache; + _urlRecordService = urlRecordService; + _localizedEntityService = localizedEntityService; T = NullLocalizer.Instance; } @@ -76,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.Information(sb.ToString()); - - foreach (var message in result.Messages) - { - if (message.MessageType == ImportMessageType.Error) - ctx.Log.Error(message.ToString(), message.FullMessage); - else if (message.MessageType == ImportMessageType.Warning) - ctx.Log.Warning(message.ToString()); - else - ctx.Log.Information(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) @@ -171,175 +195,196 @@ 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.Information(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)) { - try + var scopes = new List(); + + try { + 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.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); - - 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 { try { - // database context sharing problem: if there are entities in modified state left by the provider due to SaveChanges failure, + _dbCache.Enabled = true; + scopes.Each(x => x.Dispose()); + + _services.EventPublisher.Publish(new ImportExecutedEvent(ctx.ExecuteContext)); + } + catch (Exception ex) + { + logger.ErrorsAll(ex); + } + + try + { + // 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); } } } @@ -347,14 +392,16 @@ private void ImportCoreOuter(DataImporterContext ctx) public void Import(DataImportRequest request, CancellationToken cancellationToken) { - Guard.ArgumentNotNull(() => request); - Guard.ArgumentNotNull(() => cancellationToken); - - var ctx = new DataImporterContext(request, cancellationToken, T("Admin.DataExchange.Import.ProgressInfo")); + Guard.NotNull(request, nameof(request)); + Guard.NotNull(cancellationToken, nameof(cancellationToken)); - 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/DataTable/LightweightDataTable.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs index 52be0a8dfa..f1a33eb808 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs @@ -21,8 +21,8 @@ public class LightweightDataTable : IDataTable public LightweightDataTable(IList columns, IList data) { - Guard.ArgumentNotNull(() => columns); - Guard.ArgumentNotNull(() => data); + Guard.NotNull(columns, nameof(columns)); + Guard.NotNull(data, nameof(data)); if (columns.Select(x => x.Name.ToLower()).Distinct().ToArray().Length != columns.Count) { @@ -130,7 +130,7 @@ public static IDataTable FromPostedFile( int skip = 0, int take = int.MaxValue) { - Guard.ArgumentNotNull(() => file); + Guard.NotNull(file, nameof(file)); return FromFile(file.FileName, file.InputStream, file.ContentLength, new CsvConfiguration(), skip, take); } @@ -141,7 +141,7 @@ public static IDataTable FromPostedFile( int skip = 0, int take = int.MaxValue) { - Guard.ArgumentNotNull(() => file); + Guard.NotNull(file, nameof(file)); return FromFile(file.FileName, file.InputStream, file.ContentLength, configuration, skip, take); } @@ -154,9 +154,9 @@ public static IDataTable FromFile( int skip = 0, int take = int.MaxValue) { - Guard.ArgumentNotEmpty(() => fileName); - Guard.ArgumentNotNull(() => stream); - Guard.ArgumentNotNull(() => configuration); + Guard.NotEmpty(fileName, nameof(fileName)); + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(configuration, nameof(configuration)); if (contentLength == 0) { @@ -210,7 +210,7 @@ public static IDataTable FromDataReader( int skip = 0, int take = int.MaxValue) { - Guard.ArgumentNotNull(() => reader); + Guard.NotNull(reader, nameof(reader)); if (reader.IsClosed) throw new ArgumentException("This operation is invalid when the reader is closed.", "reader"); @@ -262,7 +262,7 @@ internal class LightweightDataRow : DynamicObject, IDataRow public LightweightDataRow(IDataTable table, object[] values) { - Guard.ArgumentNotNull(() => values); + Guard.NotNull(values, nameof(values)); if (table.Columns.Count != values.Length) { @@ -384,8 +384,8 @@ internal class LightweightDataColumn : IDataColumn { public LightweightDataColumn(string name, Type type) { - Guard.ArgumentNotEmpty(() => name); - Guard.ArgumentNotNull(() => type); + Guard.NotEmpty(name, nameof(name)); + Guard.NotNull(type, nameof(type)); this.Name = name; this.Type = type; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index eff63d8431..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 { @@ -100,40 +100,35 @@ public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoN item.MimeType = MediaTypeNames.Image.Jpeg; } - var extension = MimeTypes.MapMimeTypeToExtension(item.MimeType); - - if (extension.HasValue()) + if (urlOrPath.IsWebUrl()) { - if (urlOrPath.IsWebUrl()) - { - item.Url = urlOrPath; - item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); + item.Url = context.Services.WebHelper.ModifyQueryString(urlOrPath, "q=100", null); + item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); - if (DownloadedItems.ContainsKey(urlOrPath)) - { - item.Path = Path.Combine(ImageDownloadFolder, DownloadedItems[urlOrPath]); - item.Success = true; - } - else - { - item.Path = Path.Combine(ImageDownloadFolder, item.FileName + extension.EnsureStartsWith(".")); - } - } - else if (Path.IsPathRooted(urlOrPath)) + if (DownloadedItems.ContainsKey(urlOrPath)) { - item.Path = urlOrPath; + item.Path = Path.Combine(ImageDownloadFolder, DownloadedItems[urlOrPath]); item.Success = true; } else { - item.Path = Path.Combine(ImageFolder, urlOrPath); - item.Success = true; - } + var extension = MimeTypes.MapMimeTypeToExtension(item.MimeType).NullEmpty() ?? ".jpg"; - return item; + item.Path = Path.Combine(ImageDownloadFolder, item.FileName + extension.EnsureStartsWith(".")); + } + } + else if (Path.IsPathRooted(urlOrPath)) + { + item.Path = urlOrPath; + item.Success = true; + } + else + { + item.Path = Path.Combine(ImageFolder, urlOrPath); + item.Success = true; } - return null; + return item; } public void Succeeded(FileDownloadManagerItem item) @@ -149,9 +144,9 @@ protected virtual int ProcessLocalizations( IEnumerable> batch, IDictionary>> localizableProperties) where TEntity : BaseEntity, ILocalizedEntity { - Guard.ArgumentNotNull(() => context); - Guard.ArgumentNotNull(() => batch); - Guard.ArgumentNotNull(() => localizableProperties); + Guard.NotNull(context, nameof(context)); + Guard.NotNull(batch, nameof(batch)); + Guard.NotNull(localizableProperties, nameof(localizableProperties)); // Perf: determine whether our localizable properties actually have // counterparts in the source BEFORE import batch begins. This way we spare ourself 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/Events/ImportExecutedEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportExecutedEvent.cs new file mode 100644 index 0000000000..c59979e207 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportExecutedEvent.cs @@ -0,0 +1,20 @@ +using System; + +namespace SmartStore.Services.DataExchange.Import.Events +{ + public class ImportExecutedEvent + { + public ImportExecutedEvent(ImportExecuteContext context) + { + Guard.NotNull(context, nameof(context)); + + Context = context; + } + + public ImportExecuteContext Context + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportExecutingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportExecutingEvent.cs new file mode 100644 index 0000000000..3c0160b306 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportExecutingEvent.cs @@ -0,0 +1,20 @@ +using System; + +namespace SmartStore.Services.DataExchange.Import.Events +{ + public class ImportExecutingEvent + { + public ImportExecutingEvent(ImportExecuteContext context) + { + Guard.NotNull(context, nameof(context)); + + Context = context; + } + + public ImportExecuteContext Context + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs index 8c2fbebf73..9eee0876b3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs @@ -18,7 +18,7 @@ public class DataImportRequest public DataImportRequest(ImportProfile profile) { - Guard.ArgumentNotNull(() => profile); + Guard.NotNull(profile, nameof(profile)); Profile = profile; ProgressValueSetter = _voidProgressValueSetter; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index 41129e24f8..ef0a714fec 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -22,8 +22,8 @@ public class ImportDataSegmenter public ImportDataSegmenter(IDataTable table, ColumnMap map) { - Guard.ArgumentNotNull(() => table); - Guard.ArgumentNotNull(() => map); + Guard.NotNull(table, nameof(table)); + Guard.NotNull(map, nameof(map)); _table = table; _columnMap = map; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 70170c0fd3..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,12 @@ public ImportExecuteContext( CancellationToken = cancellation; CustomProperties = new Dictionary(); - Result = new ImportResult(); + } + + public DataImportRequest Request + { + get; + internal set; } /// @@ -62,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; @@ -204,8 +218,7 @@ public void SetProgress(int value, int maximum) { try { - if (_progressValueSetter != null) - _progressValueSetter.Invoke(value, maximum, _progressInfo.FormatInvariant(value, maximum)); + _progressValueSetter?.Invoke(value, maximum, _progressInfo.FormatInvariant(value, maximum)); } catch { } } @@ -218,8 +231,8 @@ public void SetProgress(string message) { try { - if (_progressValueSetter != null && message.HasValue()) - _progressValueSetter.Invoke(0, 0, message); + if (message.HasValue()) + _progressValueSetter?.Invoke(0, 0, message); } catch { } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs index 89f576e47a..9b7918fa18 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs @@ -1,53 +1,83 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using SmartStore.Core.Data; using SmartStore.Core.Domain; using SmartStore.Utilities; namespace SmartStore.Services.DataExchange.Import { - public static class ImportExtensions + public static class ImportExtensions { /// /// Get folder for import files /// /// Import profile /// Folder path - public static string GetImportFolder(this ImportProfile profile, bool content = false, bool create = false) + public static string GetImportFolder( + this ImportProfile profile, + bool content = false, + bool create = false, + bool absolutePath = true) { - var path = CommonHelper.MapPath(string.Concat("~/App_Data/ImportProfiles/", profile.FolderName, content ? "/Content" : "")); + var path = string.Concat( + DataSettings.Current.TenantPath, + "/ImportProfiles/", + profile.FolderName, + content ? "/Content" : ""); - if (create && !System.IO.Directory.Exists(path)) - System.IO.Directory.CreateDirectory(path); + if (absolutePath) + { + path = CommonHelper.MapPath(path); + + if (create && !System.IO.Directory.Exists(path)) + { + System.IO.Directory.CreateDirectory(path); + } + } 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/ImportMessage.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportMessage.cs index 1061e690e7..b21728d75b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportMessage.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportMessage.cs @@ -7,7 +7,7 @@ public class ImportMessage { public ImportMessage(string message, ImportMessageType messageType = ImportMessageType.Info) { - Guard.ArgumentNotEmpty(() => message); + Guard.NotEmpty(message, nameof(message)); this.Message = message; this.MessageType = messageType; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs index c1e54633dc..da6ad91076 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs @@ -172,7 +172,7 @@ public string GetNewProfileName(ImportEntityType entityType) public virtual ImportProfile InsertImportProfile(string fileName, string name, ImportEntityType entityType) { - Guard.ArgumentNotEmpty(() => fileName); + Guard.NotEmpty(fileName, nameof(fileName)); if (name.IsEmpty()) name = GetNewProfileName(entityType); @@ -223,19 +223,18 @@ 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); - profile.FolderName = FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath("~/App_Data/ImportProfiles"), profile.FolderName); + var path = DataSettings.Current.TenantPath + "/ImportProfiles"; + profile.FolderName = FileSystemHelper.CreateNonExistingDirectoryName(CommonHelper.MapPath(path), profile.FolderName); _importProfileRepository.Insert(profile); task.Alias = profile.Id.ToString(); _scheduleTaskService.UpdateTask(task); - _eventPublisher.EntityInserted(profile); - return profile; } @@ -245,8 +244,6 @@ public virtual void UpdateImportProfile(ImportProfile profile) throw new ArgumentNullException("profile"); _importProfileRepository.Update(profile); - - _eventPublisher.EntityUpdated(profile); } public virtual void DeleteImportProfile(ImportProfile profile) @@ -262,8 +259,6 @@ public virtual void DeleteImportProfile(ImportProfile profile) var scheduleTask = _scheduleTaskService.GetTaskById(scheduleTaskId); _scheduleTaskService.DeleteTask(scheduleTask); - _eventPublisher.EntityDeleted(profile); - if (System.IO.Directory.Exists(folder)) { FileSystemHelper.ClearDirectory(folder, true); 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 41efff1883..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); - } + 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 3c3a11551c..8b455a6955 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs @@ -1,16 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Services.DataExchange { - + public partial class SyncMappingService : ISyncMappingService { private readonly IRepository _syncMappingsRepository; @@ -22,19 +19,19 @@ public SyncMappingService(IRepository syncMappingsRepository) public void InsertSyncMapping(SyncMapping mapping) { - Guard.ArgumentNotNull(() => mapping); + Guard.NotNull(mapping, nameof(mapping)); _syncMappingsRepository.Insert(mapping); } public void InsertSyncMappings(IEnumerable mappings) { - Guard.ArgumentNotNull(() => mappings); + Guard.NotNull(mappings, nameof(mappings)); _syncMappingsRepository.InsertRange(mappings); } - public IList GetAllSyncMappings(string contextName = null, string entityName = null) + public IList GetAllSyncMappings(string contextName = null, string entityName = null, int[] entityIds = null) { var query = _syncMappingsRepository.Table; @@ -48,14 +45,19 @@ public IList GetAllSyncMappings(string contextName = null, string e query = query.Where(x => x.ContextName == contextName); } + if (entityIds != null && entityIds.Any()) + { + query = query.Where(x => entityIds.Contains(x.EntityId)); + } + return query.ToList(); } public SyncMapping GetSyncMappingByEntity(int entityId, string entityName, string contextName) { - Guard.ArgumentIsPositive(entityId, "entityId"); - Guard.ArgumentNotEmpty(() => entityName); - Guard.ArgumentNotEmpty(() => contextName); + Guard.IsPositive(entityId, nameof(entityId)); + Guard.NotEmpty(entityName, nameof(entityName)); + Guard.NotEmpty(contextName, nameof(contextName)); var query = from x in _syncMappingsRepository.Table where @@ -69,9 +71,9 @@ public SyncMapping GetSyncMappingByEntity(int entityId, string entityName, strin public SyncMapping GetSyncMappingBySource(string sourceKey, string entityName, string contextName) { - Guard.ArgumentNotEmpty(() => sourceKey); - Guard.ArgumentNotEmpty(() => entityName); - Guard.ArgumentNotEmpty(() => contextName); + Guard.NotEmpty(sourceKey, nameof(sourceKey)); + Guard.NotEmpty(entityName, nameof(entityName)); + Guard.NotEmpty(contextName, nameof(contextName)); var query = from x in _syncMappingsRepository.Table where @@ -85,21 +87,21 @@ public SyncMapping GetSyncMappingBySource(string sourceKey, string entityName, s public void DeleteSyncMapping(SyncMapping mapping) { - Guard.ArgumentNotNull(() => mapping); + Guard.NotNull(mapping, nameof(mapping)); _syncMappingsRepository.Delete(mapping); } public void DeleteSyncMappings(IEnumerable mappings) { - Guard.ArgumentNotNull(() => mappings); + Guard.NotNull(mappings, nameof(mappings)); _syncMappingsRepository.DeleteRange(mappings); } public void DeleteSyncMappingsFor(T entity) where T : BaseEntity { - Guard.ArgumentNotNull(() => entity); + Guard.NotNull(entity, nameof(entity)); if (entity is SyncMapping) { @@ -111,12 +113,12 @@ 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) { - Guard.ArgumentNotEmpty(() => contextName); + Guard.NotEmpty(contextName, nameof(contextName)); if (entityName.HasValue()) { @@ -131,7 +133,7 @@ public void DeleteSyncMappings(string contextName, string entityName = null) public void UpdateSyncMapping(SyncMapping mapping) { - Guard.ArgumentNotNull(() => mapping); + Guard.NotNull(mapping, nameof(mapping)); mapping.SyncedOnUtc = DateTime.UtcNow; _syncMappingsRepository.Update(mapping); diff --git a/src/Libraries/SmartStore.Services/Directory/CountryService.cs b/src/Libraries/SmartStore.Services/Directory/CountryService.cs index 384fd75e3c..13f6b21646 100644 --- a/src/Libraries/SmartStore.Services/Directory/CountryService.cs +++ b/src/Libraries/SmartStore.Services/Directory/CountryService.cs @@ -1,84 +1,48 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; namespace SmartStore.Services.Directory { - /// - /// Country service - /// public partial class CountryService : ICountryService { - #region Constants private const string COUNTRIES_ALL_KEY = "SmartStore.country.all-{0}"; private const string COUNTRIES_BILLING_KEY = "SmartStore.country.billing-{0}"; private const string COUNTRIES_SHIPPING_KEY = "SmartStore.country.shipping-{0}"; - private const string COUNTRIES_PATTERN_KEY = "SmartStore.country."; - #endregion - - #region Fields - - private readonly IRepository _countryRepository; - private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - private readonly IStoreContext _storeContext; - private readonly IRepository _storeMappingRepository; - - #endregion + private const string COUNTRIES_PATTERN_KEY = "SmartStore.country.*"; - #region Ctor + private readonly ICommonServices _services; + private readonly IRepository _countryRepository; + private readonly IRepository _storeMappingRepository; - public CountryService(IRequestCache requestCache, + public CountryService( + ICommonServices services, IRepository countryRepository, - IEventPublisher eventPublisher, - IStoreContext storeContext, IRepository storeMappingRepository) { - _requestCache = requestCache; _countryRepository = countryRepository; - _eventPublisher = eventPublisher; - _storeContext = storeContext; + _services = services; _storeMappingRepository = storeMappingRepository; } public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Methods - - /// - /// Deletes a country - /// - /// Country public virtual void DeleteCountry(Country country) { - if (country == null) - throw new ArgumentNullException("country"); + Guard.NotNull(country, nameof(country)); - _countryRepository.Delete(country); - - _requestCache.RemoveByPattern(COUNTRIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(country); + _countryRepository.Delete(country); + + _services.RequestCache.RemoveByPattern(COUNTRIES_PATTERN_KEY); } - /// - /// Gets all countries - /// - /// A value indicating whether to show hidden records - /// Country collection public virtual IList GetAllCountries(bool showHidden = false) { string key = string.Format(COUNTRIES_ALL_KEY, showHidden); - return _requestCache.Get(key, () => + return _services.RequestCache.Get(key, () => { var query = _countryRepository.Table; @@ -89,7 +53,7 @@ public virtual IList GetAllCountries(bool showHidden = false) if (!showHidden && !QuerySettings.IgnoreMultiStore) { - var currentStoreId = _storeContext.CurrentStore.Id; + var currentStoreId = _services.StoreContext.CurrentStore.Id; query = from c in query join sc in _storeMappingRepository.Table on new { c1 = c.Id, c2 = "Country" } equals new { c1 = sc.EntityId, c2 = sc.EntityName } into c_sm @@ -110,15 +74,10 @@ orderby cGroup.Key }); } - /// - /// Gets all countries that allow billing - /// - /// A value indicating whether to show hidden records - /// Country collection public virtual IList GetAllCountriesForBilling(bool showHidden = false) { string key = string.Format(COUNTRIES_BILLING_KEY, showHidden); - return _requestCache.Get(key, () => + return _services.RequestCache.Get(key, () => { var allCountries = GetAllCountries(showHidden); @@ -127,15 +86,10 @@ public virtual IList GetAllCountriesForBilling(bool showHidden = false) }); } - /// - /// Gets all countries that allow shipping - /// - /// A value indicating whether to show hidden records - /// Country collection public virtual IList GetAllCountriesForShipping(bool showHidden = false) { string key = string.Format(COUNTRIES_SHIPPING_KEY, showHidden); - return _requestCache.Get(key, () => + return _services.RequestCache.Get(key, () => { var allCountries = GetAllCountries(showHidden); @@ -144,11 +98,6 @@ public virtual IList GetAllCountriesForShipping(bool showHidden = false }); } - /// - /// Gets a country - /// - /// Country identifier - /// Country public virtual Country GetCountryById(int countryId) { if (countryId == 0) @@ -157,11 +106,6 @@ public virtual Country GetCountryById(int countryId) return _countryRepository.GetById(countryId); } - /// - /// Gets a country by two or three letter ISO code - /// - /// Country two or three letter ISO code - /// Country public virtual Country GetCountryByTwoOrThreeLetterIsoCode(string letterIsoCode) { if (letterIsoCode.HasValue()) @@ -174,11 +118,6 @@ public virtual Country GetCountryByTwoOrThreeLetterIsoCode(string letterIsoCode) return null; } - /// - /// Gets a country by two letter ISO code - /// - /// Country two letter ISO code - /// Country public virtual Country GetCountryByTwoLetterIsoCode(string twoLetterIsoCode) { if (twoLetterIsoCode.IsEmpty()) @@ -192,11 +131,6 @@ public virtual Country GetCountryByTwoLetterIsoCode(string twoLetterIsoCode) return country; } - /// - /// Gets a country by three letter ISO code - /// - /// Country three letter ISO code - /// Country public virtual Country GetCountryByThreeLetterIsoCode(string threeLetterIsoCode) { if (threeLetterIsoCode.IsEmpty()) @@ -210,40 +144,22 @@ public virtual Country GetCountryByThreeLetterIsoCode(string threeLetterIsoCode) return country; } - /// - /// Inserts a country - /// - /// Country public virtual void InsertCountry(Country country) { - if (country == null) - throw new ArgumentNullException("country"); - - _countryRepository.Insert(country); + Guard.NotNull(country, nameof(country)); - _requestCache.RemoveByPattern(COUNTRIES_PATTERN_KEY); + _countryRepository.Insert(country); - //event notification - _eventPublisher.EntityInserted(country); + _services.RequestCache.RemoveByPattern(COUNTRIES_PATTERN_KEY); } - /// - /// Updates the country - /// - /// Country public virtual void UpdateCountry(Country country) { - if (country == null) - throw new ArgumentNullException("country"); + Guard.NotNull(country, nameof(country)); _countryRepository.Update(country); - _requestCache.RemoveByPattern(COUNTRIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(country); + _services.RequestCache.RemoveByPattern(COUNTRIES_PATTERN_KEY); } - - #endregion - } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs b/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs index 1aa7d69449..d417801b19 100644 --- a/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs +++ b/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs @@ -2,52 +2,27 @@ using System.Collections.Generic; using System.Linq; using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Core.Plugins; +using SmartStore.Data.Caching; using SmartStore.Services.Stores; namespace SmartStore.Services.Directory { - /// - /// Currency service - /// public partial class CurrencyService : ICurrencyService { - #region Constants - private const string CURRENCIES_ALL_KEY = "SmartStore.currency.all-{0}"; - private const string CURRENCIES_PATTERN_KEY = "SmartStore.currency."; - private const string CURRENCIES_BY_ID_KEY = "SmartStore.currency.id-{0}"; - #endregion - - #region Fields - private readonly IRepository _currencyRepository; private readonly IStoreMappingService _storeMappingService; - private readonly IRequestCache _requestCache; private readonly CurrencySettings _currencySettings; private readonly IPluginFinder _pluginFinder; private readonly IEventPublisher _eventPublisher; private readonly IProviderManager _providerManager; private readonly IStoreContext _storeContext; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Currency repository - /// Store mapping repository - /// Currency settings - /// Plugin finder - /// Event published - public CurrencyService(IRequestCache requestCache, + public CurrencyService( IRepository currencyRepository, IStoreMappingService storeMappingService, CurrencySettings currencySettings, @@ -56,25 +31,15 @@ public CurrencyService(IRequestCache requestCache, IProviderManager providerManager, IStoreContext storeContext) { - this._requestCache = requestCache; - 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; } - #endregion - - #region Methods - - /// - /// Gets currency live rates - /// - /// Exchange rate currency code - /// Exchange rates public virtual IList GetCurrencyLiveRates(string exchangeRateCurrencyCode) { var exchangeRateProvider = LoadActiveExchangeRateProvider(); @@ -85,68 +50,40 @@ public virtual IList GetCurrencyLiveRates(string exchangeRateCurre return new List(); } - /// - /// Deletes currency - /// - /// Currency public virtual void DeleteCurrency(Currency currency) { - if (currency == null) - throw new ArgumentNullException("currency"); + Guard.NotNull(currency, nameof(currency)); _currencyRepository.Delete(currency); - - _requestCache.RemoveByPattern(CURRENCIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(currency); } - /// - /// Gets a currency - /// - /// Currency identifier - /// Currency public virtual Currency GetCurrencyById(int currencyId) { if (currencyId == 0) return null; - string key = string.Format(CURRENCIES_BY_ID_KEY, currencyId); - return _requestCache.Get(key, () => _currencyRepository.GetById(currencyId)); - } + return _currencyRepository.GetByIdCached(currencyId, "db.cur.id-" + currencyId); + } - /// - /// Gets a currency by code - /// - /// Currency code - /// Currency 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()); } - /// - /// Gets all currencies - /// - /// A value indicating whether to show hidden records - /// Load records allows only in specified store; pass 0 to load all records - /// Currency collection public virtual IList GetAllCurrencies(bool showHidden = false, int storeId = 0) { - string key = string.Format(CURRENCIES_ALL_KEY, showHidden); - var currencies = _requestCache.Get(key, () => - { - var query = _currencyRepository.Table; - if (!showHidden) - query = query.Where(c => c.Published); - query = query.OrderBy(c => c.DisplayOrder); - return query.ToList(); - }); - - //store mapping + var query = _currencyRepository.Table; + + if (!showHidden) + query = query.Where(c => c.Published); + + query = query.OrderBy(c => c.DisplayOrder); + + var currencies = query.ToListCached(); + + // store mapping if (storeId > 0) { currencies = currencies @@ -156,183 +93,109 @@ public virtual IList GetAllCurrencies(bool showHidden = false, int sto return currencies; } - /// - /// Inserts a currency - /// - /// Currency public virtual void InsertCurrency(Currency currency) { - if (currency == null) - throw new ArgumentNullException("currency"); + Guard.NotNull(currency, nameof(currency)); - _currencyRepository.Insert(currency); - - _requestCache.RemoveByPattern(CURRENCIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(currency); + _currencyRepository.Insert(currency); } - /// - /// Updates the currency - /// - /// Currency public virtual void UpdateCurrency(Currency currency) { - if (currency == null) - throw new ArgumentNullException("currency"); - - _currencyRepository.Update(currency); + Guard.NotNull(currency, nameof(currency)); - _requestCache.RemoveByPattern(CURRENCIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(currency); + _currencyRepository.Update(currency); } - - /// - /// Converts currency - /// - /// Amount - /// Currency exchange rate - /// Converted value public virtual decimal ConvertCurrency(decimal amount, decimal exchangeRate) { if (amount != decimal.Zero && exchangeRate != decimal.Zero) return amount * exchangeRate; + return decimal.Zero; } - /// - /// Converts currency - /// - /// Amount - /// Source currency code - /// Target currency code - /// Store to get the primary currencies from - /// Converted value - public virtual decimal ConvertCurrency(decimal amount, Currency sourceCurrencyCode, Currency targetCurrencyCode, Store store = null) + public virtual decimal ConvertCurrency(decimal amount, Currency sourceCurrency, Currency targetCurrency, Store store = null) { decimal result = amount; - if (sourceCurrencyCode.Id == targetCurrencyCode.Id) + if (sourceCurrency.Id == targetCurrency.Id) return result; - if (result != decimal.Zero && sourceCurrencyCode.Id != targetCurrencyCode.Id) + + if (result != decimal.Zero && sourceCurrency.Id != targetCurrency.Id) { - result = ConvertToPrimaryExchangeRateCurrency(result, sourceCurrencyCode, store); - result = ConvertFromPrimaryExchangeRateCurrency(result, targetCurrencyCode, store); + result = ConvertToPrimaryExchangeRateCurrency(result, sourceCurrency, store); + result = ConvertFromPrimaryExchangeRateCurrency(result, targetCurrency, store); } + return result; } - /// - /// Converts to primary exchange rate currency - /// - /// Amount - /// Source currency code - /// Store to get the primary exchange rate currency from - /// Converted value - public virtual decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null) + public virtual decimal ConvertToPrimaryExchangeRateCurrency(decimal amount, Currency sourceCurrency, Store store = null) { decimal result = amount; var primaryExchangeRateCurrency = (store == null ? _storeContext.CurrentStore.PrimaryExchangeRateCurrency : store.PrimaryExchangeRateCurrency); - if (result != decimal.Zero && sourceCurrencyCode.Id != primaryExchangeRateCurrency.Id) + if (result != decimal.Zero && sourceCurrency.Id != primaryExchangeRateCurrency.Id) { - decimal exchangeRate = sourceCurrencyCode.Rate; + decimal exchangeRate = sourceCurrency.Rate; if (exchangeRate == decimal.Zero) - throw new SmartException(string.Format("Exchange rate not found for currency [{0}]", sourceCurrencyCode.Name)); + throw new SmartException(string.Format("Exchange rate not found for currency [{0}]", sourceCurrency.Name)); result = result / exchangeRate; } return result; } - /// - /// Converts from primary exchange rate currency - /// - /// Amount - /// Target currency code - /// Store to get the primary exchange rate currency from - /// Converted value - public virtual decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrencyCode, Store store = null) + public virtual decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrency, Store store = null) { decimal result = amount; var primaryExchangeRateCurrency = (store == null ? _storeContext.CurrentStore.PrimaryExchangeRateCurrency : store.PrimaryExchangeRateCurrency); - if (result != decimal.Zero && targetCurrencyCode.Id != primaryExchangeRateCurrency.Id) + if (result != decimal.Zero && targetCurrency.Id != primaryExchangeRateCurrency.Id) { - decimal exchangeRate = targetCurrencyCode.Rate; + decimal exchangeRate = targetCurrency.Rate; if (exchangeRate == decimal.Zero) - throw new SmartException(string.Format("Exchange rate not found for currency [{0}]", targetCurrencyCode.Name)); + throw new SmartException(string.Format("Exchange rate not found for currency [{0}]", targetCurrency.Name)); result = result * exchangeRate; } return result; } - /// - /// Converts to primary store currency - /// - /// Amount - /// Source currency code - /// Store to get the primary store currency from - /// Converted value - public virtual decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null) + public virtual decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrency, Store store = null) { decimal result = amount; var primaryStoreCurrency = (store == null ? _storeContext.CurrentStore.PrimaryStoreCurrency : store.PrimaryStoreCurrency); - if (result != decimal.Zero && sourceCurrencyCode.Id != primaryStoreCurrency.Id) + if (result != decimal.Zero && sourceCurrency.Id != primaryStoreCurrency.Id) { - decimal exchangeRate = sourceCurrencyCode.Rate; + decimal exchangeRate = sourceCurrency.Rate; if (exchangeRate == decimal.Zero) - throw new SmartException(string.Format("Exchange rate not found for currency [{0}]", sourceCurrencyCode.Name)); + throw new SmartException(string.Format("Exchange rate not found for currency [{0}]", sourceCurrency.Name)); result = result / exchangeRate; } return result; } - /// - /// Converts from primary store currency - /// - /// Amount - /// Target currency code - /// Converted value - public virtual decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrencyCode, Store store = null) + public virtual decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrency, Store store = null) { decimal result = amount; var primaryStoreCurrency = (store == null ? _storeContext.CurrentStore.PrimaryStoreCurrency : store.PrimaryStoreCurrency); - result = ConvertCurrency(amount, primaryStoreCurrency, targetCurrencyCode, store); + result = ConvertCurrency(amount, primaryStoreCurrency, targetCurrency, store); return result; } - - /// - /// Load active exchange rate provider - /// - /// Active exchange rate provider public virtual Provider LoadActiveExchangeRateProvider() { return LoadExchangeRateProviderBySystemName(_currencySettings.ActiveExchangeRateProviderSystemName) ?? LoadAllExchangeRateProviders().FirstOrDefault(); } - /// - /// Load exchange rate provider by system name - /// - /// System name - /// Found exchange rate provider public virtual Provider LoadExchangeRateProviderBySystemName(string systemName) { return _providerManager.GetProvider(systemName); } - /// - /// Load all exchange rate providers - /// - /// Exchange rate providers public virtual IEnumerable> LoadAllExchangeRateProviders() { return _providerManager.GetAllProviders(); } - #endregion } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs b/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs index 73d312fc89..2b3ba237fa 100644 --- a/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs +++ b/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs @@ -1,51 +1,29 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; +using SmartStore.Data.Caching; +using SmartStore.Services.Catalog; using SmartStore.Services.Customers; namespace SmartStore.Services.Directory { - /// - /// DeliveryTime service - /// public partial class DeliveryTimeService : IDeliveryTimeService { - #region Constants - private const string DELIVERYTIMES_ALL_KEY = "SMN.deliverytime.all"; - private const string DELIVERYTIMES_PATTERN_KEY = "SMN.deliverytime."; - #endregion - - #region Fields - private readonly IRepository _deliveryTimeRepository; private readonly IRepository _productRepository; private readonly IRepository _attributeCombinationRepository; - private readonly IRequestCache _requestCache; private readonly ICustomerService _customerService; private readonly IPluginFinder _pluginFinder; private readonly IEventPublisher _eventPublisher; private readonly CatalogSettings _catalogSettings; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// DeliveryTime repository - /// Customer service - /// Currency settings - /// Plugin finder - /// Event published - public DeliveryTimeService(IRequestCache requestCache, + public DeliveryTimeService( IRepository deliveryTimeRepository, IRepository productRepository, IRepository attributeCombinationRepository, @@ -54,7 +32,6 @@ public DeliveryTimeService(IRequestCache requestCache, IEventPublisher eventPublisher, CatalogSettings catalogSettings) { - this._requestCache = requestCache; this._deliveryTimeRepository = deliveryTimeRepository; this._customerService = customerService; this._pluginFinder = pluginFinder; @@ -62,30 +39,21 @@ public DeliveryTimeService(IRequestCache requestCache, this._productRepository = productRepository; this._attributeCombinationRepository = attributeCombinationRepository; this._catalogSettings = catalogSettings; - } - #endregion - - #region Methods + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } - /// - /// Deletes DeliveryTime - /// - /// DeliveryTime - public virtual void DeleteDeliveryTime(DeliveryTime deliveryTime) + public virtual void DeleteDeliveryTime(DeliveryTime deliveryTime) { if (deliveryTime == null) throw new ArgumentNullException("deliveryTime"); if (this.IsAssociated(deliveryTime.Id)) - throw new SmartException("The delivery time cannot be deleted. It has associated product variants"); + throw new SmartException(T("Admin.Configuration.DeliveryTimes.CannotDeleteAssignedProducts")); _deliveryTimeRepository.Delete(deliveryTime); - - _requestCache.RemoveByPattern(DELIVERYTIMES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(deliveryTime); } public virtual bool IsAssociated(int deliveryTimeId) @@ -101,90 +69,69 @@ from p in _productRepository.Table return query.Count() > 0; } - /// - /// Gets a DeliveryTime - /// - /// DeliveryTime identifier - /// DeliveryTime public virtual DeliveryTime GetDeliveryTimeById(int deliveryTimeId) { if (deliveryTimeId == 0) - return null; - - return _deliveryTimeRepository.GetById(deliveryTimeId); + { + if (_catalogSettings.ShowDefaultDeliveryTime) + { + return GetDefaultDeliveryTime(); + } + else + { + return null; + } + } + + return _deliveryTimeRepository.GetByIdCached(deliveryTimeId, "deliverytime-{0}".FormatInvariant(deliveryTimeId)); } - /// - /// Gets the delivery time for a product - /// - /// The product - /// Delivery time - 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); } - /// - /// Gets all delivery times - /// - /// A value indicating whether to show hidden records - /// DeliveryTime collection public virtual IList GetAllDeliveryTimes() { - string key = string.Format(DELIVERYTIMES_ALL_KEY); - return _requestCache.Get(key, () => - { - var query = _deliveryTimeRepository.Table; - query = query.OrderBy(c => c.DisplayOrder); - var deliveryTimes = query.ToList(); - return deliveryTimes; - }); - } + var query = _deliveryTimeRepository.Table.OrderBy(c => c.DisplayOrder); + var deliveryTimes = query.ToListCached("db.delivtimes.all"); + return deliveryTimes; + } - /// - /// Inserts a DeliveryTime - /// - /// DeliveryTime public virtual void InsertDeliveryTime(DeliveryTime deliveryTime) { if (deliveryTime == null) throw new ArgumentNullException("deliveryTime"); _deliveryTimeRepository.Insert(deliveryTime); - - _requestCache.RemoveByPattern(DELIVERYTIMES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(deliveryTime); } - /// - /// Updates the DeliveryTime - /// - /// DeliveryTime public virtual void UpdateDeliveryTime(DeliveryTime deliveryTime) { if (deliveryTime == null) throw new ArgumentNullException("deliveryTime"); _deliveryTimeRepository.Update(deliveryTime); - - _requestCache.RemoveByPattern(DELIVERYTIMES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(deliveryTime); } - #endregion + public virtual void SetToDefault(DeliveryTime deliveryTime) + { + if (deliveryTime == null) + throw new ArgumentNullException("deliveryTime"); + + var deliveryTimes = GetAllDeliveryTimes(); + foreach(var time in deliveryTimes) + { + time.IsDefault = time.Equals(deliveryTime) ? true : false; + _deliveryTimeRepository.Update(time); + } + } + + public virtual DeliveryTime GetDefaultDeliveryTime() + { + return _deliveryTimeRepository.Table.Where(x => x.IsDefault == true).FirstOrDefault(); + } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/EcbExchangeRateProvider.cs b/src/Libraries/SmartStore.Services/Directory/EcbExchangeRateProvider.cs index 7c43f0a2c1..2e941fadff 100644 --- a/src/Libraries/SmartStore.Services/Directory/EcbExchangeRateProvider.cs +++ b/src/Libraries/SmartStore.Services/Directory/EcbExchangeRateProvider.cs @@ -1,66 +1,112 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Net; using System.Xml; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Plugins; -using SmartStore.Services.Localization; namespace SmartStore.Services.Directory { [SystemName("CurrencyExchange.ECB")] - [FriendlyName("ECB exchange rate provider")] + [FriendlyName("ECB currency exchange rate provider")] [DisplayOrder(0)] public class EcbExchangeRateProvider : IExchangeRateProvider { - private readonly ILocalizationService _localizationService; - - public EcbExchangeRateProvider(ILocalizationService localizationService) + /// + /// Gets live currency exchange rates + /// + /// Exchange rate currency code + /// Currency exchange rates + public IList GetCurrencyLiveRates(string exchangeRateCurrencyCode) { - this._localizationService = localizationService; - } + if (exchangeRateCurrencyCode.IsEmpty()) + return new List(); - /// - /// Gets currency live rates - /// - /// Exchange rate currency code - /// Exchange rates - public IList GetCurrencyLiveRates(string exchangeRateCurrencyCode) - { - if (String.IsNullOrEmpty(exchangeRateCurrencyCode) || - exchangeRateCurrencyCode.ToLower() != "eur") - throw new SmartException(_localizationService.GetResource("Providers.ExchangeRate.EcbExchange.SetCurrencyToEURO")); + var result = new Dictionary(); + var targetCurrency = exchangeRateCurrencyCode.ToUpper(); + var updateDate = DateTime.UtcNow; - var exchangeRates = new List(); + // old url still works too: http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml + var request = WebRequest.Create("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml") as HttpWebRequest; - var request = WebRequest.Create("http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml") as HttpWebRequest; - using (WebResponse response = request.GetResponse()) + using (var response = request.GetResponse()) { var document = new XmlDocument(); document.Load(response.GetResponseStream()); + var nsmgr = new XmlNamespaceManager(document.NameTable); nsmgr.AddNamespace("ns", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref"); nsmgr.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01"); var node = document.SelectSingleNode("gesmes:Envelope/ns:Cube/ns:Cube", nsmgr); - var updateDate = DateTime.ParseExact(node.Attributes["time"].Value, "yyyy-MM-dd", null); + var ecbDate = DateTime.ParseExact(node.Attributes["time"].Value, "yyyy-MM-dd", null); - var provider = new NumberFormatInfo(); + // see https://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html + updateDate = new DateTime(ecbDate.Year, ecbDate.Month, ecbDate.Day, 16, 0, 0); + + var provider = new NumberFormatInfo(); provider.NumberDecimalSeparator = "."; provider.NumberGroupSeparator = ""; - foreach (XmlNode node2 in node.ChildNodes) + + foreach (XmlNode childNode in node.ChildNodes) { - exchangeRates.Add(new Core.Domain.Directory.ExchangeRate() - { - CurrencyCode = node2.Attributes["currency"].Value, - Rate = decimal.Parse(node2.Attributes["rate"].Value, provider), - UpdatedOn = updateDate - } - ); + // parse ECB rate + var rate = new ExchangeRate + { + CurrencyCode = childNode.Attributes["currency"].Value.EmptyNull().ToUpper(), + Rate = decimal.Parse(childNode.Attributes["rate"].Value, provider), + UpdatedOn = updateDate + }; + + // add it to result dictionary + if (!result.ContainsKey(rate.CurrencyCode) && rate.Rate != decimal.Zero) + { + result.Add(rate.CurrencyCode, rate); + } } } - return exchangeRates; - } + // if target currency is EUR then we are ready because ECB always returns EUR rates + if (targetCurrency != "EUR") + { + // cross calculation not possible if ECB list does not contain target currency + if (!result.ContainsKey(targetCurrency)) + return new List(); + + // get clone of EUR rates + var euroRates = result.ToDictionary(x => x.Key, x => x.Value.Rate); + + // cross calculate rates for target currency + result.Each(x => + { + if (x.Value.CurrencyCode == "EUR") + { + x.Value.Rate = 1.0M / euroRates[targetCurrency]; + } + else + { + x.Value.Rate = euroRates[x.Value.CurrencyCode] / euroRates[targetCurrency]; + } + }); + + // remove target currency form result because its rate is always 1.0 + result.Remove(targetCurrency); + + // add EUR rate + if (!result.ContainsKey("EUR")) + { + result.Add("EUR", new ExchangeRate + { + CurrencyCode = "EUR", + Rate = 1.0M / euroRates[targetCurrency], + UpdatedOn = updateDate + }); + } + } + + return result.Select(x => x.Value).ToList(); + } } } diff --git a/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs b/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs index 11a2ab20ce..71ec189d47 100644 --- a/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs +++ b/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs @@ -1,129 +1,58 @@ using System; -using System.Diagnostics; -using System.IO; using System.Net; +using MaxMind.GeoIP2; using SmartStore.Core; using SmartStore.Core.Caching; -using MaxMind.GeoIP; +using SmartStore.Utilities; using SmDir = SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Directory { - /// - /// Country lookup helper - /// - 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/ICountryService.cs b/src/Libraries/SmartStore.Services/Directory/ICountryService.cs index d91346a4ff..001d2d55fd 100644 --- a/src/Libraries/SmartStore.Services/Directory/ICountryService.cs +++ b/src/Libraries/SmartStore.Services/Directory/ICountryService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using SmartStore.Core.Domain.Directory; @@ -74,5 +75,5 @@ public partial interface ICountryService /// /// Country void UpdateCountry(Country country); - } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs b/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs index 65f633fed4..14be063172 100644 --- a/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs +++ b/src/Libraries/SmartStore.Services/Directory/ICurrencyService.cs @@ -71,11 +71,11 @@ public partial interface ICurrencyService /// Converts currency /// /// Amount - /// Source currency code - /// Target currency code + /// Source currency code + /// Target currency code /// Store to get the primary currencies from /// Converted value - decimal ConvertCurrency(decimal amount, Currency sourceCurrencyCode, Currency targetCurrencyCode, Store store = null); + decimal ConvertCurrency(decimal amount, Currency sourceCurrency, Currency targetCurrency, Store store = null); /// /// Converts to primary exchange rate currency @@ -90,28 +90,28 @@ public partial interface ICurrencyService /// Converts from primary exchange rate currency /// /// Amount - /// Target currency code + /// Target currency code /// Store to get the primary exchange rate currency from /// Converted value - decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrencyCode, Store store = null); + decimal ConvertFromPrimaryExchangeRateCurrency(decimal amount, Currency targetCurrency, Store store = null); /// /// Converts to primary store currency /// /// Amount - /// Source currency code + /// Source currency code /// Store to get the primary store currency from /// Converted value - decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrencyCode, Store store = null); + decimal ConvertToPrimaryStoreCurrency(decimal amount, Currency sourceCurrency, Store store = null); /// /// Converts from primary store currency /// /// Amount - /// Target currency code + /// Target currency code /// Store to get the primary store currency from /// Converted value - decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrencyCode, Store store = null); + decimal ConvertFromPrimaryStoreCurrency(decimal amount, Currency targetCurrency, Store store = null); diff --git a/src/Libraries/SmartStore.Services/Directory/IDeliveryTimeService.cs b/src/Libraries/SmartStore.Services/Directory/IDeliveryTimeService.cs index 884d4ab2dd..36663e2e6c 100644 --- a/src/Libraries/SmartStore.Services/Directory/IDeliveryTimeService.cs +++ b/src/Libraries/SmartStore.Services/Directory/IDeliveryTimeService.cs @@ -52,5 +52,18 @@ public partial interface IDeliveryTimeService /// /// DeliveryTime void UpdateDeliveryTime(DeliveryTime deliveryTime); + + /// + /// Updates a delivery time + /// + /// DeliveryTime + void SetToDefault(DeliveryTime deliveryTime); + + /// + /// Gets the default delivery time + /// + /// The product + /// Delivery time + DeliveryTime GetDefaultDeliveryTime(); } } \ 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/McExchangeRateProvider.cs b/src/Libraries/SmartStore.Services/Directory/McExchangeRateProvider.cs deleted file mode 100644 index 13fa8f8ee9..0000000000 --- a/src/Libraries/SmartStore.Services/Directory/McExchangeRateProvider.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Xml; -using SmartStore.Core.Plugins; -using SmartStore.Services.Localization; -using SmartStore.Core.Logging; - -namespace SmartStore.Services.Directory -{ - [SystemName("CurrencyExchange.MoneyConverter")] - [FriendlyName("Money converter exchange rate provider")] - [DisplayOrder(1)] - public class McExchangeRateProvider : IExchangeRateProvider - { - private readonly ILogger _logger; - - public McExchangeRateProvider(ILogger logger) - { - this._logger = logger; - } - - /// - /// Gets currency live rates - /// - /// Exchange rate currency code - /// Exchange rates - public IList GetCurrencyLiveRates(string exchangeRateCurrencyCode) { - var exchangeRates = new List(); - string url = string.Format("http://themoneyconverter.com/rss-feed/{0}/rss.xml", exchangeRateCurrencyCode); - - var request = WebRequest.Create(url) as HttpWebRequest; - using (var response = request.GetResponse()) { - var xmlDoc = new XmlDocument(); - xmlDoc.Load(response.GetResponseStream()); - var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); - nsmgr.AddNamespace("atom", "http://www.w3.org/2005/Atom"); - nsmgr.AddNamespace("cf", "http://www.microsoft.com/schemas/rss/core/2005"); - nsmgr.AddNamespace("cfi", "http://www.microsoft.com/schemas/rss/core/2005/internal"); - - foreach (XmlNode node in xmlDoc.DocumentElement.FirstChild.ChildNodes) { - if (node.Name == "item") { - /* - - AED/NZD - http://themoneyconverter.com/NZD/AED.aspx - http://themoneyconverter.com/NZD/AED.aspx - Fri, 20 Feb 2009 08:01:41 GMT - 2009-02-20T08:01:41Z - 2009-02-20T08:01:41Z - 1 New Zealand Dollar = 1.84499 Arab Emirates Dirham - Middle-East - 32 - 1074571199 - true - http://themoneyconverter.com/NZD/rss.xml - 2009-02-20T08:05:27.168Z - - */ - try { - var rate = new Core.Domain.Directory.ExchangeRate(); - foreach (XmlNode detailNode in node.ChildNodes) { - switch (detailNode.Name) { - case "title": - rate.CurrencyCode = detailNode.InnerText.Substring(0, 3); - break; - - case "pubDate": - rate.UpdatedOn = DateTime.Parse(detailNode.InnerText, CultureInfo.InvariantCulture); - break; - - case "description": - string description = detailNode.InnerText; - int x = description.IndexOf('='); - int y = description.IndexOf(' ', x + 2); - - // 1 2 3 4 - //01234567890123456789012345678901234567890 - // 1 New Zealand Dollar = 0.78815 Australian Dollar - // x = 21 - // y = 30 - string rateText = description.Substring(x + 1, y - x - 1).Trim(); - rate.Rate = decimal.Parse(rateText, CultureInfo.InvariantCulture); - break; - - default: - break; - } - } - - // Update the Rate in the collection if its already in there - if (rate.CurrencyCode != null) { - exchangeRates.Add(rate); - } - } - catch (Exception exc) { - _logger.Warning(string.Format("Error parsing currency rates (MC): {0}", exc.Message), exc); - } - } // if node is an item - } // foreach child node under - } - - return exchangeRates; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Directory/MeasureService.cs b/src/Libraries/SmartStore.Services/Directory/MeasureService.cs index e6cf9e2bef..1ff51e67a9 100644 --- a/src/Libraries/SmartStore.Services/Directory/MeasureService.cs +++ b/src/Libraries/SmartStore.Services/Directory/MeasureService.cs @@ -6,102 +6,47 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Events; +using SmartStore.Data.Caching; namespace SmartStore.Services.Directory { - /// - /// Measure dimension service - /// public partial class MeasureService : IMeasureService { - #region Constants - private const string MEASUREDIMENSIONS_ALL_KEY = "SmartStore.measuredimension.all"; - private const string MEASUREWEIGHTS_ALL_KEY = "SmartStore.measureweight.all"; - private const string MEASUREDIMENSIONS_PATTERN_KEY = "SmartStore.measuredimension."; - private const string MEASUREWEIGHTS_PATTERN_KEY = "SmartStore.measureweight."; - private const string MEASUREDIMENSIONS_BY_ID_KEY = "SmartStore.measuredimension.id-{0}"; - private const string MEASUREWEIGHTS_BY_ID_KEY = "SmartStore.measureweight.id-{0}"; - #endregion - - #region Fields - private readonly IRepository _measureDimensionRepository; private readonly IRepository _measureWeightRepository; - private readonly IRequestCache _requestCache; private readonly MeasureSettings _measureSettings; private readonly IEventPublisher _eventPublisher; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Dimension repository - /// Weight repository - /// Measure settings - /// Event published - public MeasureService(IRequestCache requestCache, + public MeasureService( IRepository measureDimensionRepository, IRepository measureWeightRepository, MeasureSettings measureSettings, IEventPublisher eventPublisher) { - _requestCache = requestCache; _measureDimensionRepository = measureDimensionRepository; _measureWeightRepository = measureWeightRepository; _measureSettings = measureSettings; _eventPublisher = eventPublisher; } - #endregion - - #region Methods - #region Dimensions - /// - /// Deletes measure dimension - /// - /// Measure dimension public virtual void DeleteMeasureDimension(MeasureDimension measureDimension) { if (measureDimension == null) throw new ArgumentNullException("measureDimension"); _measureDimensionRepository.Delete(measureDimension); - - _requestCache.RemoveByPattern(MEASUREDIMENSIONS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(measureDimension); } - - /// - /// Gets a measure dimension by identifier - /// - /// Measure dimension identifier - /// Measure dimension public virtual MeasureDimension GetMeasureDimensionById(int measureDimensionId) { if (measureDimensionId == 0) return null; - string key = string.Format(MEASUREDIMENSIONS_BY_ID_KEY, measureDimensionId); - return _requestCache.Get(key, () => - { - return _measureDimensionRepository.GetById(measureDimensionId); - }); - } + return _measureDimensionRepository.GetByIdCached(measureDimensionId, "db.dimension.id-" + measureDimensionId); + } - /// - /// Gets a measure dimension by system keyword - /// - /// The system keyword - /// Measure dimension public virtual MeasureDimension GetMeasureDimensionBySystemKeyword(string systemKeyword) { if (String.IsNullOrEmpty(systemKeyword)) @@ -114,66 +59,31 @@ public virtual MeasureDimension GetMeasureDimensionBySystemKeyword(string system return null; } - /// - /// Gets all measure dimensions - /// - /// Measure dimension collection public virtual IList GetAllMeasureDimensions() { - string key = MEASUREDIMENSIONS_ALL_KEY; - return _requestCache.Get(key, () => - { - var query = from md in _measureDimensionRepository.Table - orderby md.DisplayOrder - select md; - var measureDimensions = query.ToList(); - return measureDimensions; - - }); - } + var query = from md in _measureDimensionRepository.Table + orderby md.DisplayOrder + select md; + var measureDimensions = query.ToListCached("db.dimension.all"); + return measureDimensions; + } - /// - /// Inserts a measure dimension - /// - /// Measure dimension public virtual void InsertMeasureDimension(MeasureDimension measure) { if (measure == null) throw new ArgumentNullException("measure"); _measureDimensionRepository.Insert(measure); - - _requestCache.RemoveByPattern(MEASUREDIMENSIONS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(measure); } - /// - /// Updates the measure dimension - /// - /// Measure dimension public virtual void UpdateMeasureDimension(MeasureDimension measure) { if (measure == null) throw new ArgumentNullException("measure"); _measureDimensionRepository.Update(measure); - - _requestCache.RemoveByPattern(MEASUREDIMENSIONS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(measure); } - /// - /// Converts dimension - /// - /// Quantity - /// Source dimension - /// Target dimension - /// A value indicating whether a result should be rounded - /// Converted value public virtual decimal ConvertDimension(decimal quantity, MeasureDimension sourceMeasureDimension, MeasureDimension targetMeasureDimension, bool round = true) { @@ -188,12 +98,6 @@ public virtual decimal ConvertDimension(decimal quantity, return result; } - /// - /// Converts to primary measure dimension - /// - /// Quantity - /// Source dimension - /// Converted value public virtual decimal ConvertToPrimaryMeasureDimension(decimal quantity, MeasureDimension sourceMeasureDimension) { @@ -209,12 +113,6 @@ public virtual decimal ConvertToPrimaryMeasureDimension(decimal quantity, return result; } - /// - /// Converts from primary dimension - /// - /// Quantity - /// Target dimension - /// Converted value public virtual decimal ConvertFromPrimaryMeasureDimension(decimal quantity, MeasureDimension targetMeasureDimension) { @@ -234,118 +132,61 @@ public virtual decimal ConvertFromPrimaryMeasureDimension(decimal quantity, #region Weights - /// - /// Deletes measure weight - /// - /// Measure weight public virtual void DeleteMeasureWeight(MeasureWeight measureWeight) { if (measureWeight == null) throw new ArgumentNullException("measureWeight"); _measureWeightRepository.Delete(measureWeight); - - _requestCache.RemoveByPattern(MEASUREWEIGHTS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(measureWeight); } - /// - /// Gets a measure weight by identifier - /// - /// Measure weight identifier - /// Measure weight public virtual MeasureWeight GetMeasureWeightById(int measureWeightId) { if (measureWeightId == 0) return null; - string key = string.Format(MEASUREWEIGHTS_BY_ID_KEY, measureWeightId); - return _requestCache.Get(key, () => - { - return _measureWeightRepository.GetById(measureWeightId); - }); - } + return _measureWeightRepository.GetByIdCached(measureWeightId, "db.weight.id-" + measureWeightId); + } - /// - /// Gets a measure weight by system keyword - /// - /// The system keyword - /// Measure weight 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(); } - /// - /// Gets all measure weights - /// - /// Measure weight collection public virtual IList GetAllMeasureWeights() { - string key = MEASUREWEIGHTS_ALL_KEY; - return _requestCache.Get(key, () => - { - var query = from mw in _measureWeightRepository.Table - orderby mw.DisplayOrder - select mw; - var measureWeights = query.ToList(); - return measureWeights; - }); - } + var query = from mw in _measureWeightRepository.Table + orderby mw.DisplayOrder + select mw; + + var measureWeights = query.ToListCached("db.weight.all"); + return measureWeights; + } - /// - /// Inserts a measure weight - /// - /// Measure weight public virtual void InsertMeasureWeight(MeasureWeight measure) { if (measure == null) throw new ArgumentNullException("measure"); _measureWeightRepository.Insert(measure); - - _requestCache.RemoveByPattern(MEASUREWEIGHTS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(measure); } - /// - /// Updates the measure weight - /// - /// Measure weight public virtual void UpdateMeasureWeight(MeasureWeight measure) { if (measure == null) throw new ArgumentNullException("measure"); _measureWeightRepository.Update(measure); - - _requestCache.RemoveByPattern(MEASUREWEIGHTS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(measure); } - /// - /// Converts weight - /// - /// Quantity - /// Source weight - /// Target weight - /// A value indicating whether a result should be rounded - /// Converted value - 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) @@ -353,17 +194,13 @@ public virtual decimal ConvertWeight(decimal quantity, result = ConvertToPrimaryMeasureWeight(result, sourceMeasureWeight); result = ConvertFromPrimaryMeasureWeight(result, targetMeasureWeight); } + if (round) result = Math.Round(result, 2); + return result; } - /// - /// Converts to primary measure weight - /// - /// Quantity - /// Source weight - /// Converted value public virtual decimal ConvertToPrimaryMeasureWeight(decimal quantity, MeasureWeight sourceMeasureWeight) { decimal result = quantity; @@ -378,17 +215,11 @@ public virtual decimal ConvertToPrimaryMeasureWeight(decimal quantity, MeasureWe return result; } - /// - /// Converts from primary weight - /// - /// Quantity - /// Target weight - /// Converted value - 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; @@ -396,11 +227,10 @@ 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; } #endregion - - #endregion } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/QuantityUnitService.cs b/src/Libraries/SmartStore.Services/Directory/QuantityUnitService.cs index 4fc1254eb1..1048718152 100644 --- a/src/Libraries/SmartStore.Services/Directory/QuantityUnitService.cs +++ b/src/Libraries/SmartStore.Services/Directory/QuantityUnitService.cs @@ -1,48 +1,23 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Events; -using SmartStore.Core.Plugins; +using SmartStore.Data.Caching; using SmartStore.Services.Customers; namespace SmartStore.Services.Directory { - /// - /// QuantityUnit service - /// public partial class QuantityUnitService : IQuantityUnitService { - #region Constants - private const string MEASUREUNITS_ALL_KEY = "SMN.quantityunits.all"; - private const string MEASUREUNITS_PATTERN_KEY = "SMN.quantityunit."; - #endregion - - #region Fields - private readonly IRepository _quantityUnitRepository; private readonly IRepository _productRepository; - private readonly IRequestCache _requestCache; private readonly IEventPublisher _eventPublisher; private readonly CatalogSettings _catalogSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// QuantityUnit repository - /// Customer service - /// Currency settings - /// Plugin finder - /// Event published - public QuantityUnitService(IRequestCache requestCache, + + public QuantityUnitService( IRepository quantityUnitRepository, IRepository productRepository, IRepository attributeCombinationRepository, @@ -50,21 +25,12 @@ public QuantityUnitService(IRequestCache requestCache, IEventPublisher eventPublisher, CatalogSettings catalogSettings) { - this._requestCache = requestCache; this._quantityUnitRepository = quantityUnitRepository; this._eventPublisher = eventPublisher; this._productRepository = productRepository; this._catalogSettings = catalogSettings; } - #endregion - - #region Methods - - /// - /// Deletes QuantityUnit - /// - /// QuantityUnit public virtual void DeleteQuantityUnit(QuantityUnit quantityUnit) { if (quantityUnit == null) @@ -74,11 +40,6 @@ public virtual void DeleteQuantityUnit(QuantityUnit quantityUnit) throw new SmartException("The quantity unit cannot be deleted. It has associated product variants"); _quantityUnitRepository.Delete(quantityUnit); - - _requestCache.RemoveByPattern(MEASUREUNITS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(quantityUnit); } public virtual bool IsAssociated(int quantityUnitId) @@ -94,11 +55,6 @@ from p in _productRepository.Table return query.Count() > 0; } - /// - /// Gets a QuantityUnit - /// - /// QuantityUnit identifier - /// QuantityUnit public virtual QuantityUnit GetQuantityUnitById(int? quantityUnitId) { if (quantityUnitId == null || quantityUnitId == 0) @@ -116,11 +72,6 @@ public virtual QuantityUnit GetQuantityUnitById(int? quantityUnitId) return _quantityUnitRepository.GetById(quantityUnitId); } - /// - /// Gets the measure unit for a product - /// - /// The product - /// QuantityUnit public virtual QuantityUnit GetQuantityUnit(Product product) { if (product == null) @@ -129,44 +80,22 @@ public virtual QuantityUnit GetQuantityUnit(Product product) return GetQuantityUnitById(product.QuantityUnitId ?? 0); } - /// - /// Gets all measure units - /// - /// A value indicating whether to show hidden records - /// QuantityUnit collection public virtual IList GetAllQuantityUnits() { - string key = string.Format(MEASUREUNITS_ALL_KEY); - return _requestCache.Get(key, () => - { - var query = _quantityUnitRepository.Table; - query = query.OrderBy(c => c.DisplayOrder); - var quantityUnits = query.ToList(); - return quantityUnits; - }); - } + var query = _quantityUnitRepository.Table.OrderBy(c => c.DisplayOrder); + + var quantityUnits = query.ToListCached("db.qtyunit.all"); + return quantityUnits; + } - /// - /// Inserts a QuantityUnit - /// - /// QuantityUnit public virtual void InsertQuantityUnit(QuantityUnit quantityUnit) { if (quantityUnit == null) throw new ArgumentNullException("quantityUnit"); _quantityUnitRepository.Insert(quantityUnit); - - _requestCache.RemoveByPattern(MEASUREUNITS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(quantityUnit); } - /// - /// Updates the QuantityUnit - /// - /// QuantityUnit public virtual void UpdateQuantityUnit(QuantityUnit quantityUnit) { if (quantityUnit == null) @@ -189,14 +118,6 @@ public virtual void UpdateQuantityUnit(QuantityUnit quantityUnit) } _quantityUnitRepository.Update(quantityUnit); - - _requestCache.RemoveByPattern(MEASUREUNITS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(quantityUnit); } - - #endregion - } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs b/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs index be561b3240..db65144d52 100644 --- a/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs +++ b/src/Libraries/SmartStore.Services/Directory/StateProvinceService.cs @@ -1,67 +1,32 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Events; +using SmartStore.Data.Caching; namespace SmartStore.Services.Directory { - /// - /// State province service - /// public partial class StateProvinceService : IStateProvinceService { - #region Constants - private const string STATEPROVINCES_ALL_KEY = "SmartStore.stateprovince.all-{0}"; - private const string STATEPROVINCES_PATTERN_KEY = "SmartStore.stateprovince."; - #endregion - - #region Fields - private readonly IRepository _stateProvinceRepository; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - - #endregion - - #region Ctor - /// - /// Ctor - /// - /// Cache manager - /// State/province repository - /// Event published - public StateProvinceService(IRequestCache requestCache, + public StateProvinceService( IRepository stateProvinceRepository, IEventPublisher eventPublisher) { - _requestCache = requestCache; _stateProvinceRepository = stateProvinceRepository; _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - /// - /// Deletes a state/province - /// - /// The state/province public virtual void DeleteStateProvince(StateProvince stateProvince) { if (stateProvince == null) throw new ArgumentNullException("stateProvince"); _stateProvinceRepository.Delete(stateProvince); - - _requestCache.RemoveByPattern(STATEPROVINCES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(stateProvince); } public virtual IQueryable GetAllStateProvinces(bool showHidden = false) @@ -74,11 +39,6 @@ public virtual IQueryable GetAllStateProvinces(bool showHidden = return query; } - /// - /// Gets a state/province - /// - /// The state/province identifier - /// State/province public virtual StateProvince GetStateProvinceById(int stateProvinceId) { if (stateProvinceId == 0) @@ -87,11 +47,6 @@ public virtual StateProvince GetStateProvinceById(int stateProvinceId) return _stateProvinceRepository.GetById(stateProvinceId); } - /// - /// Gets a state/province - /// - /// The state/province abbreviation - /// State/province public virtual StateProvince GetStateProvinceByAbbreviation(string abbreviation) { var query = from sp in _stateProvinceRepository.Table @@ -101,61 +56,32 @@ public virtual StateProvince GetStateProvinceByAbbreviation(string abbreviation) return stateProvince; } - /// - /// Gets a state/province collection by country identifier - /// - /// Country identifier - /// A value indicating whether to show hidden records - /// State/province collection public virtual IList GetStateProvincesByCountryId(int countryId, bool showHidden = false) { - string key = string.Format(STATEPROVINCES_ALL_KEY, countryId); - return _requestCache.Get(key, () => - { - var query = from sp in _stateProvinceRepository.Table - orderby sp.DisplayOrder - where sp.CountryId == countryId && - (showHidden || sp.Published) - select sp; - var stateProvinces = query.ToList(); - return stateProvinces; - }); - } + var query = from sp in _stateProvinceRepository.Table + orderby sp.DisplayOrder + where sp.CountryId == countryId && + (showHidden || sp.Published) + select sp; + + var stateProvinces = query.ToListCached("db.regions.{0}.{1}".FormatInvariant(countryId, showHidden)); + return stateProvinces; + } - /// - /// Inserts a state/province - /// - /// State/province public virtual void InsertStateProvince(StateProvince stateProvince) { if (stateProvince == null) throw new ArgumentNullException("stateProvince"); _stateProvinceRepository.Insert(stateProvince); - - _requestCache.RemoveByPattern(STATEPROVINCES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(stateProvince); } - /// - /// Updates a state/province - /// - /// State/province public virtual void UpdateStateProvince(StateProvince stateProvince) { if (stateProvince == null) throw new ArgumentNullException("stateProvince"); _stateProvinceRepository.Update(stateProvince); - - _requestCache.RemoveByPattern(STATEPROVINCES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(stateProvince); } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs b/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs index 0882af90c5..b96dce8613 100644 --- a/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs +++ b/src/Libraries/SmartStore.Services/Directory/UpdateExchangeRateTask.cs @@ -37,7 +37,7 @@ public void Execute(TaskExecutionContext ctx) if (lastUpdateTime.AddHours(1) < DateTime.UtcNow) { - //update rates each one hour + // update rates every hour var exchangeRates = _currencyService.GetCurrencyLiveRates(_services.StoreContext.CurrentStore.PrimaryExchangeRateCurrency.CurrencyCode); foreach (var exchageRate in exchangeRates) @@ -45,13 +45,15 @@ public void Execute(TaskExecutionContext ctx) var currency = _currencyService.GetCurrencyByCode(exchageRate.CurrencyCode); if (currency != null) { - currency.Rate = exchageRate.Rate; - currency.UpdatedOnUtc = DateTime.UtcNow; - _currencyService.UpdateCurrency(currency); + if (currency.Rate != exchageRate.Rate) + { + currency.Rate = exchageRate.Rate; + _currencyService.UpdateCurrency(currency); + } } } - //save new update time value + // save new update time value _currencySettings.LastUpdateTime = DateTime.UtcNow.ToBinary(); _services.Settings.SaveSetting(_currencySettings); } 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 a693dcf830..660e5f23a1 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -1,32 +1,23 @@ 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.Services.Orders; +using SmartStore.Services.Customers; namespace SmartStore.Services.Discounts { - /// - /// Discount service - /// public partial class DiscountService : IDiscountService { - #region Constants private const string DISCOUNTS_ALL_KEY = "SmartStore.discount.all-{0}-{1}"; - private const string DISCOUNTS_PATTERN_KEY = "SmartStore.discount."; - #endregion - - #region Fields + private const string DISCOUNTS_PATTERN_KEY = "SmartStore.discount.*"; private readonly IRepository _discountRepository; private readonly IRepository _discountRequirementRepository; @@ -34,52 +25,37 @@ 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; - #endregion - - #region Ctor - - 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) { - this._requestCache = requestCache; - this._discountRepository = discountRepository; - this._discountRequirementRepository = discountRequirementRepository; - this._discountUsageHistoryRepository = discountUsageHistoryRepository; - this._storeContext = storeContext; - this._genericAttributeService = genericAttributeService; - this._pluginFinder = pluginFinder; - this._eventPublisher = eventPublisher; - this._settingService = settingService; - this._providerManager = providerManager; - } - - #endregion - - #region Utilities - - /// - /// Checks discount limitation for customer - /// - /// Discount - /// Customer - /// Value indicating whether discount can be used - protected virtual bool CheckDiscountLimitations(Discount discount, Customer customer) + _requestCache = requestCache; + _discountRepository = discountRepository; + _discountRequirementRepository = discountRequirementRepository; + _discountUsageHistoryRepository = discountUsageHistoryRepository; + _storeContext = storeContext; + _genericAttributeService = genericAttributeService; + _providerManager = providerManager; + _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) { - if (discount == null) - throw new ArgumentNullException("discount"); + Guard.NotNull(discount, nameof(discount)); switch (discount.DiscountLimitation) { @@ -112,32 +88,14 @@ protected virtual bool CheckDiscountLimitations(Discount discount, Customer cust return false; } - #endregion - - #region Methods - - /// - /// Delete discount - /// - /// Discount 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); - - //event notification - _eventPublisher.EntityDeleted(discount); } - /// - /// Gets a discount - /// - /// Discount identifier - /// Discount public virtual Discount GetDiscountById(int discountId) { if (discountId == 0) @@ -146,129 +104,89 @@ public virtual Discount GetDiscountById(int discountId) return _discountRepository.GetById(discountId); } - /// - /// Gets all discounts - /// - /// Discount type; null to load all discount - /// A value indicating whether to show hidden records - /// Discount collection - public virtual IList GetAllDiscounts(DiscountType? discountType, string couponCode = "", bool showHidden = false) + public virtual IEnumerable GetAllDiscounts(DiscountType? discountType, string couponCode = "", bool showHidden = false) { int? discountTypeId = null; if (discountType.HasValue) discountTypeId = (int)discountType.Value; - //we load all discounts, and filter them by passed "discountType" parameter later - //we do it because we know that this method is invoked several times per HTTP request with distinct "discountType" parameter - //that's why let's access the database only once + // we load all discounts, and filter them by passed "discountType" parameter later + // we do it because we know that this method is invoked several times per HTTP request with distinct "discountType" parameter + // that's why we access the database only once string key = string.Format(DISCOUNTS_ALL_KEY, showHidden, couponCode); var result = _requestCache.Get(key, () => { var query = _discountRepository.Table; + if (!showHidden) { - //The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. - //That's why we pass the date value - var nowUtc = DateTime.UtcNow; + // The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. + // That's why we pass the date value + var nowUtc = DateTime.UtcNow.Date; query = query.Where(d => (!d.StartDateUtc.HasValue || d.StartDateUtc <= nowUtc) - && (!d.EndDateUtc.HasValue || d.EndDateUtc >= nowUtc) - ); + && (!d.EndDateUtc.HasValue || d.EndDateUtc >= nowUtc)); } + if (!String.IsNullOrWhiteSpace(couponCode)) { couponCode = couponCode.Trim(); - query = query.Where(d => d.CouponCode == couponCode); } + query = query.OrderByDescending(d => d.Id); var discounts = query.ToList(); - return discounts; + + var map = new Multimap(); + discounts.Each(x => map.Add(x.DiscountTypeId, x)); + + return map; }); - if (discountTypeId.HasValue && discountTypeId.Value > 0) + + if (discountTypeId > 0) { - result = result.Where(d => d.DiscountTypeId == discountTypeId).ToList(); + return result[discountTypeId.Value]; } - return result; + + return result.SelectMany(x => x.Value); } - /// - /// Inserts a discount - /// - /// Discount public virtual void InsertDiscount(Discount discount) { - if (discount == null) - throw new ArgumentNullException("discount"); + Guard.NotNull(discount, nameof(discount)); _discountRepository.Insert(discount); _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(discount); } - /// - /// Updates the 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); - - //event notification - _eventPublisher.EntityUpdated(discount); } - /// - /// Delete discount requirement - /// - /// Discount requirement 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); - - //event notification - _eventPublisher.EntityDeleted(discountRequirement); } - /// - /// Load discount requirement rule by system name - /// - /// System name - /// Found discount requirement rule public virtual Provider LoadDiscountRequirementRuleBySystemName(string systemName, int storeId = 0) { return _providerManager.GetProvider(systemName, storeId); } - /// - /// Load all discount requirement rules - /// - /// Discount requirement rules public virtual IEnumerable> LoadAllDiscountRequirementRules(int storeId = 0) { return _providerManager.GetAllProviders(storeId); } - /// - /// Get discount by coupon code - /// - /// CouponCode - /// A value indicating whether to show hidden records - /// Discount public virtual Discount GetDiscountByCouponCode(string couponCode, bool showHidden = false) { if (String.IsNullOrWhiteSpace(couponCode)) @@ -278,46 +196,39 @@ public virtual Discount GetDiscountByCouponCode(string couponCode, bool showHidd return discount; } - /// - /// Check discount requirements - /// - /// Discount - /// Customer - /// true - requirement is met; otherwise, false - public virtual bool IsDiscountValid(Discount discount, Customer customer) + public virtual bool IsDiscountValid(Discount discount, Customer customer) { - if (discount == null) - throw new ArgumentNullException("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; } - /// - /// Check discount requirements - /// - /// Discount - /// Customer - /// Coupon code to validate - /// true - requirement is met; otherwise, false public virtual bool IsDiscountValid(Discount discount, Customer customer, string couponCodeToValidate) { - if (discount == null) - throw new ArgumentNullException("discount"); + 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) + // Check coupon code + if (discount.RequiresCouponCode) { - if (String.IsNullOrEmpty(discount.CouponCode)) - return false; + if (discount.CouponCode.IsEmpty()) + return Cached(false); + if (!discount.CouponCode.Equals(couponCodeToValidate, StringComparison.InvariantCultureIgnoreCase)) - return false; + return Cached(false); } - //check date range + // Check date range var now = DateTime.UtcNow; var store = _storeContext.CurrentStore; @@ -325,27 +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.ShoppingCartItems - .Filter(ShoppingCartType.ShoppingCart, store.Id) - .ToList(); - - if (cart.Any(x => x.Product.IsGiftCard)) - return false; + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + if (cart.Any(x => x.Item?.Product != null && x.Item.Product.IsGiftCard)) + return Cached(false); } // discount requirements @@ -355,27 +264,27 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, store.Id); if (requirementRule == null) continue; - - var request = new CheckDiscountRequirementRequest + + var request = new CheckDiscountRequirementRequest { DiscountRequirement = req, Customer = customer, Store = store }; - // TODO: cache result... CheckRequirement is very often called if (!requirementRule.Value.CheckRequirement(request)) - return false; + return Cached(false); } - return true; + return Cached(true); + + bool Cached(bool value) + { + _discountValidityCache[cacheKey] = value; + return value; + } } - /// - /// Gets a discount usage history record - /// - /// Discount usage history record identifier - /// Discount usage history public virtual DiscountUsageHistory GetDiscountUsageHistoryById(int discountUsageHistoryId) { if (discountUsageHistoryId == 0) @@ -385,78 +294,50 @@ public virtual DiscountUsageHistory GetDiscountUsageHistoryById(int discountUsag return duh; } - /// - /// Gets all discount usage history records - /// - /// Discount identifier - /// Customer identifier - /// Page index - /// Page size - /// Discount usage history records - public virtual IPagedList GetAllDiscountUsageHistory(int? discountId, - int? customerId, int pageIndex, int pageSize) + public virtual IPagedList GetAllDiscountUsageHistory(int? discountId, int? customerId, int pageIndex, int pageSize) { var query = _discountUsageHistoryRepository.Table; + if (discountId.HasValue && discountId.Value > 0) query = query.Where(duh => duh.DiscountId == discountId.Value); + if (customerId.HasValue && customerId.Value > 0) query = query.Where(duh => duh.Order != null && duh.Order.CustomerId == customerId.Value); + query = query.OrderByDescending(c => c.CreatedOnUtc); return new PagedList(query, pageIndex, pageSize); } - /// - /// Insert discount usage history record - /// - /// Discount usage history record 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); - - //event notification - _eventPublisher.EntityInserted(discountUsageHistory); } - - /// - /// Update discount usage history record - /// - /// Discount usage history record 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); - - //event notification - _eventPublisher.EntityUpdated(discountUsageHistory); } - /// - /// Delete discount usage history record - /// - /// Discount usage history record 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); - - //event notification - _eventPublisher.EntityDeleted(discountUsageHistory); } - #endregion + class DiscountKey : Tuple + { + public DiscountKey(Discount discount, Customer customer, string customerCouponCode) + : base(discount, customer, customerCouponCode) + { + } + } } } diff --git a/src/Libraries/SmartStore.Services/Discounts/IDiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/IDiscountService.cs index 8302251085..8aa55980f7 100644 --- a/src/Libraries/SmartStore.Services/Discounts/IDiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/IDiscountService.cs @@ -25,13 +25,13 @@ public partial interface IDiscountService /// Discount Discount GetDiscountById(int discountId); - /// - /// Gets all discounts - /// - /// Discount type; null to load all discount - /// A value indicating whether to show hidden records - /// Discount collection - IList GetAllDiscounts(DiscountType? discountType, string couponCode = "", bool showHidden = false); + /// + /// Gets all discounts + /// + /// Discount type; null to load all discount + /// A value indicating whether to show hidden records + /// Discount collection + IEnumerable GetAllDiscounts(DiscountType? discountType, string couponCode = "", bool showHidden = false); /// /// Inserts a discount 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 f9c0a1e032..0000000000 --- a/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using SmartStore.Core.Infrastructure; -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) - { - this._consumers = consumers; - this._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/DefaultMessageBus.cs b/src/Libraries/SmartStore.Services/Events/DefaultMessageBus.cs new file mode 100644 index 0000000000..f4c72f778b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Events/DefaultMessageBus.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SmartStore.Core; + +namespace SmartStore.Services.Events +{ + public class DefaultMessageBus : IMessageBus + { + private readonly IMessageBroker _messageBroker; + + public DefaultMessageBus(IEnumerable messageBrokers) + { + _messageBroker = messageBrokers.FirstOrDefault(); + } + + public void Subscribe(string channel, Action handler) + { + if (_messageBroker == null) + return; + + _messageBroker.Subscribe(channel, handler); + } + + public void Publish(string channel, string message) + { + if (_messageBroker == null) + return; + + _messageBroker.Publish(channel, message); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Events/EventPublisher.cs b/src/Libraries/SmartStore.Services/Events/EventPublisher.cs index fc8b33df35..e6529d9469 100644 --- a/src/Libraries/SmartStore.Services/Events/EventPublisher.cs +++ b/src/Libraries/SmartStore.Services/Events/EventPublisher.cs @@ -1,111 +1,51 @@ using System; -using System.Linq; using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Threading; +using System.Linq; +using System.Text; using System.Threading.Tasks; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Plugins; -using SmartStore.Core.Logging; -using SmartStore.Core.Async; -using SmartStore.Collections; -using Autofac; -using System.Diagnostics; +using SmartStore.ComponentModel; using SmartStore.Core.Events; +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 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; + public EventPublisher(IConsumerRegistry registry, IConsumerResolver resolver, IConsumerInvoker invoker) + { + _registry = registry; + _resolver = resolver; + _invoker = invoker; - // 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); - foreach (var consumer in consumers) - { - consumer.HandleEvent(eventMessage); - } - }).ContinueWith(t => - { - if (t.IsFaulted) - { - var ex = t.Exception; - if (ex != null) - { - ex.InnerExceptions.Each(x => LogError(x)); - } - } - }); - } + Logger = NullLogger.Instance; + } - // now execute all sync consumers - consumers = consumerFactory.GetConsumers(false); - foreach (var consumer in consumers) - { - PublishEvent(consumer, eventMessage); - } - } + public ILogger Logger { get; set; } - private void PublishEvent(IConsumer x, T eventMessage) + public void Publish(T message) where T : class { - try - { - x.HandleEvent(eventMessage); - } - catch (Exception ex) - { - LogError(ex); - } - } + var descriptors = _registry.GetConsumers(message); - private void LogError(Exception exception) - { - try + if (!descriptors.Any()) { - var logger = EngineContext.Current.Resolve(); - logger.Error(exception.Message, exception); - } - catch - { - //do nothing + return; } - } - private void RemoveFromQueue(object eventMessage) - { - Timer timer; - if (_queue.TryRemove(eventMessage, out timer)) + var envelopeType = typeof(ConsumeContext<>).MakeGenericType(typeof(T)); + var envelope = (ConsumeContext)FastActivator.CreateInstance(envelopeType, message); + + 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/Events/IMessageBroker.cs b/src/Libraries/SmartStore.Services/Events/IMessageBroker.cs new file mode 100644 index 0000000000..ce54a1965b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Events/IMessageBroker.cs @@ -0,0 +1,10 @@ +using System; + +namespace SmartStore.Services.Events +{ + public interface IMessageBroker + { + void Subscribe(string channel, Action handler); + void Publish(string channel, string message); + } +} diff --git a/src/Libraries/SmartStore.Services/Events/IMessageBus.cs b/src/Libraries/SmartStore.Services/Events/IMessageBus.cs new file mode 100644 index 0000000000..d7245ba7e2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Events/IMessageBus.cs @@ -0,0 +1,10 @@ +using System; + +namespace SmartStore.Services.Events +{ + public interface IMessageBus + { + void Subscribe(string channel, Action handler); + void Publish(string channel, string message); + } +} diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs deleted file mode 100644 index 216894b2d2..0000000000 --- a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using System.Web; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Media; -using SmartStore.Services.Catalog; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; - -namespace SmartStore -{ - public static class NameValueCollectionExtensions - { - // TODO: find place to make it public static - private static string AttributeFormatedName(int productAttributeId, int attributeId, int productId = 0, int bundleItemId = 0) - { - if (productId == 0) - return "product_attribute_{0}_{1}".FormatInvariant(productAttributeId, attributeId); - else - return "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(productId, bundleItemId, productAttributeId, attributeId); - } - - public static void AddProductAttribute(this NameValueCollection collection, int productAttributeId, int attributeId, int valueId, int productId = 0, int bundleItemId = 0) - { - if (productAttributeId != 0 && attributeId != 0 && valueId != 0) - { - var name = AttributeFormatedName(productAttributeId, attributeId, productId, bundleItemId); - - collection.Add(name, valueId.ToString()); - } - } - - /// - /// Get selected attributes from query string - /// - /// Name value collection with selected attributes - /// Query string parameters - /// Attribute query data items with following structure: - /// Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id, [BundleItem.Id] - /// Product identifier to filter - public static void GetSelectedAttributes(this NameValueCollection collection, NameValueCollection queryString, List> attributes, int productId = 0) - { - Guard.NotNull(() => collection); - - // ambiguous parameters: let other query string parameters win over the json formatted attributes parameter - if (queryString != null && queryString.Count > 0) - { - var items = queryString.AllKeys - .Where(x => x.EmptyNull().StartsWith("product_attribute_")) - .SelectMany(queryString.GetValues, (k, v) => new { key = k.EmptyNull(), value = v.TrimSafe() }); - - foreach (var item in items) - { - var ids = item.key.Replace("product_attribute_", "").SplitSafe("_"); - if (ids.Count() > 3) - { - if (productId == 0 || (productId != 0 && productId == ids[0].ToInt())) - { - collection.Add(item.key, item.value); - } - } - } - } - - if (attributes != null && attributes.Count > 0) - { - var items = attributes.Where(i => i.Count > 3); - - if (productId != 0) - items = items.Where(i => i[0] == productId); - - foreach (var item in items) - { - var name = AttributeFormatedName(item[1], item[2], item[0], item.Count > 4 ? item[4] : 0); - - collection.Add(name, item[3].ToString()); - } - } - } - - /// Takes selected elements from collection and creates a attribute XML string from it. - /// how the name of the controls are formatted. frontend includes productId, backend does not. - public static string CreateSelectedAttributesXml(this NameValueCollection collection, - int productId, - IEnumerable variantAttributes, - IProductAttributeParser productAttributeParser, - ILocalizationService localizationService, - IDownloadService downloadService, - CatalogSettings catalogSettings, - HttpRequestBase request, List warnings, - bool formatWithProductId = true, - int bundleItemId = 0) - { - if (collection == null) - return ""; - - string controlId; - string selectedAttributes = ""; - - foreach (var attribute in variantAttributes) - { - controlId = AttributeFormatedName(attribute.ProductAttributeId, attribute.Id, formatWithProductId ? productId : 0, bundleItemId); - - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - { - var ctrlAttributes = collection[controlId]; - if (ctrlAttributes.HasValue()) - { - var selectedAttributeId = ctrlAttributes.SplitSafe(",").SafeGet(0).ToInt(); - if (selectedAttributeId > 0) - selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); - } - } - break; - - case AttributeControlType.Checkboxes: - { - var cblAttributes = collection[controlId]; - if (cblAttributes.HasValue()) - { - foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - var selectedAttributeId = item.SplitSafe(",").SafeGet(0).ToInt(); - if (selectedAttributeId > 0) - selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); - } - } - } - break; - - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - { - var txtAttribute = collection[controlId]; - if (txtAttribute.HasValue()) - { - string enteredText = txtAttribute.Trim(); - selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, enteredText); - } - } - break; - - case AttributeControlType.Datepicker: - { - var date = collection[controlId + "_day"]; - var month = collection[controlId + "_month"]; - var year = collection[controlId + "_year"]; - DateTime? selectedDate = null; - - try - { - selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(date)); - } - catch { } - - if (selectedDate.HasValue) - { - selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedDate.Value.ToString("D")); - } - } - break; - - case AttributeControlType.FileUpload: - if (request == null) - { - Guid downloadGuid; - Guid.TryParse(collection[controlId], out downloadGuid); - var download = downloadService.GetDownloadByGuid(downloadGuid); - if (download != null) - { - download.IsTransient = false; - downloadService.UpdateDownload(download); - selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, download.DownloadGuid.ToString()); - } - } - else - { - var postedFile = request.Files[controlId]; - if (postedFile != null && postedFile.FileName.HasValue()) - { - int fileMaxSize = catalogSettings.FileUploadMaximumSizeBytes; - if (postedFile.ContentLength > fileMaxSize) - { - warnings.Add(string.Format(localizationService.GetResource("ShoppingCart.MaximumUploadedFileSize"), (int)(fileMaxSize / 1024))); - } - else - { - //save an uploaded file - var download = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = false, - DownloadUrl = "", - DownloadBinary = postedFile.InputStream.ToByteArray(), - ContentType = postedFile.ContentType, - Filename = System.IO.Path.GetFileNameWithoutExtension(postedFile.FileName), - Extension = System.IO.Path.GetExtension(postedFile.FileName), - IsNew = true - }; - downloadService.InsertDownload(download); - //save attribute - selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, download.DownloadGuid.ToString()); - } - } - } - break; - - default: - break; - } - } - return selectedAttributes; - } - - public static string AddGiftCardAttribute(this NameValueCollection collection, string attributes, int productId, IProductAttributeParser productAttributeParser, int bundleItemId = 0) - { - string recipientName = ""; - string recipientEmail = ""; - string senderName = ""; - string senderEmail = ""; - string giftCardMessage = ""; - - string strProductId = ""; - if (productId != 0) - strProductId = "_{0}_{1}".FormatWith(productId, bundleItemId); - - foreach (string formKey in collection.AllKeys) - { - if (formKey.Equals(string.Format("giftcard{0}.RecipientName", strProductId), StringComparison.InvariantCultureIgnoreCase)) - { - recipientName = collection[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard{0}.RecipientEmail", strProductId), StringComparison.InvariantCultureIgnoreCase)) - { - recipientEmail = collection[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard{0}.SenderName", strProductId), StringComparison.InvariantCultureIgnoreCase)) - { - senderName = collection[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard{0}.SenderEmail", strProductId), StringComparison.InvariantCultureIgnoreCase)) - { - senderEmail = collection[formKey]; - continue; - } - if (formKey.Equals(string.Format("giftcard{0}.Message", strProductId), StringComparison.InvariantCultureIgnoreCase)) - { - giftCardMessage = collection[formKey]; - continue; - } - } - - return productAttributeParser.AddGiftCardAttribute(attributes, recipientName, recipientEmail, senderName, senderEmail, giftCardMessage); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs deleted file mode 100644 index 6bbd9756f9..0000000000 --- a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Linq; -using Newtonsoft.Json; - -namespace SmartStore.Services.Filter -{ - [JsonObject(MemberSerialization.OptIn)] - public class FilterCriteria : IComparable - { - [JsonProperty] - public string Name { get; set; } - - [JsonProperty] - public string Value { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public string Entity { get; set; } - - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public bool Or { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public string Type { get; set; } - - [JsonConverter(typeof(OperatorConverter))] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public FilterOperator Operator { get; set; } - - /// left, right or none parenthesis - //[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public bool? Open { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public int? ID { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public int? PId { get; set; } - - // Metadata - public int MatchCount { get; set; } - public int DisplayOrder { get; set; } - public int DisplayOrderValues { get; set; } - public bool IsInactive { get; set; } - public string NameLocalized { get; set; } - public string ValueLocalized { get; set; } - - public string SqlName - { - get - { - if (Entity.IsCaseInsensitiveEqual("Manufacturer") && !Name.Contains('.')) - return "{0}.{1}".FormatInvariant(Entity, Name); - - return Name; - } - } - - public bool IsRange - { - get - { - return (Value.HasValue() && Value.Contains('~') && (Operator == FilterOperator.RangeGreaterEqualLessEqual || Operator == FilterOperator.RangeGreaterEqualLess)); - } - } - - int IComparable.CompareTo(object obj) - { - var filter = (FilterCriteria)obj; - - var compare = string.Compare(this.Entity, filter.Entity, true); - - if (compare == 0) - { - compare = string.Compare(this.Name, filter.Name, true); - - if (compare == 0) - compare = string.Compare(this.Value, filter.Value, true); - } - - return compare; - } - - public override string ToString() - { - try - { - return JsonConvert.SerializeObject(this); - } - catch (Exception exc) - { - exc.Dump(); - } - return ""; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs b/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs deleted file mode 100644 index 56f0655f26..0000000000 --- a/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web; -using Newtonsoft.Json; -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Catalog; -using SmartStore.Services.Localization; - -namespace SmartStore.Services.Filter -{ - public static class FilterExtensions - { - private static string FormatPrice(string value) - { - if (value.HasValue()) - { - decimal d = 0; - - if (StringToPrice(value, out d)) - return EngineContext.Current.Resolve().FormatPrice(d, true, false); - } - return value; - } - - public static string ToDescription(this FilterCriteria criteria) - { - var localize = EngineContext.Current.Resolve(); - - if (criteria == null || criteria.Value.IsEmpty()) - return localize.GetResource("Common.Unspecified"); - - if (criteria.Operator == FilterOperator.RangeGreaterEqualLessEqual || criteria.Operator == FilterOperator.RangeGreaterEqualLess) - { - string valueLeft, valueRight; - criteria.Value.SplitToPair(out valueLeft, out valueRight, "~"); - - if (criteria.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) - return "{0} - {1}".FormatInvariant(FormatPrice(valueLeft), FormatPrice(valueRight)); - - return "{0} - {1}".FormatInvariant(valueLeft, valueRight); - } - - var value = (criteria.ValueLocalized.HasValue() ? criteria.ValueLocalized : criteria.Value); - - if (criteria.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) - value = FormatPrice(criteria.Value); - - if (criteria.Operator == FilterOperator.Unequal) - return "≠ {0}".FormatInvariant(value); - if (criteria.Operator == FilterOperator.Greater) - return "> {0}".FormatInvariant(value); - if (criteria.Operator == FilterOperator.GreaterEqual) - return "≥ {0}".FormatInvariant(value); - if (criteria.Operator == FilterOperator.Less) - return "< {0}".FormatInvariant(value); - if (criteria.Operator == FilterOperator.LessEqual) - return "≤ {0}".FormatInvariant(value); - if (criteria.Operator == FilterOperator.Contains) - return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.Contains"), value); - if (criteria.Operator == FilterOperator.StartsWith) - return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.StartsWith"), value); - if (criteria.Operator == FilterOperator.EndsWith) - return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.EndsWith"), value); - - return value; - } - - public static FilterCriteria ParsePriceString(this string priceRange) - { - try - { - if (priceRange.HasValue()) - { - priceRange = priceRange.Trim(); - - if (priceRange.HasValue()) - { - decimal from, to; - string[] range = priceRange.Split(new char[] { '-' }); - bool hasFrom = range.StringToPrice(0, out from); - bool hasTo = range.StringToPrice(1, out to); - - var criteria = new FilterCriteria - { - Name = "Price", - Entity = FilterService.ShortcutPrice - }; - - // avoid overlapping - if (hasFrom && hasTo) - { - //criteria.Operator = FilterOperator.RangeGreaterEqualLess; - criteria.Operator = FilterOperator.RangeGreaterEqualLessEqual; - criteria.Value = from.ToString(CultureInfo.InvariantCulture) + "~" + to.ToString(CultureInfo.InvariantCulture); - } - else if (hasFrom) - { - //criteria.Operator = FilterOperator.GreaterEqual; - criteria.Operator = FilterOperator.Greater; - criteria.Value = from.ToString(CultureInfo.InvariantCulture); - } - else if (hasTo) - { - //criteria.Operator = FilterOperator.LessEqual; - criteria.Operator = FilterOperator.Less; - criteria.Value = to.ToString(CultureInfo.InvariantCulture); - } - else - { - return null; - } - return criteria; - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - return null; - } - - public static bool StringToPrice(string value, out decimal result) - { - result = 0; - return (value.HasValue() && decimal.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out result)); - } - - public static bool StringToPrice(this string[] range, int index, out decimal result) - { - result = 0; - if (range != null && index < range.Length) - { - var value = range[index].Trim(); - - return StringToPrice(value, out result); - } - return false; - } - - public static string GetUrl(this FilterProductContext context, FilterCriteria criteriaAdd = null, FilterCriteria criteriaRemove = null) - { - var url = "{0}?pagesize={1}&viewmode={2}".FormatInvariant(context.Path, context.PageSize, context.ViewMode); - - if (context.OrderBy.HasValue) - { - url = "{0}&orderby={1}".FormatInvariant(url, context.OrderBy.Value); - } - - try - { - if (criteriaAdd != null || criteriaRemove != null) - { - var criterias = new List(); - - if (context.Criteria != null && context.Criteria.Count > 0) - criterias.AddRange(context.Criteria.Where(c => !c.IsInactive)); - - if (criteriaAdd != null) - criterias.Add(criteriaAdd); - else - criterias.RemoveAll(c => c.Entity.IsCaseInsensitiveEqual(criteriaRemove.Entity) && c.Name.IsCaseInsensitiveEqual(criteriaRemove.Name) && c.Value.IsCaseInsensitiveEqual(criteriaRemove.Value)); - - if (criterias.Count > 0) - url = "{0}&filter={1}".FormatInvariant(url, HttpUtility.UrlEncode(JsonConvert.SerializeObject(criterias))); - } - } - catch (Exception exc) - { - exc.Dump(); - } - - return url; - } - - public static bool IsActive(this FilterProductContext context, FilterCriteria criteria) - { - if (criteria != null && context.Criteria != null) - { - var foundCriteria = context.Criteria.FirstOrDefault(c => !c.IsInactive && - c.Entity.IsCaseInsensitiveEqual(criteria.Entity) && c.Name.IsCaseInsensitiveEqual(criteria.Name) && c.Value.IsCaseInsensitiveEqual(criteria.Value)); - - return (foundCriteria != null); - } - return false; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Filter/FilterMisc.cs b/src/Libraries/SmartStore.Services/Filter/FilterMisc.cs deleted file mode 100644 index edfbc624a1..0000000000 --- a/src/Libraries/SmartStore.Services/Filter/FilterMisc.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; -using System.Text; - -namespace SmartStore.Services.Filter -{ - public class FilterSql - { - public List Criteria { get; set; } - public List Values { get; set; } - public StringBuilder WhereClause { get; set; } - - public override string ToString() - { - if (WhereClause != null && Values != null) - return "{0}, {1}".FormatWith(WhereClause.ToString(), string.Join(", ", Values.ToArray())); - return ""; - } - } - - - public class FilterProductContext - { - public string Filter { get; set; } - public int ParentCategoryID { get; set; } - public List CategoryIds { get; set; } - public string Path { get; set; } - public int PageSize { get; set; } - public int? OrderBy { get; set; } - public string ViewMode { get; set; } - - public List Criteria { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Services/Filter/FilterOperator.cs b/src/Libraries/SmartStore.Services/Filter/FilterOperator.cs deleted file mode 100644 index ed49bed2b5..0000000000 --- a/src/Libraries/SmartStore.Services/Filter/FilterOperator.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using Newtonsoft.Json; - -namespace SmartStore.Services.Filter -{ - public sealed class FilterOperator - { - private const string _equal = "="; - private const string _unequal = "!="; - private const string _greater = ">"; - private const string _greaterEqual = ">="; - private const string _less = "<"; - private const string _lessEqual = "<="; - private const string _contains = "contain"; - private const string _startWith = "start"; - private const string _endsWith = "end"; - private const string _rangeGreaterEqualLessEqual = ">=<="; - private const string _rangeGreaterEqualLess = ">=<"; - - private readonly string _name; - - private FilterOperator(string name) - { - this._name = name; - } - - public static readonly FilterOperator Equal = new FilterOperator(_equal); - public static readonly FilterOperator Unequal = new FilterOperator(_unequal); - public static readonly FilterOperator Greater = new FilterOperator(_greater); - public static readonly FilterOperator GreaterEqual = new FilterOperator(_greaterEqual); - public static readonly FilterOperator Less = new FilterOperator(_less); - public static readonly FilterOperator LessEqual = new FilterOperator(_lessEqual); - public static readonly FilterOperator Contains = new FilterOperator(_contains); - public static readonly FilterOperator StartsWith = new FilterOperator(_startWith); - public static readonly FilterOperator EndsWith = new FilterOperator(_endsWith); - public static readonly FilterOperator RangeGreaterEqualLessEqual = new FilterOperator(_rangeGreaterEqualLessEqual); - public static readonly FilterOperator RangeGreaterEqualLess = new FilterOperator(_rangeGreaterEqualLess); - - public override string ToString() - { - return _name ?? "="; - } - public static FilterOperator Parse(string op) - { - switch (op) - { - case _equal: - return FilterOperator.Equal; - case _unequal: - return FilterOperator.Unequal; - case _greater: - return FilterOperator.Greater; - case _greaterEqual: - return FilterOperator.GreaterEqual; - case _less: - return FilterOperator.Less; - case _lessEqual: - return FilterOperator.LessEqual; - case _rangeGreaterEqualLessEqual: - return FilterOperator.RangeGreaterEqualLessEqual; - case _rangeGreaterEqualLess: - return FilterOperator.RangeGreaterEqualLess; - - default: - if (op.IsCaseInsensitiveEqual(_contains)) - return FilterOperator.Contains; - - if (op.IsCaseInsensitiveEqual(_startWith)) - return FilterOperator.StartsWith; - - if (op.IsCaseInsensitiveEqual(_endsWith)) - return FilterOperator.EndsWith; - - return null; - } - } - } - - - public class OperatorConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(FilterOperator); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader != null && reader.TokenType == JsonToken.String) - { - return FilterOperator.Parse(reader.Value as string); - } - return null; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Filter/FilterService.cs b/src/Libraries/SmartStore.Services/Filter/FilterService.cs deleted file mode 100644 index d3e1ceeaac..0000000000 --- a/src/Libraries/SmartStore.Services/Filter/FilterService.cs +++ /dev/null @@ -1,627 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Linq.Dynamic; -using System.Text; -using Newtonsoft.Json; -using SmartStore.ComponentModel; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Catalog; -using SmartStore.Services.Localization; - -namespace SmartStore.Services.Filter -{ - public partial class FilterService : IFilterService - { - private const string _defaultType = "String"; - - private readonly IProductService _productService; - private readonly ICategoryService _categoryService; - private readonly CatalogSettings _catalogSettings; - private readonly IRepository _productRepository; - private readonly IRepository _productCategoryRepository; - private readonly ILocalizedEntityService _localizedEntityService; - private readonly ICommonServices _services; - - private IQueryable _products; - - public FilterService(IProductService productService, - ICategoryService categoryService, - CatalogSettings catalogSettings, - IRepository productRepository, - IRepository productCategoryRepository, - ILocalizedEntityService localizedEntityService, - ICommonServices services) - { - _productService = productService; - _categoryService = categoryService; - _catalogSettings = catalogSettings; - _productRepository = productRepository; - _productCategoryRepository = productCategoryRepository; - _localizedEntityService = localizedEntityService; - _services = services; - } - - public static string ShortcutPrice { get { return "_Price"; } } - public static string ShortcutSpecAttribute { get { return "_SpecId"; } } - - // helper - private string ValidateValue(string value, string alternativeValue) - { - if (value.HasValue() && !value.IsCaseInsensitiveEqual("null")) - return value; - - return alternativeValue; - } - - private string FormatParameterIndex(ref int index) - { - //if (curlyBracketFormatting) - // return "{0}{1}{2}".FormatWith('{', index++, '}'); - - return "@{0}".FormatWith(index++); - } - - private object FilterValueToObject(string value, string type) - { - if (value == null) - value = ""; - - //if (value == "__UtcNow__") - // return DateTime.UtcNow; - - //if (value == "__Now__") - // return DateTime.Now; - - //if (curlyBracketFormatting) - // return value.FormatWith("\"{0}\"", value.Replace("\"", "\"\"")); - - Type t = Type.GetType("System.{0}".FormatInvariant(ValidateValue(type, _defaultType))); - - var result = TypeConverterFactory.GetConverter(t).ConvertFrom(CultureInfo.InvariantCulture, value); - - return result; - } - - private bool IsShortcut(FilterSql context, FilterCriteria itm, ref int index) - { - if (itm.Entity.IsCaseInsensitiveEqual(ShortcutPrice)) - { - // TODO: where clause of special price not correct. product can appear in price and in special price range. - - if (itm.IsRange) - { - string valueLeft, valueRight; - itm.Value.SplitToPair(out valueLeft, out valueRight, "~"); - - context.WhereClause.AppendFormat("((Price >= {0} And Price {1} {2}) Or (SpecialPrice >= {0} And SpecialPrice {1} {2} And SpecialPriceStartDateTimeUtc <= {3} And SpecialPriceEndDateTimeUtc >= {3}))", - FormatParameterIndex(ref index), - itm.Operator == FilterOperator.RangeGreaterEqualLessEqual ? "<=" : "<", - FormatParameterIndex(ref index), - FormatParameterIndex(ref index) - ); - - context.Values.Add(FilterValueToObject(valueLeft, itm.Type ?? "Decimal")); - context.Values.Add(FilterValueToObject(valueRight, itm.Type ?? "Decimal")); - context.Values.Add(DateTime.UtcNow); - } - else - { - context.WhereClause.AppendFormat("(Price {0} {1} Or (SpecialPrice {0} {1} And SpecialPriceStartDateTimeUtc <= {2} And SpecialPriceEndDateTimeUtc >= {2}))", - itm.Operator == null ? "=" : itm.Operator.ToString(), - FormatParameterIndex(ref index), - FormatParameterIndex(ref index)); - - context.Values.Add(FilterValueToObject(itm.Value, itm.Type ?? "Decimal")); - context.Values.Add(DateTime.UtcNow); - } - } - else if (itm.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute)) - { - context.WhereClause.AppendFormat("SpecificationAttributeOptionId {0} {1}", itm.Operator == null ? "=" : itm.Operator.ToString(), FormatParameterIndex(ref index)); - - context.Values.Add(itm.ID ?? 0); - } - else - { - return false; - } - return true; - } - - private void FilterParentheses(List criteria) - { - // Logical or combine all criteria with same name. - // - // "The order of precedence for the logical operators is NOT (highest), followed by AND, followed by OR. - // The order of evaluation at the same precedence level is from left to right." - // http://www.databasedev.co.uk/sql-multiple-conditions.html - - if (criteria.Count > 0) - { - criteria.Sort(); - criteria.ForEach(c => { c.Open = null; c.Or = false; }); - - var data = ( - from c in criteria - group c by c.Entity).Where(g => g.Count() > 1); - //group c by c.Name).Where(g => g.Count() > 1); - - foreach (var grp in data) - { - grp.ToList().ForEach(f => f.Or = true); - grp.First().Or = false; - grp.First().Open = true; - grp.Last().Open = false; - } - } - } - - private IQueryable AllProducts(List categoryIds) - { - if (_products == null) - { - var searchContext = new ProductSearchContext - { - Query = _productRepository.TableUntracked, - FeaturedProducts = (_catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false), - StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode, - VisibleIndividuallyOnly = true - }; - - if (categoryIds != null && categoryIds.Count > 1) - { - _products = _productService.PrepareProductSearchQuery(searchContext); - - var distinctIds = ( - from p in _productRepository.TableUntracked - join pc in _productCategoryRepository.TableUntracked on p.Id equals pc.ProductId - where categoryIds.Contains(pc.CategoryId) - select p.Id).Distinct(); - - _products = - from p in _products - join x in distinctIds on p.Id equals x - select p; - } - else - { - searchContext.CategoryIds = categoryIds; - - _products = _productService.PrepareProductSearchQuery(searchContext); - } - } - return _products; - } - - private List ProductFilterablePrices(FilterProductContext context) - { - var result = new List(); - FilterCriteria criteria; - Category category; - - var tmp = new FilterProductContext - { - ParentCategoryID = context.ParentCategoryID, - CategoryIds = context.CategoryIds, - Criteria = new List() - }; - - if (context.ParentCategoryID != 0 && (category = _categoryService.GetCategoryById(context.ParentCategoryID)) != null && category.PriceRanges.HasValue()) - { - string[] ranges = category.PriceRanges.SplitSafe(";"); - - foreach (string range in ranges) - { - if ((criteria = range.ParsePriceString()) != null) - { - tmp.Criteria.Clear(); - tmp.Criteria.AddRange(context.Criteria); - tmp.Criteria.Add(criteria); - - try - { - criteria.MatchCount = ProductFilter(tmp).Count(); - } - catch (Exception exc) - { - exc.Dump(); - } - - if (criteria.MatchCount > 0) - result.Add(criteria); - } - } - } - - result.ForEach(c => c.IsInactive = true); - return result; - } - - private List ProductFilterableManufacturer(FilterProductContext context, bool getAll = false) - { - var query = ProductFilter(context); - - var manus = - from p in query - from pm in p.ProductManufacturers - where !pm.Manufacturer.Deleted - select pm.Manufacturer; - - var grouped = - from m in manus - group m by m.Id into grp - orderby grp.Key - select new FilterCriteria - { - MatchCount = grp.Count(), - Value = grp.FirstOrDefault().Name, - DisplayOrder = grp.FirstOrDefault().DisplayOrder - }; - - if (_catalogSettings.SortFilterResultsByMatches) - { - grouped = grouped.OrderByDescending(m => m.MatchCount); - } - else - { - grouped = grouped.OrderBy(m => m.DisplayOrder); - } - - if (!getAll) - { - grouped = grouped.Take(_catalogSettings.MaxFilterItemsToDisplay); - } - - var lst = grouped.ToList(); - - lst.ForEach(c => - { - c.Name = "Name"; - c.Entity = "Manufacturer"; - c.IsInactive = true; - }); - - return lst; - } - - private List ProductFilterableSpecAttributes(FilterProductContext context, string attributeName = null) - { - List criterias = null; - var languageId = _services.WorkContext.WorkingLanguage.Id; - var query = ProductFilter(context); - - var attributes = - from p in query - from sa in p.ProductSpecificationAttributes - where sa.AllowFiltering - select sa.SpecificationAttributeOption; - - if (attributeName.HasValue()) - { - attributes = attributes.Where(a => a.SpecificationAttribute.Name == attributeName); - } - - var grouped = - from a in attributes - group a by new { a.SpecificationAttributeId, a.Id } into g - select new FilterCriteria - { - Name = g.FirstOrDefault().SpecificationAttribute.Name, - Value = g.FirstOrDefault().Name, - ID = g.Key.Id, - PId = g.FirstOrDefault().SpecificationAttribute.Id, - MatchCount = g.Count(), - DisplayOrder = g.FirstOrDefault().SpecificationAttribute.DisplayOrder, - DisplayOrderValues = g.FirstOrDefault().DisplayOrder - }; - - if (_catalogSettings.SortFilterResultsByMatches) - { - criterias = grouped - .OrderBy(a => a.DisplayOrder) - .ThenByDescending(a => a.MatchCount) - .ThenBy(a => a.DisplayOrderValues) - .ToList(); - } - else - { - criterias = grouped - .OrderBy(a => a.DisplayOrder) - .ThenBy(a => a.DisplayOrderValues) - .ToList(); - } - - criterias.ForEach(c => - { - c.Entity = ShortcutSpecAttribute; - c.IsInactive = true; - - if (c.PId.HasValue) - c.NameLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.PId.Value, "SpecificationAttribute", "Name"); - - if (c.ID.HasValue) - c.ValueLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.ID.Value, "SpecificationAttributeOption", "Name"); - }); - - return criterias; - } - - public virtual List Deserialize(string jsonData) - { - if (jsonData.HasValue()) - { - if (jsonData.StartsWith("[")) - { - return JsonConvert.DeserializeObject>(jsonData); - } - - return new List { JsonConvert.DeserializeObject(jsonData) }; - } - return new List(); - } - - public virtual string Serialize(List criteria) - { - //criteria.FindAll(c => c.Type.IsNullOrEmpty()).ForEach(c => c.Type = _defaultType); - if (criteria != null && criteria.Count > 0) - return JsonConvert.SerializeObject(criteria); - - return ""; - } - - public virtual FilterProductContext CreateFilterProductContext(string filter, int categoryID, string path, int? pagesize, int? orderby, string viewmode) - { - var context = new FilterProductContext - { - Filter = filter, - ParentCategoryID = categoryID, - CategoryIds = new List { categoryID }, - Path = path, - PageSize = pagesize ?? 12, - ViewMode = viewmode, - OrderBy = orderby, - Criteria = Deserialize(filter) - }; - - if (_catalogSettings.ShowProductsFromSubcategories) - { - context.CategoryIds.AddRange( - _categoryService.GetAllCategoriesByParentCategoryId(categoryID).Select(x => x.Id) - ); - } - - int languageId = _services.WorkContext.WorkingLanguage.Id; - - foreach (var criteria in context.Criteria.Where(x => x.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute))) - { - if (criteria.PId.HasValue) - criteria.NameLocalized = _localizedEntityService.GetLocalizedValue(languageId, criteria.PId.Value, "SpecificationAttribute", "Name"); - - if (criteria.ID.HasValue) - criteria.ValueLocalized = _localizedEntityService.GetLocalizedValue(languageId, criteria.ID.Value, "SpecificationAttributeOption", "Name"); - } - - return context; - } - - public virtual bool ToWhereClause(FilterSql context) - { - if (context.Values == null) - context.Values = new List(); - else - context.Values.Clear(); - - if (context.WhereClause == null) - context.WhereClause = new StringBuilder(); - else - context.WhereClause.Clear(); - - int index = 0; - - FilterParentheses(context.Criteria); - - foreach (var itm in context.Criteria) - { - if (context.WhereClause.Length > 0) - context.WhereClause.AppendFormat(" {0} ", itm.Or ? "Or" : "And"); - - if (itm.Open.HasValue && itm.Open.Value) - context.WhereClause.Append("("); - - if (IsShortcut(context, itm, ref index)) - { - } - else if (itm.IsRange) - { - string valueLeft, valueRight; - itm.Value.SplitToPair(out valueLeft, out valueRight, "~"); - - context.WhereClause.AppendFormat("({0} >= {1} And {0} {2} {3})", - itm.SqlName, - FormatParameterIndex(ref index), - itm.Operator == FilterOperator.RangeGreaterEqualLessEqual ? "<=" : "<", - FormatParameterIndex(ref index) - ); - - context.Values.Add(FilterValueToObject(valueLeft, itm.Type)); - context.Values.Add(FilterValueToObject(valueRight, itm.Type)); - } - else if (itm.Value.IsEmpty()) - { - context.WhereClause.AppendFormat("ASCII({0}) Is Null", itm.SqlName); // true if null or empty (string) - } - else - { - context.WhereClause.Append(itm.SqlName); - - if (itm.Operator == FilterOperator.Contains) - context.WhereClause.Append(".Contains("); - else if (itm.Operator == FilterOperator.StartsWith) - context.WhereClause.Append(".StartsWith("); - else if (itm.Operator == FilterOperator.EndsWith) - context.WhereClause.Append(".EndsWith("); - else - context.WhereClause.AppendFormat(" {0} ", itm.Operator == null ? "=" : itm.Operator.ToString()); - - context.WhereClause.Append(FormatParameterIndex(ref index)); - - if (itm.Operator == FilterOperator.Contains || itm.Operator == FilterOperator.StartsWith || itm.Operator == FilterOperator.EndsWith) - context.WhereClause.Append(")"); - - context.Values.Add(FilterValueToObject(itm.Value, itm.Type)); - } - - if (itm.Open.HasValue && !itm.Open.Value) - context.WhereClause.Append(")"); - } - return (context.WhereClause.Length > 0); - } - - public virtual bool ToWhereClause(FilterSql context, List findIn, Predicate match) - { - if (context.Criteria != null) - context.Criteria.Clear(); // ! - - context.Criteria = findIn.FindAll(match); - - return ToWhereClause(context); - } - - public virtual IQueryable ProductFilter(FilterProductContext context) - { - var sql = new FilterSql(); - var query = AllProducts(context.CategoryIds); - - // prices - if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && c.Entity.IsCaseInsensitiveEqual(ShortcutPrice))) - { - query = query.Where(sql.WhereClause.ToString(), sql.Values.ToArray()); - } - - // manufacturer - if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && c.Entity.IsCaseInsensitiveEqual("Manufacturer"))) - { - var pmq = - from p in query - from pm in p.ProductManufacturers - where !pm.Manufacturer.Deleted - select pm; - - query = pmq - .Where(sql.WhereClause.ToString(), sql.Values.ToArray()) - .Select(pm => pm.Product); - } - - // specification attribute - if (ToWhereClause(sql, context.Criteria, c => !c.IsInactive && (c.Entity.IsCaseInsensitiveEqual("SpecificationAttributeOption") || c.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute)))) - { - //var saq = ( - // from p in query - // from sa in p.ProductSpecificationAttributes - // select sa).Where(a => a.AllowFiltering).Where(sql.WhereClause.ToString(), sql.Values.ToArray()); - - //query = saq.Select(sa => sa.Product); - - int countSameNameAttributes = sql.Criteria - .Where(c => c.Entity.IsCaseInsensitiveEqual(ShortcutSpecAttribute)) - .GroupBy(c => c.Name) - .Count(); - - var specRepository = EngineContext.Current.Resolve>(); - - var saq = specRepository.TableUntracked - .Where(a => a.AllowFiltering) - .Where(sql.WhereClause.ToString(), sql.Values.ToArray()) - .GroupBy(a => a.ProductId) - .Where(grp => (grp.Count() >= countSameNameAttributes)); - - query = - from p in query - join sa in saq on p.Id equals sa.Key - select p; - } - - // sort - var order = (ProductSortingEnum)(context.OrderBy ?? 0); - switch (order) - { - case ProductSortingEnum.NameDesc: - query = query.OrderByDescending(p => p.Name); - break; - case ProductSortingEnum.PriceAsc: - query = query.OrderBy(p => p.Price); - break; - case ProductSortingEnum.PriceDesc: - query = query.OrderByDescending(p => p.Price); - break; - case ProductSortingEnum.CreatedOn: - query = query.OrderByDescending(p => p.CreatedOnUtc); - break; - case ProductSortingEnum.CreatedOnAsc: - query = query.OrderBy(p => p.CreatedOnUtc); - break; - default: - query = query.OrderBy(p => p.Name); - break; - } - - // distinct cause same products can be mapped to sub-categories... too slow - //query = - // from p in query - // group p by p.Id into grp - // orderby grp.Key - // select grp.FirstOrDefault(); - - //query.ToString().Dump(); - return query; - } - - public virtual void ProductFilterable(FilterProductContext context) - { - if (context.Criteria.FirstOrDefault(c => c.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) == null) - context.Criteria.AddRange(ProductFilterablePrices(context)); - - if (context.Criteria.FirstOrDefault(c => c.Name.IsCaseInsensitiveEqual("Name") && c.Entity.IsCaseInsensitiveEqual("Manufacturer")) == null) - context.Criteria.AddRange(ProductFilterableManufacturer(context)); - - context.Criteria.AddRange(ProductFilterableSpecAttributes(context)); - } - - public virtual void ProductFilterableMultiSelect(FilterProductContext context, string filterMultiSelect) - { - var criteriaMultiSelect = Deserialize(filterMultiSelect).FirstOrDefault(); - List inactive = null; - - if (criteriaMultiSelect != null) - { - context.Criteria.RemoveAll(c => c.Name.IsCaseInsensitiveEqual(criteriaMultiSelect.Name) && c.Entity.IsCaseInsensitiveEqual(criteriaMultiSelect.Entity)); - - if (criteriaMultiSelect.Name.IsCaseInsensitiveEqual("Name") && criteriaMultiSelect.Entity.IsCaseInsensitiveEqual("Manufacturer")) - inactive = ProductFilterableManufacturer(context, true); - else if (criteriaMultiSelect.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutPrice)) - inactive = ProductFilterablePrices(context); - else if (criteriaMultiSelect.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutSpecAttribute)) - inactive = ProductFilterableSpecAttributes(context, criteriaMultiSelect.Name); - } - - // filters WITHOUT the multiple selectable filters - var excludedFilter = Serialize(context.Criteria); - - // filters WITH the multiple selectable filters (required for highlighting selected values) - context.Criteria = Deserialize(context.Filter); - - context.Filter = excludedFilter; - - if (inactive != null) - { - inactive.ForEach(c => c.IsInactive = true); - context.Criteria.AddRange(inactive); - } - } - } -} - diff --git a/src/Libraries/SmartStore.Services/Filter/IFilterService.cs b/src/Libraries/SmartStore.Services/Filter/IFilterService.cs deleted file mode 100644 index f27c9ff91f..0000000000 --- a/src/Libraries/SmartStore.Services/Filter/IFilterService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using SmartStore.Core.Domain.Catalog; - -namespace SmartStore.Services.Filter -{ - public partial interface IFilterService - { - List Deserialize(string jsonData); - string Serialize(List criteria); - - FilterProductContext CreateFilterProductContext(string filter, int categoryID, string path, int? pagesize, int? orderby, string viewmode); - - bool ToWhereClause(FilterSql context); - bool ToWhereClause(FilterSql context, List findIn, Predicate match); - - IQueryable ProductFilter(FilterProductContext context); - - void ProductFilterable(FilterProductContext context); - void ProductFilterableMultiSelect(FilterProductContext context, string filterMultiSelect); - } -} 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/ForumMessageFactoryExtensions.cs b/src/Libraries/SmartStore.Services/Forums/ForumMessageFactoryExtensions.cs new file mode 100644 index 0000000000..0e8a1140be --- /dev/null +++ b/src/Libraries/SmartStore.Services/Forums/ForumMessageFactoryExtensions.cs @@ -0,0 +1,50 @@ +using System; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Messages; +using SmartStore.Services.Messages; + +namespace SmartStore.Services.Forums +{ + public static class ForumMessageFactoryExtensions + { + /// + /// Sends a forum subscription message to a customer + /// + public static CreateMessageResult SendNewForumTopicMessage(this IMessageFactory factory, Customer customer, ForumTopic forumTopic, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(forumTopic, nameof(forumTopic)); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewForumTopic, languageId, customer: customer), true, forumTopic, forumTopic.Forum); + } + + /// + /// Sends a forum subscription message to a customer + /// + /// Friendly forum topic page to use for URL generation (1-based) + public static CreateMessageResult SendNewForumPostMessage(this IMessageFactory factory, Customer customer, ForumPost forumPost, int topicPageIndex, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(forumPost, nameof(forumPost)); + + var bag = new ModelPart + { + ["TopicPageIndex"] = topicPageIndex + }; + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewForumPost, languageId, customer: customer), true, bag, forumPost, forumPost.ForumTopic, forumPost.ForumTopic.Forum); + } + + /// + /// Sends a private message notification + /// + public static CreateMessageResult SendPrivateMessageNotification(this IMessageFactory factory, Customer customer, PrivateMessage privateMessage, int languageId = 0) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(privateMessage, nameof(privateMessage)); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewPrivateMessage, languageId, privateMessage.StoreId, customer), true, privateMessage); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index d8f53c05b0..11b744b8ab 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -1,1114 +1,808 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Data.Entity; using SmartStore.Core; -using SmartStore.Core.Caching; 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 { - /// - /// Forum service - /// public partial class ForumService : IForumService { - #region Constants - - private const string FORUMGROUP_ALL_KEY = "SmartStore.forumgroup.all-{0}"; - private const string FORUM_ALLBYFORUMGROUPID_KEY = "SmartStore.forum.allbyforumgroupid-{0}"; - private const string FORUMGROUP_PATTERN_KEY = "SmartStore.forumgroup."; - private const string FORUM_PATTERN_KEY = "SmartStore.forum."; - - #endregion - - #region Fields - private readonly IRepository _forumGroupRepository; private readonly IRepository _forumRepository; private readonly IRepository _forumTopicRepository; 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 IRequestCache _requestCache; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IRepository _storeMappingRepository; - private readonly ICommonServices _services; - - #endregion - - #region Ctor + private readonly ICommonServices _services; - public ForumService(IRequestCache requestCache, + public ForumService( IRepository forumGroupRepository, IRepository forumRepository, IRepository forumTopicRepository, IRepository forumPostRepository, IRepository forumPrivateMessageRepository, IRepository forumSubscriptionRepository, + IRepository storeMappingRepository, + IRepository aclRepository, ForumSettings forumSettings, IRepository customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - IWorkflowMessageService workflowMessageService, - IRepository storeMappingRepository, - ICommonServices services) + ICommonServices services) { - _requestCache = requestCache; _forumGroupRepository = forumGroupRepository; _forumRepository = forumRepository; _forumTopicRepository = forumTopicRepository; _forumPostRepository = forumPostRepository; _forumPrivateMessageRepository = forumPrivateMessageRepository; _forumSubscriptionRepository = forumSubscriptionRepository; + _storeMappingRepository = storeMappingRepository; + _aclRepository = aclRepository; _forumSettings = forumSettings; _customerRepository = customerRepository; _genericAttributeService = genericAttributeService; _customerService = customerService; - _workflowMessageService = workflowMessageService; - _storeMappingRepository = storeMappingRepository; - _services = services; + _services = services; } - public DbQuerySettings QuerySettings { get; set; } - - #endregion + public DbQuerySettings QuerySettings { get; set; } - #region Utilities - - /// - /// Update forum stats - /// - /// The forum identifier - 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); } - /// - /// Update forum topic stats - /// - /// The forum topic identifier - 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); } - /// - /// Update customer stats - /// - /// The customer identifier - 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; - } - #endregion + if (!showHidden && !QuerySettings.IgnoreAcl) + { + var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - #region Methods + 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; - /// - /// Deletes a forum group - /// - /// Forum group - public virtual void DeleteForumGroup(ForumGroup forumGroup) - { - if (forumGroup == null) + joinApplied = true; + } + + if (joinApplied) { - throw new ArgumentNullException("forumGroup"); + 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); - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + return query.ToListCached(); + } - //event notification - _services.EventPublisher.EntityDeleted(forumGroup); + public virtual void InsertForumGroup(ForumGroup group) + { + Guard.NotNull(group, nameof(group)); + + _forumGroupRepository.Insert(group); } - /// - /// Gets a forum group - /// - /// The forum group identifier - /// Forum group - public virtual ForumGroup GetForumGroupById(int forumGroupId) + public virtual void UpdateForumGroup(ForumGroup group) { - if (forumGroupId == 0) - return null; + Guard.NotNull(group, nameof(group)); - return _forumGroupRepository.GetById(forumGroupId); + _forumGroupRepository.Update(group); } - /// - /// Gets all forum groups - /// - /// Forum groups - public virtual IList GetAllForumGroups(bool showHidden = false) + public virtual void DeleteForumGroup(ForumGroup group) { - string key = string.Format(FORUMGROUP_ALL_KEY, showHidden); - return _requestCache.Get(key, () => + if (group != null) { - var query = _forumGroupRepository.Table; - - if (!showHidden && !QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _services.StoreContext.CurrentStore.Id; - - 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; - - query = - from fg in query - group fg by fg.Id into fgGroup - orderby fgGroup.Key - select fgGroup.FirstOrDefault(); - } - - query = query.OrderBy(m => m.DisplayOrder); - - return query.ToList(); - }); + _forumGroupRepository.Delete(group); + } } - /// - /// Inserts a forum group - /// - /// Forum group - public virtual void InsertForumGroup(ForumGroup forumGroup) + #endregion + + #region Forum + + 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); - //cache - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityInserted(forumGroup); + return entity; } - /// - /// Updates the forum group - /// - /// Forum group - public virtual void UpdateForumGroup(ForumGroup forumGroup) + public virtual void InsertForum(Forum forum) { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } + Guard.NotNull(forum, nameof(forum)); - _forumGroupRepository.Update(forumGroup); + _forumRepository.Insert(forum); + } - //cache - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + public virtual void UpdateForum(Forum forum) + { + Guard.NotNull(forum, nameof(forum)); - //event notification - _services.EventPublisher.EntityUpdated(forumGroup); + _forumRepository.Update(forum); } - /// - /// Deletes a forum - /// - /// 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); - //event notification - _services.EventPublisher.EntityDeleted(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); - //event notification - _services.EventPublisher.EntityDeleted(fs2); } - //delete forum + // Delete forum. _forumRepository.Delete(forum); + } - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + #endregion - //event notification - _services.EventPublisher.EntityDeleted(forum); - } + #region Topic - /// - /// Gets a forum - /// - /// The forum identifier - /// Forum - public virtual Forum GetForumById(int forumId) + public virtual ForumTopic GetTopicById(int topicId) { - if (forumId == 0) + if (topicId == 0) + { return null; + } - return _forumRepository.GetById(forumId); - } + var entity = _forumTopicRepository.Table + .Expand(x => x.Forum) + .Expand(x => x.Forum.ForumGroup) + .FirstOrDefault(x => x.Id == topicId); - /// - /// Gets forums by forum group identifier - /// - /// The forum group identifier - /// Forums - public virtual IList GetAllForumsByGroupId(int forumGroupId) - { - string key = string.Format(FORUM_ALLBYFORUMGROUPID_KEY, forumGroupId); - return _requestCache.Get(key, () => - { - var query = from f in _forumRepository.Table - orderby f.DisplayOrder - where f.ForumGroupId == forumGroupId - select f; - var forums = query.ToList(); - return forums; - }); + return entity; } - /// - /// Inserts a forum - /// - /// Forum - public virtual void InsertForum(Forum forum) + 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); - - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + var query = _forumTopicRepository.Table + .Where(x => topicIds.Contains(x.Id)); - //event notification - _services.EventPublisher.EntityInserted(forum); + return query.OrderBySequence(topicIds).ToList(); } - /// - /// Updates the forum - /// - /// Forum - 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); - - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityUpdated(forum); - } - - /// - /// Deletes a forum topic - /// - /// Forum topic - 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); - //event notification - _services.EventPublisher.EntityDeleted(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); - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); - - //event notification - _services.EventPublisher.EntityDeleted(forumTopic); + var topics = new PagedList(query, pageIndex, pageSize); + return topics; } - /// - /// Gets a forum topic - /// - /// The forum topic identifier - /// Forum Topic - public virtual ForumTopic GetTopicById(int forumTopicId) + public virtual IList GetActiveTopics(int forumId, int count, bool showHidden = false) { - return GetTopicById(forumTopicId, false); - } + var joinApplied = false; + var customer = _services.WorkContext.CurrentCustomer; - /// - /// Gets a forum topic - /// - /// The forum topic identifier - /// The value indicating whether to increase forum topic views - /// Forum Topic - public virtual ForumTopic GetTopicById(int forumTopicId, bool increaseViews) - { - if (forumTopicId == 0) + 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; - /// - /// Gets all forum topics - /// - /// The forum identifier - /// The customer identifier - /// Keywords - /// Search type - /// Limit by the last number days; 0 to load all topics - /// Page index - /// Page size - /// Forum Topics - 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); - /// - /// Gets active forum topics - /// - /// The forum identifier - /// Count of forum topics to return - /// Forum Topics - 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; } - /// - /// Inserts a forum topic - /// - /// Forum topic - /// A value indicating whether to send notifications to subscribed customers - 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); - //cache - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + UpdateForumStatistics(forum); - //event notification - _services.EventPublisher.EntityInserted(forumTopic); - - //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()) { - _workflowMessageService.SendNewForumTopicMessage(subscription.Customer, forumTopic, forum, languageId); + _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, topic, languageId); } } } } - /// - /// Updates the forum topic - /// - /// Forum topic - public virtual void UpdateTopic(ForumTopic forumTopic) + public virtual void UpdateTopic(ForumTopic topic, bool updateStatistics) { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } - - _forumTopicRepository.Update(forumTopic); - - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + Guard.NotNull(topic, nameof(topic)); - //event notification - _services.EventPublisher.EntityUpdated(forumTopic); - } - - /// - /// Moves the forum topic - /// - /// The forum topic identifier - /// New forum identifier - /// Moved forum 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; - forumTopic.UpdatedOnUtc = DateTime.UtcNow; - 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; } - /// - /// Deletes a forum post - /// - /// Forum post - 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; - - //delete topic if it was the first post - bool deleteTopic = false; - var firstPost = forumTopic.GetFirstPost(this); - if (firstPost != null && firstPost.Id == forumPost.Id) - { - deleteTopic = true; - } + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = topic.Customer ?? _customerService.GetCustomerById(topic.CustomerId); - //delete forum post - _forumPostRepository.Delete(forumPost); + _forumTopicRepository.Delete(topic); - //delete topic - if (deleteTopic) + // Delete forum subscriptions. + var subscriptions = _forumSubscriptionRepository.Table.Where(x => x.TopicId == topic.Id).ToList(); + foreach (var subscription in subscriptions) { - DeleteTopic(forumTopic); + _forumSubscriptionRepository.Delete(subscription); } - //update stats - if (!deleteTopic) + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); + } + + public virtual ForumTopic MoveTopic(int topicId, int newForumId) + { + var topic = GetTopicById(topicId); + if (topic != null && IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, topic)) { - UpdateForumTopicStats(forumTopicId); - } - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + var previousForumId = topic.ForumId; + var newForum = GetForumById(newForumId); - //clear cache - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + if (newForum != null && previousForumId != newForumId) + { + topic.ForumId = newForum.Id; + UpdateTopic(topic, false); - //event notification - _services.EventPublisher.EntityDeleted(forumPost); + UpdateForumStatistics(GetForumById(previousForumId)); + UpdateForumStatistics(newForum); + } + } + return topic; } - /// - /// Gets a forum post - /// - /// The forum post identifier - /// Forum Post - 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; } - /// - /// Gets all forum posts - /// - /// The forum topic identifier - /// The customer identifier - /// Keywords - /// Page index - /// Page size - /// Posts - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, int pageIndex, int pageSize) - { - return GetAllPosts(forumTopicId, customerId, keywords, true, pageIndex, pageSize); - } + #endregion - /// - /// Gets all forum posts - /// - /// The forum topic identifier - /// The customer identifier - /// Keywords - /// Sort order - /// Page index - /// Page size - /// Forum Posts - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize) + #region Post + + 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; } - /// - /// Inserts a forum post - /// - /// The forum post - /// A value indicating whether to send notifications to subscribed customers - 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)); + + 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)); - //update stats - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumPost.TopicId); - int forumId = forumTopic.ForumId; - UpdateForumTopicStats(forumPost.TopicId); - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + _forumPostRepository.Insert(post); - //clear cache - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); - //event notification - _services.EventPublisher.EntityInserted(forumPost); + 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()) { - _workflowMessageService.SendNewForumPostMessage(subscription.Customer, forumPost, forumTopic, forum, friendlyTopicPageIndex, languageId); + _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, post, friendlyTopicPageIndex, languageId); } } } } - /// - /// Updates the forum post - /// - /// Forum post - public virtual void UpdatePost(ForumPost forumPost) + public virtual void UpdatePost(ForumPost post, bool updateStatistics) { - //validation - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } + Guard.NotNull(post, nameof(post)); - _forumPostRepository.Update(forumPost); + _forumPostRepository.Update(post); - _requestCache.RemoveByPattern(FORUMGROUP_PATTERN_KEY); - _requestCache.RemoveByPattern(FORUM_PATTERN_KEY); + if (updateStatistics) + { + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); - //event notification - _services.EventPublisher.EntityUpdated(forumPost); + UpdateForumStatistics(forum); + UpdateTopicStatistics(topic); + UpdateCustomerStatistics(customer); + } } - /// - /// Deletes a private message - /// - /// Private message - 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); + } - //event notification - _services.EventPublisher.EntityDeleted(privateMessage); + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); } - /// - /// Gets a private message - /// - /// The private message identifier - /// Private message - 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; } - /// - /// Gets private messages - /// - /// The store identifier; pass 0 to load all messages - /// The customer identifier who sent the message - /// The customer identifier who should receive the message - /// 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 - public virtual IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId, - int toCustomerId, bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient, - string keywords, int pageIndex, int pageSize) + public virtual IPagedList GetAllPrivateMessages( + int storeId, + int fromCustomerId, + int toCustomerId, + bool? isRead, + bool? isDeletedByAuthor, + bool? isDeletedByRecipient, + int pageIndex, + int pageSize) { var query = _forumPrivateMessageRepository.Table; - if (storeId > 0) - query = query.Where(pm => storeId == pm.StoreId); + + 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)); + query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient); } - query = query.OrderByDescending(pm => pm.CreatedOnUtc); - var privateMessages = new PagedList(query, pageIndex, pageSize); + query = query.OrderByDescending(pm => pm.CreatedOnUtc); + var privateMessages = new PagedList(query, pageIndex, pageSize); return privateMessages; } - /// - /// Inserts a private message - /// - /// Private message - 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); - //event notification - _services.EventPublisher.EntityInserted(privateMessage); - - 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) { - _workflowMessageService.SendPrivateMessageNotification(customerTo, privateMessage, _services.WorkContext.WorkingLanguage.Id); + _services.MessageFactory.SendPrivateMessageNotification(customerTo, message, _services.WorkContext.WorkingLanguage.Id); } } - /// - /// Updates the private message - /// - /// Private message - 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); - //event notification - _services.EventPublisher.EntityDeleted(privateMessage); + _forumPrivateMessageRepository.Delete(message); } else { - _forumPrivateMessageRepository.Update(privateMessage); - //event notification - _services.EventPublisher.EntityUpdated(privateMessage); + _forumPrivateMessageRepository.Update(message); } } - /// - /// Deletes a forum subscription - /// - /// Forum subscription - public virtual void DeleteSubscription(ForumSubscription forumSubscription) + public virtual void DeletePrivateMessage(PrivateMessage message) { - if (forumSubscription == null) + if (message != null) { - throw new ArgumentNullException("forumSubscription"); - } + _forumPrivateMessageRepository.Delete(message); + } + } - _forumSubscriptionRepository.Delete(forumSubscription); + #endregion - //event notification - _services.EventPublisher.EntityDeleted(forumSubscription); - } + #region Subscription - /// - /// Gets a forum subscription - /// - /// The forum subscription identifier - /// Forum subscription - public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) + 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; } - /// - /// Gets forum subscriptions - /// - /// The customer identifier - /// The forum identifier - /// The topic identifier - /// Page index - /// Page size - /// Forum subscriptions public virtual IPagedList GetAllSubscriptions(int customerId, int forumId, int topicId, int pageIndex, int pageSize) { var fsQuery = @@ -1131,54 +825,35 @@ where fsQuery.Contains(fs.SubscriptionGuid) return forumSubscriptions; } - /// - /// Inserts a forum subscription - /// - /// Forum subscription - 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); - - //event notification - _services.EventPublisher.EntityInserted(forumSubscription); + _forumSubscriptionRepository.Insert(subscription); } - /// - /// Updates the forum subscription - /// - /// Forum 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)); - //event notification - _services.EventPublisher.EntityUpdated(forumSubscription); + _forumSubscriptionRepository.Update(subscription); } - /// - /// Check whether customer is allowed to create new topics - /// - /// Customer - /// Forum - /// True if allowed, otherwise false - 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); } + } - if (customer == null) + #endregion + + #region Customer + + public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) + { + if (forum == null || customer == null) { return false; } @@ -1188,7 +863,7 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -1196,67 +871,28 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru return true; } - /// - /// Check whether customer is allowed to edit topic - /// - /// Customer - /// Topic - /// True if allowed, otherwise false public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic topic) { - if (topic == null) + if (customer != null && topic != null) { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - 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; } - /// - /// Check whether customer is allowed to move topic - /// - /// Customer - /// Topic - /// True if allowed, otherwise false 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; } @@ -1264,62 +900,33 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t return false; } - /// - /// Check whether customer is allowed to delete topic - /// - /// Customer - /// Topic - /// True if allowed, otherwise false 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; } - /// - /// Check whether customer is allowed to create new post - /// - /// Customer - /// Topic - /// True if allowed, otherwise false 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; } @@ -1327,118 +934,47 @@ public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic return true; } - /// - /// Check whether customer is allowed to edit post - /// - /// Customer - /// Topic - /// True if allowed, otherwise false public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost post) { - if (post == null) - { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) - { - return true; - } - - if (_forumSettings.AllowCustomersToEditPosts) - { - bool ownPost = customer.Id == post.CustomerId; - return ownPost; - } - - return false; - } - - /// - /// Check whether customer is allowed to delete post - /// - /// Customer - /// Topic - /// True if allowed, otherwise false - 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 (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToDeletePosts) - { - bool ownPost = customer.Id == post.CustomerId; - return ownPost; + if (_forumSettings.AllowCustomersToEditPosts && post.Published) + { + var ownPost = customer.Id == post.CustomerId; + return ownPost; + } } return false; } - /// - /// Check whether customer is allowed to set topic priority - /// - /// Customer - /// True if allowed, otherwise false - public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) + public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post) { - if (customer == null) + if (post != null && 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; } - /// - /// Check whether customer is allowed to watch topics - /// - /// Customer - /// True if allowed, otherwise false public virtual bool IsCustomerAllowedToSubscribe(Customer customer) { - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (customer == null || customer.IsGuest()) { return false; } @@ -1446,32 +982,6 @@ public virtual bool IsCustomerAllowedToSubscribe(Customer customer) return true; } - /// - /// Calculates topic page index by post identifier - /// - /// Forum topic identifier - /// Page size - /// Post identifier - /// Page index - 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 + #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 6869379919..eea83a391d 100644 --- a/src/Libraries/SmartStore.Services/Helpers/DateTimeHelper.cs +++ b/src/Libraries/SmartStore.Services/Helpers/DateTimeHelper.cs @@ -9,9 +9,6 @@ namespace SmartStore.Services.Helpers { - /// - /// Represents a datetime helper - /// public partial class DateTimeHelper : IDateTimeHelper { private readonly IWorkContext _workContext; @@ -19,59 +16,35 @@ public partial class DateTimeHelper : IDateTimeHelper private readonly ISettingService _settingService; private readonly DateTimeSettings _dateTimeSettings; - /// - /// Ctor - /// - /// Work context - /// Generic attribute service - /// Setting service - /// Datetime settings - public DateTimeHelper(IWorkContext workContext, + private TimeZoneInfo _cachedUserTimeZone; + + public DateTimeHelper( + IWorkContext workContext, IGenericAttributeService genericAttributeService, ISettingService settingService, DateTimeSettings dateTimeSettings) { - this._workContext = workContext; - this._genericAttributeService = genericAttributeService; - this._settingService = settingService; - this._dateTimeSettings = dateTimeSettings; + _workContext = workContext; + _genericAttributeService = genericAttributeService; + _settingService = settingService; + _dateTimeSettings = dateTimeSettings; } - /// - /// Retrieves a System.TimeZoneInfo object from the registry based on its identifier. - /// - /// The time zone identifier, which corresponds to the System.TimeZoneInfo.Id property. - /// A System.TimeZoneInfo object whose identifier is the value of the id parameter. public virtual TimeZoneInfo FindTimeZoneById(string id) { return TimeZoneInfo.FindSystemTimeZoneById(id); } - /// - /// Returns a sorted collection of all the time zones - /// - /// A read-only collection of System.TimeZoneInfo objects. public virtual ReadOnlyCollection GetSystemTimeZones() { return TimeZoneInfo.GetSystemTimeZones(); } - /// - /// Converts the date and time to current user date and time - /// - /// The date and time (respesents local system time or UTC time) to convert. - /// A DateTime value that represents time that corresponds to the dateTime parameter in customer time zone. public virtual DateTime ConvertToUserTime(DateTime dt) { return ConvertToUserTime(dt, dt.Kind); } - /// - /// Converts the date and time to current user date and time - /// - /// The date and time (respesents local system time or UTC time) to convert. - /// The source datetimekind - /// A DateTime value that represents time that corresponds to the dateTime parameter in customer time zone. public virtual DateTime ConvertToUserTime(DateTime dt, DateTimeKind sourceDateTimeKind) { dt = DateTime.SpecifyKind(dt, sourceDateTimeKind); @@ -79,58 +52,28 @@ public virtual DateTime ConvertToUserTime(DateTime dt, DateTimeKind sourceDateTi return TimeZoneInfo.ConvertTime(dt, currentUserTimeZoneInfo); } - /// - /// Converts the date and time to current user date and time - /// - /// The date and time to convert. - /// The time zone of dateTime. - /// A DateTime value that represents time that corresponds to the dateTime parameter in customer time zone. public virtual DateTime ConvertToUserTime(DateTime dt, TimeZoneInfo sourceTimeZone) { var currentUserTimeZoneInfo = this.CurrentTimeZone; return ConvertToUserTime(dt, sourceTimeZone, currentUserTimeZoneInfo); } - /// - /// Converts the date and time to current user date and time - /// - /// The date and time to convert. - /// The time zone of dateTime. - /// The time zone to convert dateTime to. - /// A DateTime value that represents time that corresponds to the dateTime parameter in customer time zone. public virtual DateTime ConvertToUserTime(DateTime dt, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone) { return TimeZoneInfo.ConvertTime(dt, sourceTimeZone, destinationTimeZone); } - /// - /// Converts the date and time to Coordinated Universal Time (UTC) - /// - /// The date and time (respesents local system time or UTC time) to convert. - /// A DateTime value that represents the Coordinated Universal Time (UTC) that corresponds to the dateTime parameter. The DateTime value's Kind property is always set to DateTimeKind.Utc. public virtual DateTime ConvertToUtcTime(DateTime dt) { return ConvertToUtcTime(dt, dt.Kind); } - /// - /// Converts the date and time to Coordinated Universal Time (UTC) - /// - /// The date and time (respesents local system time or UTC time) to convert. - /// The source datetimekind - /// A DateTime value that represents the Coordinated Universal Time (UTC) that corresponds to the dateTime parameter. The DateTime value's Kind property is always set to DateTimeKind.Utc. public virtual DateTime ConvertToUtcTime(DateTime dt, DateTimeKind sourceDateTimeKind) { dt = DateTime.SpecifyKind(dt, sourceDateTimeKind); return TimeZoneInfo.ConvertTimeToUtc(dt); } - /// - /// Converts the date and time to Coordinated Universal Time (UTC) - /// - /// The date and time to convert. - /// The time zone of dateTime. - /// A DateTime value that represents the Coordinated Universal Time (UTC) that corresponds to the dateTime parameter. The DateTime value's Kind property is always set to DateTimeKind.Utc. public virtual DateTime ConvertToUtcTime(DateTime dt, TimeZoneInfo sourceTimeZone) { if (sourceTimeZone.IsInvalidTime(dt)) @@ -144,15 +87,13 @@ public virtual DateTime ConvertToUtcTime(DateTime dt, TimeZoneInfo sourceTimeZon } } - /// - /// Gets a customer time zone - /// - /// Customer - /// Customer time zone; if customer is null, then default store time zone public virtual TimeZoneInfo GetCustomerTimeZone(Customer customer) { - //registered user - TimeZoneInfo timeZoneInfo = null; + if (_cachedUserTimeZone != null) + return _cachedUserTimeZone; + + // registered user + TimeZoneInfo timeZone = null; if (_dateTimeSettings.AllowCustomersToSetTimeZone) { string timeZoneId = string.Empty; @@ -161,25 +102,24 @@ public virtual TimeZoneInfo GetCustomerTimeZone(Customer customer) try { - if (!String.IsNullOrEmpty(timeZoneId)) - timeZoneInfo = FindTimeZoneById(timeZoneId); + if (timeZoneId.HasValue()) + timeZone = FindTimeZoneById(timeZoneId); } - catch (Exception exc) + catch (Exception ex) { - Debug.Write(exc.ToString()); + Debug.Write(ex.ToString()); } } - //default timezone - if (timeZoneInfo == null) - timeZoneInfo = this.DefaultStoreTimeZone; + // default timezone + if (timeZone == null) + timeZone = this.DefaultStoreTimeZone; + + _cachedUserTimeZone = timeZone; - return timeZoneInfo; + return timeZone; } - /// - /// Gets or sets a default store time zone - /// public virtual TimeZoneInfo DefaultStoreTimeZone { get @@ -187,12 +127,12 @@ public virtual TimeZoneInfo DefaultStoreTimeZone TimeZoneInfo timeZoneInfo = null; try { - if (!String.IsNullOrEmpty(_dateTimeSettings.DefaultStoreTimeZoneId)) - timeZoneInfo = FindTimeZoneById(_dateTimeSettings.DefaultStoreTimeZoneId); + if (_dateTimeSettings.DefaultStoreTimeZoneId.HasValue()) + timeZoneInfo = FindTimeZoneById(_dateTimeSettings.DefaultStoreTimeZoneId); } - catch (Exception exc) + catch (Exception ex) { - Debug.Write(exc.ToString()); + Debug.Write(ex.ToString()); } if (timeZoneInfo == null) @@ -210,12 +150,11 @@ public virtual TimeZoneInfo DefaultStoreTimeZone _dateTimeSettings.DefaultStoreTimeZoneId = defaultTimeZoneId; _settingService.SaveSetting(_dateTimeSettings); - } + _cachedUserTimeZone = null; + + } } - /// - /// Gets or sets the current user time zone - /// public virtual TimeZoneInfo CurrentTimeZone { get @@ -233,9 +172,9 @@ public virtual TimeZoneInfo CurrentTimeZone timeZoneId = value.Id; } - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, - SystemCustomerAttributeNames.TimeZoneId, timeZoneId); - } + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.TimeZoneId, timeZoneId); + _cachedUserTimeZone = null; + } } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Hooks/AclEntityHook.cs b/src/Libraries/SmartStore.Services/Hooks/AclEntityHook.cs new file mode 100644 index 0000000000..b1518ba862 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/AclEntityHook.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Security; +using SmartStore.Services.Security; + +namespace SmartStore.Services.Common +{ + public class AclEntityHook : DbSaveHook + { + private readonly Lazy _aclService; + private readonly HashSet _toDelete = new HashSet(); + + public AclEntityHook(Lazy aclService) + { + _aclService = aclService; + } + + protected override void OnDeleted(IAclSupported entity, IHookedEntity entry) + { + var entityType = entry.EntityType; + + var records = _aclService.Value.GetAclRecordsFor(entityType.Name, entry.Entity.Id); + _toDelete.AddRange(records); + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + _toDelete.Each(x => _aclService.Value.DeleteAclRecord(x)); + scope.Commit(); + } + + _toDelete.Clear(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/AclEntityPostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/AclEntityPostDeleteHook.cs deleted file mode 100644 index 05445c8653..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/AclEntityPostDeleteHook.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using SmartStore.Core; -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Security; -using SmartStore.Services.Security; - -namespace SmartStore.Services.Common -{ - public class AclEntityPostDeleteHook : PostDeleteHook - { - private readonly Lazy _aclService; - - public AclEntityPostDeleteHook(Lazy aclService) - { - this._aclService = aclService; - } - - public override void Hook(IAclSupported entity, HookEntityMetadata metadata) - { - var baseEntity = entity as BaseEntity; - - if (baseEntity == null) - return; - - var entityType = baseEntity.GetUnproxiedType(); - - var records = _aclService.Value.GetAclRecordsFor(entityType.Name, baseEntity.Id); - records.Each(x => _aclService.Value.DeleteAclRecord(x)); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Hooks/AuditableHook.cs b/src/Libraries/SmartStore.Services/Hooks/AuditableHook.cs new file mode 100644 index 0000000000..cb22b6a757 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/AuditableHook.cs @@ -0,0 +1,26 @@ +using System; +using SmartStore.Core; +using SmartStore.Core.Data.Hooks; + +namespace SmartStore.Services.Hooks +{ + [Important] + public class AuditableHook : DbSaveHook + { + protected override void OnInserting(IAuditable entity, IHookedEntity entry) + { + var now = DateTime.UtcNow; + + if (entity.CreatedOnUtc == DateTime.MinValue) + entity.CreatedOnUtc = now; + + if (entity.UpdatedOnUtc == DateTime.MinValue) + entity.UpdatedOnUtc = now; + } + + protected override void OnUpdating(IAuditable entity, IHookedEntity entry) + { + entity.UpdatedOnUtc = DateTime.UtcNow; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/FixProductMainPictureHook.cs b/src/Libraries/SmartStore.Services/Hooks/FixProductMainPictureHook.cs new file mode 100644 index 0000000000..91c9121160 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/FixProductMainPictureHook.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Data.Utilities; + +namespace SmartStore.Services.Common +{ + public class FixProductMainPictureHook : DbSaveHook + { + private readonly IRepository _rsProduct; + private readonly HashSet _products = new HashSet(); + + public FixProductMainPictureHook(IRepository rsProduct) + { + _rsProduct = rsProduct; + } + + protected override void OnDeleting(ProductPicture entity, IHookedEntity entry) + { + Fix(entity); + } + + protected override void OnInserting(ProductPicture entity, IHookedEntity entry) + { + Fix(entity); + } + + protected override void OnUpdating(ProductPicture entity, IHookedEntity entry) + { + Fix(entity); + } + + private void Fix(ProductPicture entity) + { + var product = entity.Product ?? _rsProduct.GetById(entity.ProductId); + if (product != null) + { + _products.Add(product); + } + } + + public override void OnBeforeSaveCompleted() + { + foreach (var product in _products) + { + DataMigrator.FixProductMainPictureId(_rsProduct.Context, product); + } + + _products.Clear(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityHook.cs b/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityHook.cs new file mode 100644 index 0000000000..c0f0942078 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityHook.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Localization; +using SmartStore.Services.Localization; + +namespace SmartStore.Services.Hooks +{ + public class LocalizedEntityHook : DbSaveHook + { + private readonly Lazy _localizedEntityService; + private readonly HashSet _toDelete = new HashSet(); + + public LocalizedEntityHook(Lazy localizedEntityService) + { + _localizedEntityService = localizedEntityService; + } + + protected override void OnDeleted(ILocalizedEntity entity, IHookedEntity entry) + { + var entityType = entry.EntityType; + var localizedEntities = _localizedEntityService.Value.GetLocalizedProperties(entry.Entity.Id, entityType.Name); + _toDelete.AddRange(localizedEntities); + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + using (_localizedEntityService.Value.BeginScope()) + { + _toDelete.Each(x => _localizedEntityService.Value.DeleteLocalizedProperty(x)); + } + + scope.Commit(); + _toDelete.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityPostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityPostDeleteHook.cs deleted file mode 100644 index d1ae20cc34..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityPostDeleteHook.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Localization; -using SmartStore.Services.Localization; - -namespace SmartStore.Services.Hooks -{ - - public class LocalizedEntityPostDeleteHook : PostDeleteHook - { - private readonly ILocalizedEntityService _localizedEntityService; - - public LocalizedEntityPostDeleteHook(ILocalizedEntityService localizedEntityService) - { - this._localizedEntityService = localizedEntityService; - } - - public override void Hook(ILocalizedEntity entity, HookEntityMetadata metadata) - { - var baseEntity = entity as BaseEntity; - - if (baseEntity == null) - return; - - var entityType = baseEntity.GetUnproxiedType(); - var localizedEntities = this._localizedEntityService.GetLocalizedProperties(baseEntity.Id, entityType.Name); - - localizedEntities.Each(x => this._localizedEntityService.DeleteLocalizedProperty(x)); - } - } - -} diff --git a/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityRelationHook.cs b/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityRelationHook.cs new file mode 100644 index 0000000000..61555fb8eb --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/LocalizedEntityRelationHook.cs @@ -0,0 +1,111 @@ +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.Localization; +using SmartStore.Services.Catalog; +using SmartStore.Services.Localization; + +namespace SmartStore.Services.Hooks +{ + /// + /// Deletes localized properties of entities that were deleted by referential integrity. + /// Otherwise these localized properties would remain in the database. + /// + public class LocalizedEntityRelationHook : DbSaveHook + { + private readonly Lazy> _productAttributeRepository; + private readonly Lazy _localizedEntityService; + private readonly Lazy _productAttributeService; + private readonly Lazy _specificationAttributeService; + + private readonly HashSet _toDelete = new HashSet(); + + private static readonly HashSet _candidateTypes = new HashSet(new Type[] + { + typeof(SpecificationAttribute), + typeof(ProductVariantAttribute), + typeof(ProductAttribute), + typeof(ProductAttributeOptionsSet) + }); + + public LocalizedEntityRelationHook( + Lazy> productAttributeRepository, + Lazy localizedEntityService, + Lazy productAttributeService, + Lazy specificationAttributeService) + { + _productAttributeRepository = productAttributeRepository; + _localizedEntityService = localizedEntityService; + _productAttributeService = productAttributeService; + _specificationAttributeService = specificationAttributeService; + } + + protected override void OnDeleting(BaseEntity entity, IHookedEntity entry) + { + var type = entry.EntityType; + + if (!_candidateTypes.Contains(type)) + throw new NotSupportedException(); + + if (type == typeof(SpecificationAttribute)) + { + var attribute = (SpecificationAttribute)entry.Entity; + var attributeOptions = attribute.SpecificationAttributeOptions.ToList(); + if (!attributeOptions.Any()) + attributeOptions = _specificationAttributeService.Value.GetSpecificationAttributeOptionsBySpecificationAttribute(attribute.Id).ToList(); + + attributeOptions.ForEach(x => _toDelete.AddRange(_localizedEntityService.Value.GetLocalizedProperties(x.Id, "SpecificationAttributeOption"))); + } + else if (type == typeof(ProductVariantAttribute)) + { + var attribute = (ProductVariantAttribute)entry.Entity; + var attributeOptions = attribute.ProductVariantAttributeValues.ToList(); + if (!attributeOptions.Any()) + attributeOptions = _productAttributeService.Value.GetProductVariantAttributeValues(attribute.Id).ToList(); + + attributeOptions.ForEach(x => _toDelete.AddRange(_localizedEntityService.Value.GetLocalizedProperties(x.Id, "ProductVariantAttributeValue"))); + } + else if (type == typeof(ProductAttribute)) + { + var optionIds = ( + from a in _productAttributeRepository.Value.TableUntracked + from os in a.ProductAttributeOptionsSets + from ao in os.ProductAttributeOptions + where a.Id == entry.Entity.Id + select ao.Id).ToList(); + + optionIds.ForEach(x => _toDelete.AddRange(_localizedEntityService.Value.GetLocalizedProperties(x, "ProductAttributeOption"))); + } + else if (type == typeof(ProductAttributeOptionsSet)) + { + var set = (ProductAttributeOptionsSet)entry.Entity; + var attributeOptions = set.ProductAttributeOptions.ToList(); + if (!attributeOptions.Any()) + attributeOptions = _productAttributeService.Value.GetProductAttributeOptionsByOptionsSetId(set.Id).ToList(); + + attributeOptions.ForEach(x => _toDelete.AddRange(_localizedEntityService.Value.GetLocalizedProperties(x.Id, "ProductAttributeOption"))); + } + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + using (_localizedEntityService.Value.BeginScope()) + { + _toDelete.Each(x => _localizedEntityService.Value.DeleteLocalizedProperty(x)); + } + + scope.Commit(); + _toDelete.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/PictureHook.cs b/src/Libraries/SmartStore.Services/Hooks/PictureHook.cs new file mode 100644 index 0000000000..81f434a3f5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/PictureHook.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Media; +using SmartStore.Services.Catalog; + +namespace SmartStore.Services.Hooks +{ + /// + /// Deletes associated pictures of deleted entities + /// + public class PictureHook : DbSaveHook + { + private readonly Lazy _pictureService; + private readonly Lazy _productAttributeService; + + private readonly HashSet _toDelete = new HashSet(); + + private static readonly HashSet _candidateTypes = new HashSet(new Type[] + { + typeof(ProductAttributeOption), + typeof(ProductAttributeOptionsSet), + typeof(ProductAttribute), + typeof(ProductVariantAttribute), + typeof(ProductVariantAttributeValue) + }); + + public PictureHook( + Lazy pictureService, + Lazy productAttributeService) + { + _pictureService = pictureService; + _productAttributeService = productAttributeService; + } + + protected override void OnDeleting(BaseEntity entity, IHookedEntity entry) + { + var type = entry.EntityType; + + if (!_candidateTypes.Contains(type)) + throw new NotSupportedException(); + + if (type == typeof(ProductAttributeOption)) + { + var pictureId = ((ProductAttributeOption)entry.Entity).PictureId; + if (pictureId != 0) + { + _toDelete.Add(pictureId); + } + } + else if (type == typeof(ProductAttributeOptionsSet)) + { + var options = _productAttributeService.Value.GetProductAttributeOptionsByOptionsSetId(entity.Id); + _toDelete.AddRange(options.Where(x => x.PictureId != 0).Select(x => x.PictureId)); + } + else if (type == typeof(ProductAttribute)) + { + var options = _productAttributeService.Value.GetProductAttributeOptionsByAttributeId(entity.Id); + _toDelete.AddRange(options.Where(x => x.PictureId != 0).Select(x => x.PictureId)); + } + else if (type == typeof(ProductVariantAttribute)) + { + var options = _productAttributeService.Value.GetProductVariantAttributeValues(entity.Id); + _toDelete.AddRange(options.Where(x => x.PictureId != 0).Select(x => x.PictureId)); + } + else if (type == typeof(ProductVariantAttributeValue)) + { + var pictureId = ((ProductVariantAttributeValue)entry.Entity).PictureId; + if (pictureId != 0) + { + _toDelete.Add(pictureId); + } + } + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + var pictures = _pictureService.Value.GetPicturesByIds(_toDelete.ToArray()); + + pictures.Each(x => _pictureService.Value.DeletePicture(x)); + + scope.Commit(); + _toDelete.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeHook.cs b/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeHook.cs new file mode 100644 index 0000000000..3a31edcde0 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeHook.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Catalog; + +namespace SmartStore.Services.Hooks +{ + public class ProductVariantAttributeHook : DbSaveHook + { + private readonly Lazy _productAttributeService; + private readonly HashSet _toDelete = new HashSet(); + + public ProductVariantAttributeHook(Lazy productAttributeService) + { + _productAttributeService = productAttributeService; + } + + protected override void OnDeleted(ProductVariantAttribute entity, IHookedEntity entry) + { + _toDelete.Add(entity.Id); + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + _toDelete.Each(x => _productAttributeService.Value.DeleteProductBundleItemAttributeFilter(x)); + scope.Commit(); + } + + _toDelete.Clear(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributePostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributePostDeleteHook.cs deleted file mode 100644 index 69cce5eb82..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributePostDeleteHook.cs +++ /dev/null @@ -1,24 +0,0 @@ -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; - -namespace SmartStore.Services.Hooks -{ - public class ProductVariantAttributePostDeleteHook : PostDeleteHook - { - private readonly IProductAttributeService _productAttributeService; - - public ProductVariantAttributePostDeleteHook(IProductAttributeService productAttributeService) - { - _productAttributeService = productAttributeService; - } - - public override void Hook(ProductVariantAttribute entity, HookEntityMetadata metadata) - { - if (entity != null) - { - _productAttributeService.DeleteProductBundleItemAttributeFilter(entity.Id); - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeValueHook.cs b/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeValueHook.cs new file mode 100644 index 0000000000..a1d4d15db5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeValueHook.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Catalog; + +namespace SmartStore.Services.Hooks +{ + public class ProductVariantAttributeValueHook : DbSaveHook + { + private readonly Lazy _productAttributeService; + private readonly HashSet _toDelete = new HashSet(); + + public ProductVariantAttributeValueHook(Lazy productAttributeService) + { + _productAttributeService = productAttributeService; + } + + protected override void OnDeleted(ProductVariantAttributeValue entity, IHookedEntity entry) + { + _toDelete.Add(entity); + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + _toDelete.Each(x => _productAttributeService.Value.DeleteProductBundleItemAttributeFilter(x.ProductVariantAttributeId, x.Id)); + scope.Commit(); + } + + _toDelete.Clear(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeValuePostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeValuePostDeleteHook.cs deleted file mode 100644 index 33a69c9d68..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/ProductVariantAttributeValuePostDeleteHook.cs +++ /dev/null @@ -1,24 +0,0 @@ -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; - -namespace SmartStore.Services.Hooks -{ - public class ProductVariantAttributeValuePostDeleteHook : PostDeleteHook - { - private readonly IProductAttributeService _productAttributeService; - - public ProductVariantAttributeValuePostDeleteHook(IProductAttributeService productAttributeService) - { - _productAttributeService = productAttributeService; - } - - public override void Hook(ProductVariantAttributeValue entity, HookEntityMetadata metadata) - { - if (entity != null) - { - _productAttributeService.DeleteProductBundleItemAttributeFilter(entity.ProductVariantAttributeId, entity.Id); - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Hooks/SearchQueryAliasHook.cs b/src/Libraries/SmartStore.Services/Hooks/SearchQueryAliasHook.cs new file mode 100644 index 0000000000..3efd100e04 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/SearchQueryAliasHook.cs @@ -0,0 +1,435 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Autofac; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Localization; +using SmartStore.Core.Search; +using SmartStore.Services.Search.Modelling; +using SmartStore.Services.Seo; + +namespace SmartStore.Services.Hooks +{ + public class SearchQueryAliasHook : DbSaveHook + { + private readonly IComponentContext _ctx; + private readonly Lazy _catalogSearchQueryAliasMapper; + private readonly Lazy> _localizedPropertyRepository; + + private string _errorMessage; + + private static readonly HashSet _candidateTypes = new HashSet(new Type[] + { + typeof(LocalizedProperty), + typeof(SpecificationAttribute), + typeof(SpecificationAttributeOption), + typeof(ProductSpecificationAttribute), + typeof(ProductAttribute), + typeof(ProductAttributeOption), + typeof(ProductVariantAttribute), + typeof(ProductVariantAttributeValue) + }); + + public SearchQueryAliasHook( + IComponentContext ctx, + Lazy catalogSearchQueryAliasMapper, + Lazy> localizedPropertyRepository) + { + _ctx = ctx; + _catalogSearchQueryAliasMapper = catalogSearchQueryAliasMapper; + _localizedPropertyRepository = localizedPropertyRepository; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + #region Utilities + + private bool IsPropertyModified(IHookedEntity entry, string propertyName) + { + var result = false; + + if (entry.State != EntityState.Detached) + { + var prop = entry.Entry.Property(propertyName); + if (prop != null) + { + switch (entry.State) + { + case EntityState.Added: + // OriginalValues cannot be used for entities in the Added state. + result = prop.CurrentValue != null; + break; + case EntityState.Deleted: + // CurrentValues cannot be used for entities in the Deleted state. + result = prop.OriginalValue != null; + break; + default: + result = + (prop.CurrentValue != null && !prop.CurrentValue.Equals(prop.OriginalValue)) || + (prop.OriginalValue != null && !prop.OriginalValue.Equals(prop.CurrentValue)); + break; + } + } + } + + return result; + } + + private void RevertChanges(IHookedEntity entry, string errorMessage) + { + // throw exception in OnBeforeSaveCompleted + _errorMessage = errorMessage; + + // revert changes + if (entry.State == EntityState.Modified) + { + entry.State = EntityState.Unchanged; + } + else if (entry.State == EntityState.Added) + { + entry.State = EntityState.Detached; + } + } + + private bool HasAliasDuplicate( + IHookedEntity entry, + BaseEntity baseEntity, + Func, TEntity, bool> hasDuplicate = null) + where TEntity : BaseEntity + { + if (entry.InitialState == EntityState.Added || entry.InitialState == EntityState.Modified) + { + var entity = baseEntity as ISearchAlias; + if (entity != null) + { + entity.Alias = SeoExtensions.GetSeName(entity.Alias); + if (entity.Alias.HasValue()) + { + var repository = _ctx.Resolve>(); + var allEntities = repository.TableUntracked as IQueryable; + + //if (allEntities != null && allEntities.Any(x => x.Id != entity.Id && x.Alias == entity.Alias)) + if (allEntities != null) + { + var duplicateExists = hasDuplicate == null + ? allEntities.Any(x => x.Id != entity.Id && x.Alias == entity.Alias) + : hasDuplicate(repository.TableUntracked, (TEntity)entity); + + if (duplicateExists) + { + RevertChanges(entry, string.Concat(T("Common.Error.AliasAlreadyExists", entity.Alias), " ", T("Common.Error.ChooseDifferentValue"))); + return true; + } + } + } + } + } + + return false; + } + + private bool HasAliasDuplicate(IHookedEntity entry, BaseEntity baseEntity) where T1 : BaseEntity where T2 : BaseEntity + { + if (entry.InitialState == EntityState.Added || entry.InitialState == EntityState.Modified) + { + var entity = baseEntity as ISearchAlias; + if (entity != null) + { + entity.Alias = SeoExtensions.GetSeName(entity.Alias); + if (entity.Alias.HasValue()) + { + var entities1 = _ctx.Resolve>().TableUntracked as IQueryable; + var entities2 = _ctx.Resolve>().TableUntracked as IQueryable; + + var duplicate1 = entities1.FirstOrDefault(x => x.Alias == entity.Alias); + var duplicate2 = entities2.FirstOrDefault(x => x.Alias == entity.Alias); + + if (duplicate1 != null || duplicate2 != null) + { + var type = entry.EntityType; + + if (duplicate1 != null && duplicate1.Id == entity.Id && type == typeof(T1)) + return false; + if (duplicate2 != null && duplicate2.Id == entity.Id && type == typeof(T2)) + return false; + + RevertChanges(entry, string.Concat(T("Common.Error.AliasAlreadyExists", entity.Alias), " ", T("Common.Error.ChooseDifferentValue"))); + return true; + } + } + } + } + + return false; + } + + private bool HasAliasDuplicate(LocalizedProperty property) + { + var existingProperties = _localizedPropertyRepository.Value.Table.Where(x => + x.Id != property.Id && + x.LocaleKey == "Alias" && + x.LocaleKeyGroup == property.LocaleKeyGroup && + x.LanguageId == property.LanguageId && + x.LocaleValue == property.LocaleValue).ToList(); + + if (existingProperties.Count == 0) + { + // Check cases where alias has to be globally unique. + string otherKeyGroup = null; + + if (property.LocaleKeyGroup.IsCaseInsensitiveEqual("SpecificationAttribute")) + otherKeyGroup = "ProductAttribute"; + else if (property.LocaleKeyGroup.IsCaseInsensitiveEqual("ProductAttribute")) + otherKeyGroup = "SpecificationAttribute"; + + if (otherKeyGroup.HasValue()) + { + existingProperties = _localizedPropertyRepository.Value.Table.Where(x => + x.LocaleKey == "Alias" && + x.LocaleKeyGroup == otherKeyGroup && + x.LanguageId == property.LanguageId && + x.LocaleValue == property.LocaleValue).ToList(); + } + + if (existingProperties.Count == 0) + return false; + } + + var toDelete = new List(); + + foreach (var prop in existingProperties) + { + // Check if the related entity exists. The user would not be able to solve an invalidated alias when the related entity does not exist anymore. + var relatedEntityExists = true; + + if (prop.LocaleKeyGroup.IsCaseInsensitiveEqual("SpecificationAttribute")) + { + relatedEntityExists = _ctx.Resolve>().GetById(prop.EntityId) != null; + } + else if (prop.LocaleKeyGroup.IsCaseInsensitiveEqual("SpecificationAttributeOption")) + { + relatedEntityExists = _ctx.Resolve>().GetById(prop.EntityId) != null; + } + else if (prop.LocaleKeyGroup.IsCaseInsensitiveEqual("ProductAttribute")) + { + relatedEntityExists = _ctx.Resolve>().GetById(prop.EntityId) != null; + } + else if (prop.LocaleKeyGroup.IsCaseInsensitiveEqual("ProductAttributeOption")) + { + relatedEntityExists = _ctx.Resolve>().GetById(prop.EntityId) != null; + } + //else if (prop.LocaleKeyGroup.IsCaseInsensitiveEqual("ProductVariantAttributeValue")) + //{ + // relatedEntityExists = _ctx.Resolve>().GetById(prop.EntityId) != null; + //} + + if (relatedEntityExists) + { + // We cannot delete any localized property because we are going to throw duplicate alias exception in OnBeforeSaveCompleted. + return true; + } + else + { + // Delete accidentally dead localized properties. + toDelete.Add(prop); + } + } + + if (toDelete.Any()) + { + try + { + _localizedPropertyRepository.Value.DeleteRange(toDelete); + } + catch (Exception exception) + { + exception.Dump(); + } + } + + return false; + } + + private bool HasEntityDuplicate( + IHookedEntity entry, + BaseEntity baseEntity, + Func getName, + Expression> getDuplicate) where TEntity : BaseEntity + { + if (entry.InitialState == EntityState.Added || entry.InitialState == EntityState.Modified) + { + var repository = _ctx.Resolve>(); + var allEntities = repository.TableUntracked as IQueryable; + + var existingEntity = allEntities.FirstOrDefault(getDuplicate); + if (existingEntity != null && existingEntity.Id != baseEntity.Id) + { + RevertChanges(entry, string.Concat(T("Common.Error.OptionAlreadyExists", getName(existingEntity).NaIfEmpty()), " ", T("Common.Error.ChooseDifferentValue"))); + return true; + } + } + + return false; + } + + #endregion + + protected override void OnDeleting(BaseEntity entity, IHookedEntity entry) + { + HookObject(entity, entry); + } + + protected override void OnInserting(BaseEntity entity, IHookedEntity entry) + { + HookObject(entity, entry); + } + + protected override void OnUpdating(BaseEntity entity, IHookedEntity entry) + { + HookObject(entity, entry); + } + + public override void OnBeforeSaveCompleted() + { + if (_errorMessage.HasValue()) + { + var message = string.Copy(_errorMessage); + _errorMessage = null; + + throw new SmartException(message); + } + } + + private void HookObject(BaseEntity baseEntity, IHookedEntity entry) + { + var type = entry.EntityType; + + if (!_candidateTypes.Contains(type)) + throw new NotSupportedException(); + + if (type == typeof(SpecificationAttribute)) + { + if (HasAliasDuplicate(entry, baseEntity)) + return; + + if (IsPropertyModified(entry, "Alias")) + _catalogSearchQueryAliasMapper.Value.ClearAttributeCache(); + } + else if (type == typeof(SpecificationAttributeOption)) + { + var entity = (SpecificationAttributeOption)baseEntity; + + if (HasEntityDuplicate(entry, baseEntity, x => x.Name, + x => x.SpecificationAttributeId == entity.SpecificationAttributeId && x.Name == entity.Name)) + return; + + if (HasAliasDuplicate(entry, baseEntity)) + return; + + if (IsPropertyModified(entry, "Alias")) + _catalogSearchQueryAliasMapper.Value.ClearAttributeCache(); + } + else if (type == typeof(ProductSpecificationAttribute)) + { + var entity = (ProductSpecificationAttribute)baseEntity; + + if (HasEntityDuplicate(entry, baseEntity, x => x.SpecificationAttributeOption?.Name, + x => x.ProductId == entity.ProductId && x.SpecificationAttributeOptionId == entity.SpecificationAttributeOptionId)) + return; + } + else if (type == typeof(ProductAttribute)) + { + if (HasAliasDuplicate(entry, baseEntity)) + return; + + if (IsPropertyModified(entry, "Alias")) + _catalogSearchQueryAliasMapper.Value.ClearVariantCache(); + } + else if (type == typeof(ProductAttributeOption)) + { + var entity = (ProductAttributeOption)baseEntity; + + if (HasEntityDuplicate(entry, baseEntity, x => x.Name, + x => x.ProductAttributeOptionsSetId == entity.ProductAttributeOptionsSetId && x.Name == entity.Name)) + return; + + // ClearVariantCache() not necessary + if (HasAliasDuplicate(entry, baseEntity)) + return; + } + else if (type == typeof(ProductVariantAttribute)) + { + var entity = (ProductVariantAttribute)baseEntity; + + if (HasEntityDuplicate(entry, baseEntity, x => x.ProductAttribute?.Name, + x => x.ProductId == entity.ProductId && x.ProductAttributeId == entity.ProductAttributeId)) + return; + } + else if (type == typeof(ProductVariantAttributeValue)) + { + var entity = (ProductVariantAttributeValue)baseEntity; + + if (HasEntityDuplicate(entry, baseEntity, x => x.Name, + x => x.ProductVariantAttributeId == entity.ProductVariantAttributeId && x.Name == entity.Name)) + return; + + if (HasAliasDuplicate(entry, baseEntity, + (all, e) => all.Any(x => x.Id != e.Id && x.ProductVariantAttributeId == e.ProductVariantAttributeId && x.Alias == e.Alias))) + return; + + if (IsPropertyModified(entry, "Alias")) + _catalogSearchQueryAliasMapper.Value.ClearVariantCache(); + } + else if (type == typeof(LocalizedProperty)) + { + // note: not fired when SpecificationAttribute or SpecificationAttributeOption deleted. + // not necessary anyway because cache cleared by above code. + var prop = (LocalizedProperty)baseEntity; + var keyGroup = prop.LocaleKeyGroup; + + if (!prop.LocaleKey.IsCaseInsensitiveEqual("Alias")) + return; + + // validating ProductVariantAttributeValue goes too far here. + if (!keyGroup.IsCaseInsensitiveEqual("SpecificationAttribute") && + !keyGroup.IsCaseInsensitiveEqual("SpecificationAttributeOption") && + !keyGroup.IsCaseInsensitiveEqual("ProductAttribute") && + !keyGroup.IsCaseInsensitiveEqual("ProductAttributeOption")) + { + return; + } + + // check alias duplicate + if (entry.InitialState == EntityState.Added || entry.InitialState == EntityState.Modified) + { + prop.LocaleValue = SeoExtensions.GetSeName(prop.LocaleValue); + if (prop.LocaleValue.HasValue() && HasAliasDuplicate(prop)) + { + RevertChanges(entry, string.Concat(T("Common.Error.AliasAlreadyExists", prop.LocaleValue), " ", T("Common.Error.ChooseDifferentValue"))); + return; + } + } + + if (IsPropertyModified(entry, "LocaleValue")) + { + if (keyGroup.IsCaseInsensitiveEqual("SpecificationAttribute") || keyGroup.IsCaseInsensitiveEqual("SpecificationAttributeOption")) + { + _catalogSearchQueryAliasMapper.Value.ClearAttributeCache(); + } + else if (keyGroup.IsCaseInsensitiveEqual("ProductAttribute") || keyGroup.IsCaseInsensitiveEqual("ProductVariantAttributeValue")) + { + // not necessary for ProductAttributeOption + _catalogSearchQueryAliasMapper.Value.ClearVariantCache(); + } + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/SlugSupportedHook.cs b/src/Libraries/SmartStore.Services/Hooks/SlugSupportedHook.cs new file mode 100644 index 0000000000..e744e41f2d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/SlugSupportedHook.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services.Seo; + +namespace SmartStore.Services.Common +{ + public class SlugSupportedHook : DbSaveHook + { + private readonly Lazy _urlRecordService; + private readonly HashSet _toDelete = new HashSet(); + + public SlugSupportedHook(Lazy urlRecordService) + { + _urlRecordService = urlRecordService; + } + + protected override void OnDeleted(ISlugSupported entity, IHookedEntity entry) + { + var entityType = entry.EntityType; + var records = _urlRecordService.Value.GetUrlRecordsFor(entityType.Name, entry.Entity.Id); + _toDelete.AddRange(records); + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + using (_urlRecordService.Value.BeginScope()) + { + _toDelete.Each(x => _urlRecordService.Value.DeleteUrlRecord(x)); + } + + scope.Commit(); + _toDelete.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/SlugSupportedPostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/SlugSupportedPostDeleteHook.cs deleted file mode 100644 index 9c7c0cbb49..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/SlugSupportedPostDeleteHook.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using SmartStore.Core; -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Seo; -using SmartStore.Services.Seo; - -namespace SmartStore.Services.Common -{ - public class SlugSupportedPostDeleteHook : PostDeleteHook - { - private readonly Lazy _urlRecordService; - - public SlugSupportedPostDeleteHook(Lazy urlRecordService) - { - this._urlRecordService = urlRecordService; - } - - public override void Hook(ISlugSupported entity, HookEntityMetadata metadata) - { - var baseEntity = entity as BaseEntity; - - if (baseEntity == null) - return; - - var entityType = baseEntity.GetUnproxiedType(); - - var records = _urlRecordService.Value.GetUrlRecordsFor(entityType.Name, baseEntity.Id); - records.Each(x => _urlRecordService.Value.DeleteUrlRecord(x)); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Hooks/SoftDeletableHook.cs b/src/Libraries/SmartStore.Services/Hooks/SoftDeletableHook.cs new file mode 100644 index 0000000000..a7fed44987 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/SoftDeletableHook.cs @@ -0,0 +1,71 @@ +using Autofac; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services.Security; +using SmartStore.Services.Seo; +using SmartStore.Data; + +namespace SmartStore.Services.Hooks +{ + public class SoftDeletablePreUpdateHook : DbSaveHook + { + private readonly IComponentContext _ctx; + + public SoftDeletablePreUpdateHook(IComponentContext ctx) + { + _ctx = ctx; + } + + protected override void OnUpdating(ISoftDeletable entity, IHookedEntity entry) + { + var baseEntity = entry.Entity; + + var prop = entry.Entry.Property("Deleted"); + var deletedModified = !prop.CurrentValue.Equals(prop.OriginalValue); + if (!deletedModified) + return; + + var entityType = entry.EntityType; + + // mark orphaned ACL records as idle + var aclSupported = baseEntity as IAclSupported; + if (aclSupported != null && aclSupported.SubjectToAcl) + { + var shouldSetIdle = entity.Deleted; + var rsAclRecord = _ctx.Resolve>(); + + var aclService = _ctx.Resolve(); + var records = aclService.GetAclRecordsFor(entityType.Name, baseEntity.Id); + foreach (var record in records) + { + record.IsIdle = shouldSetIdle; + aclService.UpdateAclRecord(record); + } + } + + // Delete orphaned inactive UrlRecords. + // We keep the active ones on purpose in order to be able to fully restore a soft deletable entity once we implemented the "recycle bin" feature + var slugSupported = baseEntity as ISlugSupported; + if (slugSupported != null && entity.Deleted) + { + var rsUrlRecord = _ctx.Resolve>(); + + var urlRecordService = _ctx.Resolve(); + var activeRecords = urlRecordService.GetUrlRecordsFor(entityType.Name, baseEntity.Id); + using (urlRecordService.BeginScope()) + { + foreach (var record in activeRecords) + { + if (!record.IsActive) + { + urlRecordService.DeleteUrlRecord(record); + } + } + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/SoftDeletablePreUpdateHook.cs b/src/Libraries/SmartStore.Services/Hooks/SoftDeletablePreUpdateHook.cs deleted file mode 100644 index 82e9a734cd..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/SoftDeletablePreUpdateHook.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Autofac; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Seo; -using SmartStore.Services.Security; -using SmartStore.Services.Seo; - -namespace SmartStore.Services.Hooks -{ - - public class SoftDeletablePreUpdateHook : PreUpdateHook - { - private readonly IComponentContext _ctx; - - public SoftDeletablePreUpdateHook(IComponentContext ctx) - { - this._ctx = ctx; - } - - public override void Hook(ISoftDeletable entity, HookEntityMetadata metadata) - { - var baseEntity = entity as BaseEntity; - - if (baseEntity == null) - return; - - var dbContext = _ctx.Resolve(); - var modifiedProps = dbContext.GetModifiedProperties(baseEntity); - - if (!modifiedProps.ContainsKey("Deleted")) - return; - - var entityType = baseEntity.GetUnproxiedType(); - - using (var scope = new DbContextScope(ctx: dbContext, autoCommit: false)) - { - // mark orphaned ACL records as idle - var aclSupported = baseEntity as IAclSupported; - if (aclSupported != null && aclSupported.SubjectToAcl) - { - var shouldSetIdle = entity.Deleted; - var rsAclRecord = _ctx.Resolve>(); - - var aclService = _ctx.Resolve(); - var records = aclService.GetAclRecordsFor(entityType.Name, baseEntity.Id); - foreach (var record in records) - { - record.IsIdle = shouldSetIdle; - aclService.UpdateAclRecord(record); - } - } - - // Delete orphaned inactive UrlRecords. - // We keep the active ones on purpose in order to be able to fully restore a soft deletable entity once we implemented the "recycle bin" feature - var slugSupported = baseEntity as ISlugSupported; - if (slugSupported != null && entity.Deleted) - { - var rsUrlRecord = _ctx.Resolve>(); - - var urlRecordService = _ctx.Resolve(); - var activeRecords = urlRecordService.GetUrlRecordsFor(entityType.Name, baseEntity.Id); - foreach (var record in activeRecords) - { - if (!record.IsActive) - { - urlRecordService.DeleteUrlRecord(record); - } - } - } - } - } - - public override bool RequiresValidation - { - get { return false; } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Hooks/StoreMappingEntityHook.cs b/src/Libraries/SmartStore.Services/Hooks/StoreMappingEntityHook.cs new file mode 100644 index 0000000000..422ba4c281 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/StoreMappingEntityHook.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services.Stores; + +namespace SmartStore.Services.Common +{ + public class StoreMappingEntityHook : DbSaveHook + { + private readonly Lazy _storeMappingService; + private readonly HashSet _toDelete = new HashSet(); + + public StoreMappingEntityHook(Lazy storeMappingService) + { + _storeMappingService = storeMappingService; + } + + protected override void OnDeleted(IStoreMappingSupported entity, IHookedEntity entry) + { + var entityType = entry.EntityType; + + var records = _storeMappingService.Value + .GetStoreMappingsFor(entityType.Name, entry.Entity.Id) + .ToList(); + + _toDelete.AddRange(records); + } + + public override void OnAfterSaveCompleted() + { + if (_toDelete.Count == 0) + return; + + using (var scope = new DbContextScope(autoCommit: false)) + { + _toDelete.Each(x => _storeMappingService.Value.DeleteStoreMapping(x)); + scope.Commit(); + } + + _toDelete.Clear(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Hooks/StoreMappingEntityPostDeleteHook.cs b/src/Libraries/SmartStore.Services/Hooks/StoreMappingEntityPostDeleteHook.cs deleted file mode 100644 index 212ef2626f..0000000000 --- a/src/Libraries/SmartStore.Services/Hooks/StoreMappingEntityPostDeleteHook.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Linq; -using SmartStore.Core; -using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Stores; -using SmartStore.Services.Stores; - -namespace SmartStore.Services.Common -{ - public class StoreMappingEntityPostDeleteHook : PostDeleteHook - { - private readonly Lazy _storeMappingService; - - public StoreMappingEntityPostDeleteHook(Lazy storeMappingService) - { - _storeMappingService = storeMappingService; - } - - public override void Hook(IStoreMappingSupported entity, HookEntityMetadata metadata) - { - var baseEntity = entity as BaseEntity; - - if (baseEntity == null) - return; - - var entityType = baseEntity.GetUnproxiedType(); - - var records = _storeMappingService.Value - .GetStoreMappingsFor(entityType.Name, baseEntity.Id) - .ToList(); - - records.Each(x => _storeMappingService.Value.DeleteStoreMapping(x)); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Hooks/StoreSaveHook.cs b/src/Libraries/SmartStore.Services/Hooks/StoreSaveHook.cs new file mode 100644 index 0000000000..12b02a4cc1 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/StoreSaveHook.cs @@ -0,0 +1,59 @@ +using System; +using SmartStore.Services.Media; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services.Tasks; +using SmartStore.Services.Stores; +using System.Web; +using SmartStore.Utilities; + +namespace SmartStore.Services.Hooks +{ + public class StoreSaveHook : DbSaveHook + { + private readonly IPictureService _pictureService; + private readonly ITaskScheduler _taskScheduler; + private readonly IStoreService _storeService; + private readonly HttpContextBase _httpContext; + + public StoreSaveHook(IPictureService pictureService, ITaskScheduler taskScheduler, IStoreService storeService, HttpContextBase httpContext) + { + _pictureService = pictureService; + _taskScheduler = taskScheduler; + _storeService = storeService; + _httpContext = httpContext; + } + + protected override void OnUpdating(Store entity, IHookedEntity entry) + { + if (entry.IsPropertyModified(nameof(entity.ContentDeliveryNetwork))) + { + _pictureService.ClearUrlCache(); + } + } + + protected override void OnInserted(Store entity, IHookedEntity entry) + { + TryChangeSchedulerBaseUrl(); + } + + protected override void OnUpdated(Store entity, IHookedEntity entry) + { + TryChangeSchedulerBaseUrl(); + } + + protected override void OnDeleted(Store entity, IHookedEntity entry) + { + _pictureService.ClearUrlCache(); + TryChangeSchedulerBaseUrl(); + } + + private void TryChangeSchedulerBaseUrl() + { + if (CommonHelper.GetAppSetting("sm:TaskSchedulerBaseUrl").IsWebUrl() == false) + { + _taskScheduler.SetBaseUrl(_storeService, _httpContext); + } + } + } +} 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/ICommonServices.cs b/src/Libraries/SmartStore.Services/ICommonServices.cs index 1f999aa6b7..0f2030395f 100644 --- a/src/Libraries/SmartStore.Services/ICommonServices.cs +++ b/src/Libraries/SmartStore.Services/ICommonServices.cs @@ -9,92 +9,35 @@ using SmartStore.Services.Security; using SmartStore.Services.Configuration; using SmartStore.Services.Stores; -using Autofac; using SmartStore.Services.Helpers; +using Autofac; +using SmartStore.Services.Media; +using SmartStore.Services.Messages; namespace SmartStore.Services { public interface ICommonServices { - IComponentContext Container - { - get; - } - - ICacheManager Cache - { - get; - } - - IRequestCache RequestCache - { - get; - } - - IDisplayControl DisplayControl - { - get; - } - - IDbContext DbContext - { - get; - } - - IStoreContext StoreContext - { - get; - } - - IWebHelper WebHelper - { - get; - } - - IWorkContext WorkContext - { - get; - } - - IEventPublisher EventPublisher - { - get; - } - - ILocalizationService Localization - { - get; - } - - ICustomerActivityService CustomerActivity - { - get; - } - - INotifier Notifier - { - get; - } - - IPermissionService Permissions - { - get; - } - - ISettingService Settings - { - get; - } - - IStoreService StoreService - { - get; - } - - IDateTimeHelper DateTimeHelper - { - get; - } + IComponentContext Container { get; } + IApplicationEnvironment ApplicationEnvironment { get; } + ICacheManager Cache { get; } + IRequestCache RequestCache { get; } + IDisplayControl DisplayControl { get; } + IDbContext DbContext { get; } + IStoreContext StoreContext { get; } + IWebHelper WebHelper { get; } + IWorkContext WorkContext { get; } + IEventPublisher EventPublisher { get; } + ILocalizationService Localization { get; } + ICustomerActivityService CustomerActivity { get; } + IPictureService PictureService { get; } + INotifier Notifier { get; } + IPermissionService Permissions { get; } + ISettingService Settings { get; } + IStoreService StoreService { get; } + IDateTimeHelper DateTimeHelper { get; } + IChronometer Chronometer { get; } + IMessageFactory MessageFactory { get; } } public static class ICommonServicesExtensions diff --git a/src/Libraries/SmartStore.Services/IScopedService.cs b/src/Libraries/SmartStore.Services/IScopedService.cs new file mode 100644 index 0000000000..3c081b953f --- /dev/null +++ b/src/Libraries/SmartStore.Services/IScopedService.cs @@ -0,0 +1,24 @@ +using System; + +namespace SmartStore.Services +{ + public interface IScopedService + { + /// + /// Creates a long running unit of work in which cache eviction is suppressed + /// + /// Specifies whether the cache should be evicted completely on batch disposal + /// A disposable unit of work + IDisposable BeginScope(bool clearCache = true); + + /// + /// Gets a value indicating whether data has changed during a request, making cache eviction necessary. + /// + /// + /// Cache eviction sets this member to false + /// + bool HasChanges { get; } + + void ClearCache(); + } +} diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs index 6195301c03..09f2d9bf1a 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizationService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Xml; +using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Plugins; @@ -12,8 +14,6 @@ namespace SmartStore.Services.Localization /// public partial interface ILocalizationService { - void ClearCache(); - /// /// Deletes a locale string resource /// @@ -48,21 +48,28 @@ public partial interface ILocalizationService /// Language identifier /// A value indicating whether to log error if locale string resource is not found /// Locale string resource - LocaleStringResource GetLocaleStringResourceByName(string resourceName, int languageId, - bool logIfNotFound = true); + LocaleStringResource GetLocaleStringResourceByName(string resourceName, int languageId, bool logIfNotFound = true); /// /// Gets all locale string resources by language identifier /// /// Language identifier /// Locale string resources - IList GetAllResources(int languageId); + IQueryable All(int languageId); - /// - /// Inserts a locale string resource - /// - /// Locale string resource - void InsertLocaleStringResource(LocaleStringResource localeStringResource); + /// + /// Gets all locale string resources by language identifier starting with a prefix + /// + /// Pattern + /// Language identifier + /// Locale string resources matching language id and starting with + IList GetResourcesByPattern(string pattern, int languageId); + + /// + /// Inserts a locale string resource + /// + /// Locale string resource + void InsertLocaleStringResource(LocaleStringResource localeStringResource); /// /// Updates the locale string resource @@ -70,27 +77,6 @@ LocaleStringResource GetLocaleStringResourceByName(string resourceName, int lang /// Locale string resource void UpdateLocaleStringResource(LocaleStringResource localeStringResource); - ///// - ///// Gets all locale string resources by language identifier - ///// - ///// Language identifier - ///// Locale string resources - //Dictionary> GetAllResourceValues(int languageId); - - /// - /// Gets all locale string resources by language identifier - /// - /// Language identifier - /// Locale string resources - IDictionary> GetResourceValues(int languageId, bool forceAll = false); - - //// codehint: sm-delete - ///// - ///// Gets a resource string based on the specified ResourceKey property. - ///// - ///// A string representing a ResourceKey. - ///// A string representing the requested resource string. - //string GetResource(string resourceKey); /// /// Gets a resource string based on the specified ResourceKey property. diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index 070d1fbfe5..979ef91ca5 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -9,8 +9,8 @@ namespace SmartStore.Services.Localization /// /// Localized entity service interface /// - public partial interface ILocalizedEntityService - { + public partial interface ILocalizedEntityService : IScopedService + { /// /// Deletes a localized property /// @@ -42,11 +42,44 @@ public partial interface ILocalizedEntityService /// 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/LanguageService.cs b/src/Libraries/SmartStore.Services/Localization/LanguageService.cs index 1ca651bd67..3bcb3646e3 100644 --- a/src/Libraries/SmartStore.Services/Localization/LanguageService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LanguageService.cs @@ -6,28 +6,17 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Events; using SmartStore.Services.Configuration; -using SmartStore.Core.Domain.Stores; using SmartStore.Services.Stores; using SmartStore.Collections; using SmartStore.Core; +using SmartStore.Data.Caching; namespace SmartStore.Services.Localization { - /// - /// Language service - /// public partial class LanguageService : ILanguageService { - #region Constants - private const string LANGUAGES_ALL_KEY = "SmartStore.language.all-{0}"; private const string LANGUAGES_COUNT = "SmartStore.language.count-{0}"; - private const string LANGUAGES_BY_CULTURE_KEY = "SmartStore.language.culture-{0}"; - private const string LANGUAGES_BY_SEOCODE_KEY = "SmartStore.language.seocode-{0}"; - private const string LANGUAGES_PATTERN_KEY = "SmartStore.language."; - private const string LANGUAGES_BY_ID_KEY = "SmartStore.language.id-{0}"; - #endregion - - #region Fields + private const string LANGUAGES_PATTERN_KEY = "SmartStore.language.*"; private readonly IRepository _languageRepository; private readonly IStoreMappingService _storeMappingService; @@ -39,10 +28,6 @@ public partial class LanguageService : ILanguageService private readonly LocalizationSettings _localizationSettings; private readonly IEventPublisher _eventPublisher; - #endregion - - #region Ctor - public LanguageService( IRequestCache requestCache, ICacheManager cache, @@ -54,31 +39,22 @@ public LanguageService( IStoreService storeService, IStoreContext storeContext) { - this._requestCache = requestCache; - this._cache = cache; - this._languageRepository = languageRepository; - this._settingService = settingService; - this._localizationSettings = localizationSettings; - this._eventPublisher = eventPublisher; - this._storeMappingService = storeMappingService; - this._storeService = storeService; - this._storeContext = storeContext; + _requestCache = requestCache; + _cache = cache; + _languageRepository = languageRepository; + _settingService = settingService; + _localizationSettings = localizationSettings; + _eventPublisher = eventPublisher; + _storeMappingService = storeMappingService; + _storeService = storeService; + _storeContext = storeContext; } - #endregion - - #region Methods - - /// - /// Deletes a language - /// - /// Language public virtual void DeleteLanguage(Language language) { - if (language == null) - throw new ArgumentNullException("language"); - - //update default admin area language (if required) + Guard.NotNull(language, nameof(language)); + + // Update default admin area language (if required) if (_localizationSettings.DefaultAdminLanguageId == language.Id) { foreach (var activeLanguage in GetAllLanguages()) @@ -95,30 +71,21 @@ public virtual void DeleteLanguage(Language language) _languageRepository.Delete(language); // cache - _requestCache.RemoveByPattern(LANGUAGES_PATTERN_KEY); - _cache.RemoveByPattern(ServiceCacheConsumer.STORE_LANGUAGE_MAP_KEY); - - //event notification - _eventPublisher.EntityDeleted(language); + _requestCache.RemoveByPattern(LANGUAGES_PATTERN_KEY); } - /// - /// Gets all languages - /// - /// A value indicating whether to show hidden records - /// Load records allows only in specified store; pass 0 to load all records - /// Language collection public virtual IList GetAllLanguages(bool showHidden = false, int storeId = 0) { - string key = string.Format(LANGUAGES_ALL_KEY, showHidden); - var languages = _requestCache.Get(key, () => + var query = _languageRepository.Table; + + if (!showHidden) { - var query = _languageRepository.Table; - if (!showHidden) - query = query.Where(x => x.Published); - query = query.OrderBy(x => x.DisplayOrder); - return query.ToList(); - }); + query = query.Where(x => x.Published); + } + + query = query.OrderBy(x => x.DisplayOrder); + + var languages = query.ToListCached("db.lang.all.{0}".FormatInvariant(showHidden)); // store mapping if (storeId > 0) @@ -131,11 +98,6 @@ public virtual IList GetAllLanguages(bool showHidden = false, int stor return languages; } - /// - /// Gets languages count - /// - /// A value indicating whether to consider hidden records - /// The count of Languages public virtual int GetLanguagesCount(bool showHidden = false) { string key = string.Format(LANGUAGES_COUNT, showHidden); @@ -148,56 +110,34 @@ public virtual int GetLanguagesCount(bool showHidden = false) }); } - /// - /// Gets a language - /// - /// Language identifier - /// Language public virtual Language GetLanguageById(int languageId) { if (languageId == 0) return null; - string key = string.Format(LANGUAGES_BY_ID_KEY, languageId); - return _requestCache.Get(key, () => - { - return _languageRepository.GetById(languageId); - }); - } + return _languageRepository.GetByIdCached(languageId, "db.lang.id-" + languageId); + } - /// - /// Gets a language by culture code (e.g.: en-US) - /// - /// Culture code - /// Language public virtual Language GetLanguageByCulture(string culture) { if (!culture.HasValue()) return null; - string key = string.Format(LANGUAGES_BY_CULTURE_KEY, culture); - return _requestCache.Get(key, () => - { - return _languageRepository.Table.Where(x => culture.Equals(x.LanguageCulture, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - }); - } + return _languageRepository.Table + .Where(x => culture.Equals(x.LanguageCulture, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefaultCached("db.lang.culture-" + culture); + } public virtual Language GetLanguageBySeoCode(string seoCode) { if (!seoCode.HasValue()) return null; - string key = string.Format(LANGUAGES_BY_SEOCODE_KEY, seoCode); - return _requestCache.Get(key, () => - { - return _languageRepository.Table.Where(x => seoCode.Equals(x.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - }); - } + return _languageRepository.Table + .Where(x => seoCode.Equals(x.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefaultCached("db.lang.seo-" + seoCode); + } - /// - /// Inserts a language - /// - /// Language public virtual void InsertLanguage(Language language) { if (language == null) @@ -205,18 +145,10 @@ public virtual void InsertLanguage(Language language) _languageRepository.Insert(language); - //cache + // cache _requestCache.RemoveByPattern(LANGUAGES_PATTERN_KEY); - _cache.RemoveByPattern(ServiceCacheConsumer.STORE_LANGUAGE_MAP_KEY); - - //event notification - _eventPublisher.EntityInserted(language); } - /// - /// Updates a language - /// - /// Language public virtual void UpdateLanguage(Language language) { if (language == null) @@ -227,10 +159,6 @@ public virtual void UpdateLanguage(Language language) //cache _requestCache.RemoveByPattern(LANGUAGES_PATTERN_KEY); - _cache.RemoveByPattern(ServiceCacheConsumer.STORE_LANGUAGE_MAP_KEY); - - //event notification - _eventPublisher.EntityUpdated(language); } public virtual bool IsPublishedLanguage(string seoCode, int storeId = 0) @@ -241,7 +169,7 @@ public virtual bool IsPublishedLanguage(string seoCode, int storeId = 0) var map = this.GetStoreLanguageMap(); if (map.ContainsKey(storeId)) { - return map[storeId].Any(x => x.Item2 == seoCode); + return map[storeId].Any(x => x.UniqueSeoCode == seoCode); } return false; @@ -258,7 +186,7 @@ public virtual bool IsPublishedLanguage(int languageId, int storeId = 0) var map = this.GetStoreLanguageMap(); if (map.ContainsKey(storeId)) { - return map[storeId].Any(x => x.Item1 == languageId); + return map[storeId].Any(x => x.Id == languageId); } return false; @@ -272,7 +200,7 @@ public virtual string GetDefaultLanguageSeoCode(int storeId = 0) var map = this.GetStoreLanguageMap(); if (map.ContainsKey(storeId)) { - return map[storeId].FirstOrDefault().Item2; + return map[storeId].FirstOrDefault().UniqueSeoCode; } return null; @@ -286,7 +214,7 @@ public virtual int GetDefaultLanguageId(int storeId = 0) var map = this.GetStoreLanguageMap(); if (map.ContainsKey(storeId)) { - return map[storeId].FirstOrDefault().Item1; + return map[storeId].FirstOrDefault().Id; } return 0; @@ -296,11 +224,11 @@ public virtual int GetDefaultLanguageId(int storeId = 0) /// Gets a map of active/published store languages /// /// A map of store languages where key is the store id and values are tuples of language ids and seo codes - protected virtual Multimap> GetStoreLanguageMap() + protected virtual Multimap GetStoreLanguageMap() { - var result = _cache.Get(ServiceCacheConsumer.STORE_LANGUAGE_MAP_KEY, () => + var result = _cache.Get(ServiceCacheBuster.STORE_LANGUAGE_MAP_KEY, () => { - var map = new Multimap>(); + var map = new Multimap(); var allStores = _storeService.GetAllStores(); foreach (var store in allStores) @@ -315,13 +243,13 @@ protected virtual Multimap> GetStoreLanguageMap() // absolute fallback firstStoreLang = GetAllLanguages(true).FirstOrDefault(); } - map.Add(store.Id, new Tuple(firstStoreLang.Id, firstStoreLang.UniqueSeoCode)); + map.Add(store.Id, new MinifiedLanguage { Id = firstStoreLang.Id, UniqueSeoCode = firstStoreLang.UniqueSeoCode }); } else { foreach (var lang in languages) { - map.Add(store.Id, new Tuple(lang.Id, lang.UniqueSeoCode)); + map.Add(store.Id, new MinifiedLanguage { Id = lang.Id, UniqueSeoCode = lang.UniqueSeoCode }); } } } @@ -332,6 +260,10 @@ protected virtual Multimap> GetStoreLanguageMap() return result; } - #endregion + public class MinifiedLanguage + { + public int Id { get; set; } + public string UniqueSeoCode { get; set; } + } } } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs new file mode 100644 index 0000000000..eef621f93f --- /dev/null +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs @@ -0,0 +1,651 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using System.Xml; +using SmartStore.ComponentModel; +using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Plugins; +using SmartStore.Utilities; + +namespace SmartStore.Services.Localization +{ + public static class LocalizationExtensions + { + /// + /// Get localized property of an entity + /// + /// Entity type + /// Entity + /// 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 LocalizedValue GetLocalized(this T entity, Expression> keySelector, bool detectEmptyHtml = false) + where T : BaseEntity, ILocalizedEntity + { + return GetLocalizedEx( + entity, + entity.GetEntityName(), + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().WorkingLanguage, + detectEmptyHtml: detectEmptyHtml); + } + + /// + /// Get localized property of an entity + /// + /// Entity 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, + Expression> keySelector, + int languageId, + 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), + EngineContext.Current.Resolve().GetLanguageById(languageId), + returnDefaultValue, + ensureTwoPublishedLanguages, + detectEmptyHtml); + } + + /// + /// Get localized property of an entity + /// + /// Entity 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 + /// 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, + int languageId, + 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), + 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 LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector) + { + return GetLocalizedEx( + node, + "Category", + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().WorkingLanguage); + } + + /// + /// Get localized property of an instance + /// + /// Node + /// Key selector + /// /// Language identifier + /// Localized property + public static LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector, int languageId) + { + return GetLocalizedEx( + node, + "Category", + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().GetLanguageById(languageId)); + } + + /// + /// Get localized property of an instance + /// + /// Node + /// Key selector + /// /// Language + /// Localized property + public static LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector, Language language) + { + return GetLocalizedEx( + node, + "Category", + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + language); + } + + internal static LocalizedValue GetLocalizedEx(T entity, + string localeKeyGroup, + string localeKey, + Func fallback, + Language requestLanguage, + bool returnDefaultValue = true, + bool ensureTwoPublishedLanguages = true, + bool detectEmptyHtml = false) + where T : ILocalizedEntity + { + if (entity == null) + throw new ArgumentNullException(nameof(entity)); + + TProp result = default; + var str = string.Empty; + + var languageService = EngineContext.Current.Resolve(); + + Language currentLanguage = null; + if (requestLanguage != null) + { + // Ensure that we have at least two published languages + var loadLocalizedValue = true; + if (ensureTwoPublishedLanguages) + { + var totalPublishedLanguages = languageService.GetLanguagesCount(false); + loadLocalizedValue = totalPublishedLanguages >= 2; + } + + // Localized value + if (loadLocalizedValue) + { + var leService = EngineContext.Current.Resolve(); + str = leService.GetLocalizedValue(requestLanguage.Id, entity.Id, localeKeyGroup, localeKey); + + if (detectEmptyHtml && str.HasValue() && str.RemoveHtml().IsEmpty()) + { + str = string.Empty; + } + + if (str.HasValue()) + { + currentLanguage = requestLanguage; + result = (TProp)str.Convert(typeof(TProp), CultureInfo.InvariantCulture); + } + } + } + + // Set default value if required + if (returnDefaultValue && str.IsEmpty()) + { + currentLanguage = languageService.GetLanguageById(languageService.GetDefaultLanguageId()); + result = fallback(entity); + } + + 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; + } + + + /// + /// Get localized value of enum + /// + /// Enum + /// Enum value + /// Localization service + /// Work context + /// Localized value + public static string GetLocalizedEnum(this T enumValue, ILocalizationService localizationService, IWorkContext workContext) + where T : struct + { + if (workContext == null) + throw new ArgumentNullException(nameof(workContext)); + + return GetLocalizedEnum(enumValue, localizationService, workContext.WorkingLanguage.Id); + } + + /// + /// Get localized value of enum + /// + /// Enum + /// Enum value + /// Localization service + /// Language identifier + /// Localized value + public static string GetLocalizedEnum(this T enumValue, ILocalizationService localizationService, int languageId = 0) + where T : struct + { + if (localizationService == null) + throw new ArgumentNullException(nameof(localizationService)); + + if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type"); + + //localized value + string resourceName = string.Format("Enums.{0}.{1}", + typeof(T).ToString(), + //Convert.ToInt32(enumValue) + enumValue.ToString()); + + string result = localizationService.GetResource(resourceName, languageId, false, "", true); + + // Set default value if required + if (String.IsNullOrEmpty(result)) + result = Inflector.Titleize(enumValue.ToString()); + + return result; + } + + /// + /// Delete a locale resource + /// + /// Plugin + /// Resource name + public static void DeletePluginLocaleResource(this BasePlugin plugin, string resourceName) + { + var localizationService = EngineContext.Current.Resolve(); + var languageService = EngineContext.Current.Resolve(); + DeletePluginLocaleResource(plugin, localizationService, languageService, resourceName); + } + + /// + /// Delete a locale resource + /// + /// Plugin + /// Localization service + /// Language service + /// Resource name + public static void DeletePluginLocaleResource(this BasePlugin plugin, + ILocalizationService localizationService, + ILanguageService languageService, + string resourceName) + { + // actually plugin instance is not required + if (plugin == null) + throw new ArgumentNullException(nameof(plugin)); + + if (localizationService == null) + throw new ArgumentNullException(nameof(localizationService)); + + if (languageService == null) + throw new ArgumentNullException(nameof(languageService)); + + foreach (var lang in languageService.GetAllLanguages(true)) + { + var lsr = localizationService.GetLocaleStringResourceByName(resourceName, lang.Id, false); + if (lsr != null) + localizationService.DeleteLocaleStringResource(lsr); + } + } + + /// + /// Add a locale resource (if new) or update an existing one + /// + /// Plugin + /// Resource name + /// Resource value + public static void AddOrUpdatePluginLocaleResource(this BasePlugin plugin, + string resourceName, string resourceValue) + { + var localizationService = EngineContext.Current.Resolve(); + var languageService = EngineContext.Current.Resolve(); + AddOrUpdatePluginLocaleResource(plugin, localizationService, + languageService, resourceName, resourceValue); + } + + /// + /// Add a locale resource (if new) or update an existing one + /// + /// Plugin + /// Localization service + /// Language service + /// Resource name + /// Resource value + public static void AddOrUpdatePluginLocaleResource(this BasePlugin plugin, + ILocalizationService localizationService, + ILanguageService languageService, + string resourceName, + string resourceValue) + { + // Actually plugin instance is not required + if (plugin == null) + throw new ArgumentNullException("plugin"); + if (localizationService == null) + throw new ArgumentNullException("localizationService"); + if (languageService == null) + throw new ArgumentNullException("languageService"); + + foreach (var lang in languageService.GetAllLanguages(true)) + { + var lsr = localizationService.GetLocaleStringResourceByName(resourceName, lang.Id, false); + if (lsr == null) + { + lsr = new LocaleStringResource() + { + LanguageId = lang.Id, + ResourceName = resourceName, + ResourceValue = resourceValue, + IsFromPlugin = true + }; + localizationService.InsertLocaleStringResource(lsr); + } + else + { + lsr.ResourceValue = resourceValue; + localizationService.UpdateLocaleStringResource(lsr); + } + } + } + + public static void AddPluginLocaleResource(this BasePlugin plugin, ILocalizationService localizationService, string resourceName, string value, int languageID) + { + if (languageID != 0) + { + localizationService.InsertLocaleStringResource(new LocaleStringResource + { + LanguageId = languageID, + ResourceName = resourceName, + ResourceValue = value, + IsFromPlugin = true + }); + } + } + + /// + /// Get localized property value of a plugin + /// + /// Plugin + /// Plugin + /// Localization service + /// Name of the property + /// Language identifier + /// A value indicating whether to return default value (if localized is not found) + /// Localized value + public static string GetLocalizedValue(this T plugin, ILocalizationService localizationService, string propertyName, int languageId = 0, bool returnDefaultValue = true) + where T : IPlugin + { + if (plugin == null) + throw new ArgumentNullException(nameof(plugin)); + + if (plugin.PluginDescriptor == null) + throw new ArgumentNullException("PluginDescriptor cannot be loaded"); + + return plugin.PluginDescriptor.GetLocalizedValue(localizationService, propertyName, languageId, returnDefaultValue); + } + + /// + /// Get localized property value of a plugin + /// + /// Plugin descriptor + /// Localization service + /// Name of the property + /// Language identifier + /// A value indicating whether to return default value (if localized is not found) + /// Localized value + public static string GetLocalizedValue(this PluginDescriptor descriptor, ILocalizationService localizationService, string propertyName, int languageId = 0, bool returnDefaultValue = true) + { + if (localizationService == null) + throw new ArgumentNullException(nameof(localizationService)); + + if (descriptor == null) + throw new ArgumentNullException(nameof(descriptor)); + + if (propertyName == null) + throw new ArgumentNullException(nameof(propertyName)); + + string systemName = descriptor.SystemName; + string resourceName = string.Format("Plugins.{0}.{1}", propertyName, systemName); + string result = localizationService.GetResource(resourceName, languageId, false, "", true); + + if (String.IsNullOrEmpty(result) && returnDefaultValue) + { + var fastProp = FastProperty.GetProperty(descriptor.GetType(), propertyName); + if (fastProp != null) + { + result = fastProp.GetValue(descriptor) as string; + } + } + + return result; + } + + /// + /// Save localized plugin descriptor value + /// + /// Plugin + /// Plugin + /// Localization service + /// Language identifier + /// Name of the property + /// Localized value + public static void SaveLocalizedValue(this T plugin, ILocalizationService localizationService, int languageId, + string propertyName, string value) where T : IPlugin + { + if (plugin == null) + throw new ArgumentNullException("plugin"); + + if (plugin.PluginDescriptor == null) + throw new ArgumentNullException("PluginDescriptor cannot be loaded"); + + plugin.PluginDescriptor.SaveLocalizedValue(localizationService, languageId, propertyName, value); + } + + /// + /// Save localized plugin descriptor value + /// + /// Plugin + /// Localization service + /// Language identifier + /// Name of the property + /// Localized value + public static void SaveLocalizedValue(this PluginDescriptor descriptor, ILocalizationService localizationService, int languageId, + string propertyName, string value) + { + if (localizationService == null) + throw new ArgumentNullException(nameof(localizationService)); + + if (languageId == 0) + throw new ArgumentOutOfRangeException(nameof(languageId), "Language ID should not be 0"); + + if (descriptor == null) + throw new ArgumentNullException(nameof(descriptor)); + + if (propertyName == null) + throw new ArgumentNullException(nameof(propertyName)); + + string systemName = descriptor.SystemName; + string resourceName = string.Format("Plugins.{0}.{1}", propertyName, systemName); + var resource = localizationService.GetLocaleStringResourceByName(resourceName, languageId, false); + + if (resource != null) + { + if (string.IsNullOrWhiteSpace(value)) + { + //delete + localizationService.DeleteLocaleStringResource(resource); + } + else + { + //update + resource.ResourceValue = value; + localizationService.UpdateLocaleStringResource(resource); + } + } + else + { + if (!string.IsNullOrWhiteSpace(value)) + { + //insert + resource = new LocaleStringResource() + { + LanguageId = languageId, + ResourceName = resourceName, + ResourceValue = value, + }; + localizationService.InsertLocaleStringResource(resource); + } + } + } + + /// + /// Import language resources from XML file + /// + /// Language + /// XML + public static void ImportResourcesFromXml(this ILocalizationService service, + Language language, + string xml, + string rootKey = null, + bool sourceIsPlugin = false, + ImportModeFlags mode = ImportModeFlags.Insert | ImportModeFlags.Update, + bool updateTouchedResources = false) + { + if (language == null) + throw new ArgumentNullException(nameof(language)); + + if (String.IsNullOrEmpty(xml)) + return; + + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xml); + + service.ImportResourcesFromXml(language, xmlDoc, rootKey, sourceIsPlugin, mode, updateTouchedResources); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs deleted file mode 100644 index a55b8ab84f..0000000000 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs +++ /dev/null @@ -1,437 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; -using System.Xml; -using SmartStore.ComponentModel; -using SmartStore.Core; -using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Plugins; -using SmartStore.Utilities; - -namespace SmartStore.Services.Localization -{ - public static class LocalizationExtentions - { - /// - /// Get localized property of an entity - /// - /// Entity type - /// Entity - /// Key selector - /// Localized property - public static string GetLocalized(this T entity, Expression> keySelector) - where T : BaseEntity, ILocalizedEntity - { - var workContext = EngineContext.Current.Resolve(); - return GetLocalized(entity, keySelector, workContext.WorkingLanguage.Id); - } - /// - /// Get localized property of an entity - /// - /// Entity 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 - /// Localized property - public static string GetLocalized(this T entity, - Expression> keySelector, int languageId, - bool returnDefaultValue = true, bool ensureTwoPublishedLanguages = true) - where T : BaseEntity, ILocalizedEntity - { - return GetLocalized(entity, keySelector, languageId, returnDefaultValue, ensureTwoPublishedLanguages); - } - /// - /// 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 - /// Localized property - public static TPropType GetLocalized(this T entity, - Expression> keySelector, int languageId, - bool returnDefaultValue = true, bool ensureTwoPublishedLanguages = true) - where T : BaseEntity, ILocalizedEntity - { - if (entity == null) - throw new ArgumentNullException("entity"); - - 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)); - } - - TPropType result = default(TPropType); - string resultStr = string.Empty; - - //load localized value - string localeKeyGroup = typeof(T).Name; - string localeKey = propInfo.Name; - - if (languageId > 0) - { - //ensure that we have at least two published languages - bool loadLocalizedValue = true; - if (ensureTwoPublishedLanguages) - { - var lService = EngineContext.Current.Resolve(); - var totalPublishedLanguages = lService.GetLanguagesCount(false); - loadLocalizedValue = totalPublishedLanguages >= 2; - } - - //localized value - if (loadLocalizedValue) - { - var leService = EngineContext.Current.Resolve(); - resultStr = leService.GetLocalizedValue(languageId, entity.Id, localeKeyGroup, localeKey); - if (!String.IsNullOrEmpty(resultStr)) - result = resultStr.Convert(); - } - } - - //set default value if required - if (String.IsNullOrEmpty(resultStr) && returnDefaultValue) - { - //var localizer = (Func)_compiledExpressions.GetOrAdd(keySelector, exp => exp.Compile()); // --> MEM LEAK - var localizer = keySelector.Compile(); - result = localizer(entity); - } - - return result; - } - - /// - /// Get localized value of enum - /// - /// Enum - /// Enum value - /// Localization service - /// Work context - /// Localized value - public static string GetLocalizedEnum(this T enumValue, ILocalizationService localizationService, IWorkContext workContext) - where T : struct - { - if (workContext == null) - throw new ArgumentNullException("workContext"); - - return GetLocalizedEnum(enumValue, localizationService, workContext.WorkingLanguage.Id); - } - /// - /// Get localized value of enum - /// - /// Enum - /// Enum value - /// Localization service - /// Language identifier - /// Localized value - public static string GetLocalizedEnum(this T enumValue, ILocalizationService localizationService, int languageId = 0) - where T : struct - { - if (localizationService == null) - throw new ArgumentNullException("localizationService"); - - if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type"); - - //localized value - string resourceName = string.Format("Enums.{0}.{1}", - typeof(T).ToString(), - //Convert.ToInt32(enumValue) - enumValue.ToString()); - string result = localizationService.GetResource(resourceName, languageId, false, "", true); - - //set default value if required - if (String.IsNullOrEmpty(result)) - result = Inflector.Titleize(enumValue.ToString()); - - return result; - } - - /// - /// Delete a locale resource - /// - /// Plugin - /// Resource name - public static void DeletePluginLocaleResource(this BasePlugin plugin, - string resourceName) - { - var localizationService = EngineContext.Current.Resolve(); - var languageService = EngineContext.Current.Resolve(); - DeletePluginLocaleResource(plugin, localizationService, - languageService, resourceName); - } - /// - /// Delete a locale resource - /// - /// Plugin - /// Localization service - /// Language service - /// Resource name - public static void DeletePluginLocaleResource(this BasePlugin plugin, - ILocalizationService localizationService, ILanguageService languageService, - string resourceName) - { - //actually plugin instance is not required - if (plugin == null) - throw new ArgumentNullException("plugin"); - if (localizationService == null) - throw new ArgumentNullException("localizationService"); - if (languageService == null) - throw new ArgumentNullException("languageService"); - - foreach (var lang in languageService.GetAllLanguages(true)) - { - var lsr = localizationService.GetLocaleStringResourceByName(resourceName, lang.Id, false); - if (lsr != null) - localizationService.DeleteLocaleStringResource(lsr); - } - } - /// - /// Add a locale resource (if new) or update an existing one - /// - /// Plugin - /// Resource name - /// Resource value - public static void AddOrUpdatePluginLocaleResource(this BasePlugin plugin, - string resourceName, string resourceValue) - { - var localizationService = EngineContext.Current.Resolve(); - var languageService = EngineContext.Current.Resolve(); - AddOrUpdatePluginLocaleResource(plugin, localizationService, - languageService, resourceName, resourceValue); - } - /// - /// Add a locale resource (if new) or update an existing one - /// - /// Plugin - /// Localization service - /// Language service - /// Resource name - /// Resource value - public static void AddOrUpdatePluginLocaleResource(this BasePlugin plugin, - ILocalizationService localizationService, ILanguageService languageService, - string resourceName, string resourceValue) - { - //actually plugin instance is not required - if (plugin == null) - throw new ArgumentNullException("plugin"); - if (localizationService == null) - throw new ArgumentNullException("localizationService"); - if (languageService == null) - throw new ArgumentNullException("languageService"); - - foreach (var lang in languageService.GetAllLanguages(true)) - { - var lsr = localizationService.GetLocaleStringResourceByName(resourceName, lang.Id, false); - if (lsr == null) - { - lsr = new LocaleStringResource() - { - LanguageId = lang.Id, - ResourceName = resourceName, - ResourceValue = resourceValue, - IsFromPlugin = true - }; - localizationService.InsertLocaleStringResource(lsr); - } - else - { - lsr.ResourceValue = resourceValue; - localizationService.UpdateLocaleStringResource(lsr); - } - } - } - - public static void AddPluginLocaleResource(this BasePlugin plugin, ILocalizationService localizationService, string resourceName, string value, int languageID) - { - if (languageID != 0) - { - localizationService.InsertLocaleStringResource(new LocaleStringResource - { - LanguageId = languageID, - ResourceName = resourceName, - ResourceValue = value, - IsFromPlugin = true - }); - } - } - - /// - /// Get localized property value of a plugin - /// - /// Plugin - /// Plugin - /// Localization service - /// Name of the property - /// Language identifier - /// A value indicating whether to return default value (if localized is not found) - /// Localized value - public static string GetLocalizedValue(this T plugin, ILocalizationService localizationService, string propertyName, int languageId = 0, bool returnDefaultValue = true) - where T : IPlugin - { - if (plugin == null) - throw new ArgumentNullException("plugin"); - - if (plugin.PluginDescriptor == null) - throw new ArgumentNullException("PluginDescriptor cannot be loaded"); - - return plugin.PluginDescriptor.GetLocalizedValue(localizationService, propertyName, languageId, returnDefaultValue); - } - - /// - /// Get localized property value of a plugin - /// - /// Plugin descriptor - /// Localization service - /// Name of the property - /// Language identifier - /// A value indicating whether to return default value (if localized is not found) - /// Localized value - public static string GetLocalizedValue(this PluginDescriptor descriptor, ILocalizationService localizationService, string propertyName, int languageId = 0, bool returnDefaultValue = true) - { - if (localizationService == null) - throw new ArgumentNullException("localizationService"); - - if (descriptor == null) - throw new ArgumentNullException("descriptor"); - - if (propertyName == null) - throw new ArgumentNullException("name"); - - string systemName = descriptor.SystemName; - string resourceName = string.Format("Plugins.{0}.{1}", propertyName, systemName); - string result = localizationService.GetResource(resourceName, languageId, false, "", true); - - if (String.IsNullOrEmpty(result) && returnDefaultValue) - { - var fastProp = FastProperty.GetProperty(descriptor.GetType(), propertyName); - if (fastProp != null) - { - result = fastProp.GetValue(descriptor) as string; - } - } - - return result; - } - - /// - /// Save localized plugin descriptor value - /// - /// Plugin - /// Plugin - /// Localization service - /// Language identifier - /// Name of the property - /// Localized value - public static void SaveLocalizedValue(this T plugin, ILocalizationService localizationService, int languageId, - string propertyName, string value) where T : IPlugin - { - if (plugin == null) - throw new ArgumentNullException("plugin"); - - if (plugin.PluginDescriptor == null) - throw new ArgumentNullException("PluginDescriptor cannot be loaded"); - - plugin.PluginDescriptor.SaveLocalizedValue(localizationService, languageId, propertyName, value); - } - - /// - /// Save localized plugin descriptor value - /// - /// Plugin - /// Localization service - /// Language identifier - /// Name of the property - /// Localized value - public static void SaveLocalizedValue(this PluginDescriptor descriptor, ILocalizationService localizationService, int languageId, - string propertyName, string value) - { - if (localizationService == null) - throw new ArgumentNullException("localizationService"); - - if (languageId == 0) - throw new ArgumentOutOfRangeException("languageId", "Language ID should not be 0"); - - if (descriptor == null) - throw new ArgumentNullException("descriptor"); - - if (propertyName == null) - throw new ArgumentNullException("name"); - - string systemName = descriptor.SystemName; - string resourceName = string.Format("Plugins.{0}.{1}", propertyName, systemName); - var resource = localizationService.GetLocaleStringResourceByName(resourceName, languageId, false); - - if (resource != null) - { - if (string.IsNullOrWhiteSpace(value)) - { - //delete - localizationService.DeleteLocaleStringResource(resource); - } - else - { - //update - resource.ResourceValue = value; - localizationService.UpdateLocaleStringResource(resource); - } - } - else - { - if (!string.IsNullOrWhiteSpace(value)) - { - //insert - resource = new LocaleStringResource() - { - LanguageId = languageId, - ResourceName = resourceName, - ResourceValue = value, - }; - localizationService.InsertLocaleStringResource(resource); - } - } - } - - /// - /// Import language resources from XML file - /// - /// Language - /// XML - public static void ImportResourcesFromXml(this ILocalizationService service, - Language language, - string xml, - string rootKey = null, - bool sourceIsPlugin = false, - ImportModeFlags mode = ImportModeFlags.Insert | ImportModeFlags.Update, - bool updateTouchedResources = false) - { - if (language == null) - throw new ArgumentNullException("language"); - - if (String.IsNullOrEmpty(xml)) - return; - - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(xml); - - service.ImportResourcesFromXml(language, xmlDoc, rootKey, sourceIsPlugin, mode, updateTouchedResources); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index c6ac869c27..d85eafb668 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.IO; @@ -21,17 +20,13 @@ namespace SmartStore.Services.Localization { - /// - /// Provides information about localization - /// public partial class LocalizationService : ILocalizationService { - #region Constants - private const string LOCALESTRINGRESOURCES_ALL_KEY = "SmartStore.lsr.all-{0}"; - private const string LOCALESTRINGRESOURCES_PATTERN_KEY = "SmartStore.lsr."; - #endregion - - #region Fields + /// + /// 0 = segment (first 3 chars of key), 1 = language id + /// + const string LOCALESTRINGRESOURCES_SEGMENT_KEY = "localization:{0}-lang-{1}"; + const string LOCALESTRINGRESOURCES_SEGMENT_PATTERN = "localization:{0}*"; private readonly IRepository _lsrRepository; private readonly IWorkContext _workContext; @@ -39,102 +34,60 @@ public partial class LocalizationService : ILocalizationService private readonly ILanguageService _languageService; private readonly ICacheManager _cacheManager; private readonly IDbContext _dbContext; - private readonly LocalizationSettings _localizationSettings; private readonly IEventPublisher _eventPublisher; private int _notFoundLogCount = 0; private int? _defaultLanguageId; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Logger - /// Work context - /// Locale string resource repository - /// Language service - /// Data provider - /// Database Context - /// Common settings - /// Localization settings - /// Event published - public LocalizationService(ICacheManager cacheManager, - ILogger logger, IWorkContext workContext, + public LocalizationService( + ICacheManager cacheManager, + ILogger logger, + IWorkContext workContext, IRepository lsrRepository, ILanguageService languageService, - LocalizationSettings localizationSettings, IEventPublisher eventPublisher) + IEventPublisher eventPublisher) { - this._cacheManager = cacheManager; - this._logger = logger; - this._workContext = workContext; - this._lsrRepository = lsrRepository; - this._languageService = languageService; - this._dbContext = lsrRepository.Context; - this._localizationSettings = localizationSettings; - this._eventPublisher = eventPublisher; + _cacheManager = cacheManager; + _logger = logger; + _workContext = workContext; + _lsrRepository = lsrRepository; + _languageService = languageService; + _dbContext = lsrRepository.Context; + _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - public virtual void ClearCache() - { - _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); - } - - /// - /// Deletes a locale string resource - /// - /// Locale string resource - public virtual void DeleteLocaleStringResource(LocaleStringResource localeStringResource) + public virtual void DeleteLocaleStringResource(LocaleStringResource resource) { - if (localeStringResource == null) - throw new ArgumentNullException("localeStringResource"); + Guard.NotNull(resource, nameof(resource)); // cache - this.GetResourceValues(localeStringResource.LanguageId).Remove(localeStringResource.ResourceName); - + ClearCacheSegment(resource.ResourceName, resource.LanguageId); + // db - _lsrRepository.Delete(localeStringResource); - - //event notification - _eventPublisher.EntityDeleted(localeStringResource); + _lsrRepository.Delete(resource); } - /// - /// Deletes all string resources with its key beginning with rootKey. - /// - /// e.g. Plugins.Import.Biz - /// Number of deleted string resources public virtual int DeleteLocaleStringResources(string key, bool keyIsRootKey = true) { int result = 0; + if (key.HasValue()) { try { - string sqlDelete = "Delete From LocaleStringResource Where ResourceName Like '{0}%'".FormatWith(key.EndsWith(".") || !keyIsRootKey ? key : key + "."); + var sqlDelete = "Delete From LocaleStringResource Where ResourceName Like '{0}%'".FormatWith(key.EndsWith(".") || !keyIsRootKey ? key : key + "."); result = _dbContext.ExecuteSqlCommand(sqlDelete); - _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); + ClearCacheSegment(key); } catch (Exception exc) { exc.Dump(); } } + return result; } - /// - /// Gets a locale string resource - /// - /// Locale string resource identifier - /// Locale string resource public virtual LocaleStringResource GetLocaleStringResourceById(int localeStringResourceId) { if (localeStringResourceId == 0) @@ -144,11 +97,6 @@ public virtual LocaleStringResource GetLocaleStringResourceById(int localeString return localeStringResource; } - /// - /// Gets a locale string resource - /// - /// A string representing a resource name - /// Locale string resource public virtual LocaleStringResource GetLocaleStringResourceByName(string resourceName) { if (_workContext.WorkingLanguage != null) @@ -157,13 +105,6 @@ public virtual LocaleStringResource GetLocaleStringResourceByName(string resourc return null; } - /// - /// Gets a locale string resource - /// - /// A string representing a resource name - /// Language identifier - /// A value indicating whether to log error if locale string resource is not found - /// Locale string resource public virtual LocaleStringResource GetLocaleStringResourceByName(string resourceName, int languageId, bool logIfNotFound = true) { var query = from lsr in _lsrRepository.Table @@ -173,182 +114,136 @@ orderby lsr.ResourceName var localeStringResource = query.FirstOrDefault(); if (localeStringResource == null && logIfNotFound) - _logger.Warning(string.Format("Resource string ({0}) not found. Language ID = {1}", resourceName, languageId)); + _logger.Warn(string.Format("Resource string ({0}) not found. Language ID = {1}", resourceName, languageId)); return localeStringResource; } - /// - /// Gets all locale string resources by language identifier - /// - /// Language identifier - /// Locale string resources - public virtual IList GetAllResources(int languageId) + public virtual IQueryable All(int languageId) { - var query = from l in _lsrRepository.Table - orderby l.ResourceName - where l.LanguageId == languageId - select l; - var locales = query.ToList(); - return locales; + var query = from lsr in _lsrRepository.Table + orderby lsr.ResourceName + where lsr.LanguageId == languageId + select lsr; + + return query; } - /// - /// Inserts a locale string resource - /// - /// Locale string resource - public virtual void InsertLocaleStringResource(LocaleStringResource localeStringResource) - { - if (localeStringResource == null) - throw new ArgumentNullException("localeStringResource"); - - _lsrRepository.Insert(localeStringResource); + public virtual IList GetResourcesByPattern(string pattern, int languageId) + { + Guard.NotEmpty(pattern, nameof(pattern)); - //// cache - var holder = this.GetResourceValues(localeStringResource.LanguageId) as ConcurrentDictionary>; - holder.TryAdd( - localeStringResource.ResourceName, - new Tuple(localeStringResource.Id, localeStringResource.ResourceValue)); + var query = from l in _lsrRepository.Table + where l.ResourceName.StartsWith(pattern) && l.LanguageId == languageId + select l; - //event notification - _eventPublisher.EntityInserted(localeStringResource); - } + var resources = query.ToList(); + return resources; + } - /// - /// Updates the locale string resource - /// - /// Locale string resource - public virtual void UpdateLocaleStringResource(LocaleStringResource localeStringResource) + public virtual void InsertLocaleStringResource(LocaleStringResource resource) { - if (localeStringResource == null) - throw new ArgumentNullException("localeStringResource"); + Guard.NotNull(resource, nameof(resource)); - var modProps = _lsrRepository.GetModifiedProperties(localeStringResource); + _lsrRepository.Insert(resource); - _lsrRepository.Update(localeStringResource); + // cache + ClearCacheSegment(resource.ResourceName, resource.LanguageId); + } + + public virtual void UpdateLocaleStringResource(LocaleStringResource resource) + { + Guard.NotNull(resource, nameof(resource)); - // cache (TODO) - var holder = this.GetResourceValues(localeStringResource.LanguageId); + // cache object origKey = null; - if (modProps.TryGetValue("ResourceName", out origKey)) - { - holder.Remove((string)origKey); - } - else - { - holder.Remove(localeStringResource.ResourceName); - } - - var holder2 = holder as ConcurrentDictionary>; - holder2.TryAdd( - localeStringResource.ResourceName, - new Tuple(localeStringResource.Id, localeStringResource.ResourceValue)); + if (_dbContext.TryGetModifiedProperty(resource, "ResourceName", out origKey)) + { + ClearCacheSegment((string)origKey, resource.LanguageId); + } + ClearCacheSegment(resource.ResourceName, resource.LanguageId); - // event notification - _eventPublisher.EntityUpdated(localeStringResource); + _lsrRepository.Update(resource); } - public virtual IDictionary> GetResourceValues(int languageId, bool forceAll = false) - { - Action>> loadAllAction = (d) => - { - var query = from l in _lsrRepository.TableUntracked - orderby l.ResourceName - where l.LanguageId == languageId - select l; - var resources = query.ToList(); + protected virtual IDictionary GetCacheSegment(string forKey, int languageId) + { + Guard.NotEmpty(forKey, nameof(forKey)); - foreach (var res in resources) - { - var resourceName = res.ResourceName.ToLowerInvariant(); - d.AddOrUpdate( - resourceName, - (k) => new Tuple(res.Id, res.ResourceValue), - (k, v) => { d[k] = v; return v; }); - } + var segmentKey = GetSegmentKeyPart(forKey); + var cacheKey = BuildCacheSegmentKey(segmentKey, languageId); - // perf: add a dummy item indicating that data is fully loaded - d.TryAdd("!!___EOF___!!", new Tuple(0, string.Empty)); - }; + return _cacheManager.Get(cacheKey, () => + { + var resources = _lsrRepository.TableUntracked + .Where(x => x.ResourceName.StartsWith(segmentKey) && x.LanguageId == languageId) + //.OrderBy(x => x.ResourceName) + .ToList(); - string cacheKey = string.Format(LOCALESTRINGRESOURCES_ALL_KEY, languageId); - var dict = _cacheManager.Get(cacheKey, () => { - // TODO: make result cacheable in distributed cache (IDictionary) - var result = new ConcurrentDictionary>(8, 2000, StringComparer.CurrentCultureIgnoreCase); - if (forceAll || _localizationSettings.LoadAllLocaleRecordsOnStartup) - { - loadAllAction(result); - } - return result; - }); + var dict = new Dictionary(resources.Count); - if (forceAll && !_localizationSettings.LoadAllLocaleRecordsOnStartup && !dict.ContainsKey("!!___EOF___!!")) - { - // In this case the resources MIGHT not be loaded completely - // from the DB, but cached already. So load all and enforce merge. - loadAllAction(dict); - } + foreach (var res in resources) + { + dict[res.ResourceName.ToLowerInvariant()] = res.ResourceValue; + } - return dict; - } + return dict; + }); + } + + /// + /// Clears the cached resource segment from the cache + /// + /// 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 ClearCacheSegment(string forKey, int? languageId = null) + { + var segmentKey = GetSegmentKeyPart(forKey); + if (languageId.HasValue && languageId.Value > 0) + { + _cacheManager.Remove(BuildCacheSegmentKey(segmentKey, languageId.Value)); + } + else + { + _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_SEGMENT_PATTERN.FormatInvariant(segmentKey)); + } + } - /// - /// Gets a resource string based on the specified ResourceKey property. - /// - /// A string representing a ResourceKey. - /// Language identifier - /// A value indicating whether to log error if locale string resource is not found - /// Default value - /// A value indicating whether an empty string will be returned if a resource is not found and default value is set to empty string - /// A string representing the requested resource string. - public virtual string GetResource(string resourceKey, int languageId = 0, bool logIfNotFound = true, string defaultValue = "", bool returnEmptyIfNotFound = false) + public virtual string GetResource( + string resourceKey, + int languageId = 0, + bool logIfNotFound = true, + string defaultValue = "", + bool returnEmptyIfNotFound = false) { if (languageId <= 0) { if (_workContext.WorkingLanguage == null) - return defaultValue; - + { + return defaultValue; + } + languageId = _workContext.WorkingLanguage.Id; } - string result = string.Empty; - if (resourceKey == null) - resourceKey = string.Empty; - resourceKey = resourceKey.Trim().ToLowerInvariant(); - - var holder = this.GetResourceValues(languageId) as ConcurrentDictionary>; + string result = string.Empty; - Tuple lsr = null; - try - { - lsr = holder.GetOrAdd(resourceKey, (x) => - { - var query = from l in _lsrRepository.Table - where l.ResourceName == resourceKey && l.LanguageId == languageId - select l; - var res = query.FirstOrDefault(); - if (res != null) - return new Tuple(res.Id, res.ResourceValue); - return null; - }); - } - catch { } + resourceKey = resourceKey.EmptyNull().Trim().ToLowerInvariant(); - if (lsr != null) - result = lsr.Item2; + var cachedSegment = GetCacheSegment(resourceKey, languageId); - if (String.IsNullOrEmpty(result)) + if (!cachedSegment.TryGetValue(resourceKey, out result)) { if (logIfNotFound) { if (_notFoundLogCount < 50) { - _logger.Warning(string.Format("Resource string ({0}) does not exist. Language ID = {1}", resourceKey, languageId)); + _logger.Warn(string.Format("Resource string ({0}) does not exist. Language ID = {1}", resourceKey, languageId)); } else if (_notFoundLogCount == 50) { - _logger.Warning("Too many language resources do not exist (> 50). Stopped logging missing resources to prevent performance drop."); + _logger.Warn("Too many language resources do not exist (> 50). Stopped logging missing resources to prevent performance drop."); } _notFoundLogCount++; @@ -365,6 +260,7 @@ public virtual string GetResource(string resourceKey, int languageId = 0, bool l { _defaultLanguageId = _languageService.GetDefaultLanguageId(); } + var defaultLangId = _defaultLanguageId.Value; if (defaultLangId > 0 && defaultLangId != languageId) { @@ -385,15 +281,10 @@ public virtual string GetResource(string resourceKey, int languageId = 0, bool l return result; } - /// - /// Export language resources to xml - /// - /// Language - /// Result in XML format public virtual string ExportResourcesToXml(Language language) { - if (language == null) - throw new ArgumentNullException("language"); + Guard.NotNull(language, nameof(language)); + var sb = new StringBuilder(); var stringWriter = new StringWriter(sb); var xmlWriter = new XmlTextWriter(stringWriter); @@ -401,18 +292,21 @@ public virtual string ExportResourcesToXml(Language language) xmlWriter.WriteStartElement("Language"); xmlWriter.WriteAttributeString("Name", language.Name); - var resources = GetAllResources(language.Id); - foreach (var resource in resources) - { - if (resource.IsFromPlugin.GetValueOrDefault() == false) - { - xmlWriter.WriteStartElement("LocaleResource"); - xmlWriter.WriteAttributeString("Name", resource.ResourceName); - xmlWriter.WriteElementString("Value", null, resource.ResourceValue); - xmlWriter.WriteEndElement(); - } - } - + using (var scope = new DbContextScope(forceNoTracking: true)) + { + var resources = All(language.Id).ToList(); + foreach (var resource in resources) + { + if (resource.IsFromPlugin.GetValueOrDefault() == false) + { + xmlWriter.WriteStartElement("LocaleResource"); + xmlWriter.WriteAttributeString("Name", resource.ResourceName); + xmlWriter.WriteElementString("Value", null, resource.ResourceValue); + xmlWriter.WriteEndElement(); + } + } + } + xmlWriter.WriteEndElement(); xmlWriter.WriteEndDocument(); xmlWriter.Close(); @@ -425,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; @@ -484,11 +378,6 @@ public virtual void ImportPluginResourcesFromXml( } } - /// - /// Resolves a resource file for the specified language and processes the import - /// - /// Language - /// The culture code of the processed resource file private string ImportPluginResourcesForLanguage( Language language, string fileCode, @@ -536,7 +425,7 @@ private string ImportPluginResourcesForLanguage( foreach (XmlNode node in nodes) { var valueNode = node.SelectSingleNode("Value"); - var res = new LocaleStringResource() + var res = new LocaleStringResource { ResourceName = node.Attributes["Name"].InnerText.Trim(), ResourceValue = (valueNode == null ? "" : valueNode.InnerText), @@ -670,6 +559,11 @@ public virtual int ImportResourcesFromXml( if (toAdd.Any() || toUpdate.Any()) { + var segmentKeys = new HashSet(); + + toAdd.Each(x => segmentKeys.Add(GetSegmentKeyPart(x.ResourceName))); + toUpdate.Each(x => segmentKeys.Add(GetSegmentKeyPart(x.ResourceName))); + _lsrRepository.InsertRange(toAdd); toAdd.Clear(); @@ -679,7 +573,10 @@ public virtual int ImportResourcesFromXml( int num = _lsrRepository.Context.SaveChanges(); // clear cache - _cacheManager.RemoveByPattern(LOCALESTRINGRESOURCES_PATTERN_KEY); + foreach (var segmentKey in segmentKeys) + { + ClearCacheSegment(segmentKey, language.Id); + } return num; } @@ -690,7 +587,7 @@ public virtual int ImportResourcesFromXml( public virtual XmlDocument FlattenResourceFile(XmlDocument source) { - Guard.ArgumentNotNull(() => source); + Guard.NotNull(source, nameof(source)); if (source.SelectNodes("//Children").Count == 0) { @@ -786,12 +683,18 @@ private void RecursivelySortChildrenResource(LocaleStringResourceParent resource { RecursivelySortChildrenResource(child); } - } - #endregion + private string BuildCacheSegmentKey(string segment, int languageId) + { + return String.Format(LOCALESTRINGRESOURCES_SEGMENT_KEY, segment, languageId); + } + + private string GetSegmentKeyPart(string forKey) + { + return forKey.Substring(0, Math.Min(forKey.Length, 3)).ToLowerInvariant(); + } - #region Classes private class LocaleStringResourceParent : LocaleStringResource { @@ -875,7 +778,5 @@ public int Compare(object o1, object o2) return _comparison((T)o1, (T)o2); } } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs index f765e0d580..8d3c9bd41c 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs @@ -3,101 +3,257 @@ 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; -using System.Collections.Concurrent; namespace SmartStore.Services.Localization { - /// - /// Provides information about localizable entities - /// - public partial class LocalizedEntityService : ILocalizedEntityService + /// + /// Provides information about localizable entities + /// + public partial class LocalizedEntityService : ScopedServiceBase, ILocalizedEntityService { - private const string LOCALIZEDPROPERTY_ALL_KEY = "SmartStore.localizedproperty.all"; - - private readonly IRepository _localizedPropertyRepository; + /// + /// 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_ALLSEGMENTS_PATTERN = "localizedproperty:*"; + + private readonly IRepository _localizedPropertyRepository; private readonly ICacheManager _cacheManager; - private readonly LocalizationSettings _localizationSettings; + private readonly PerformanceSettings _performanceSettings; + + private readonly IDictionary _prefetchedCollections; + private static int _lastCacheSegmentSize = -1; public LocalizedEntityService( ICacheManager cacheManager, - IRepository localizedPropertyRepository, - LocalizationSettings localizationSettings) + IRepository localizedPropertyRepository, + PerformanceSettings performanceSettings) { - this._cacheManager = cacheManager; - this._localizedPropertyRepository = localizedPropertyRepository; - this._localizationSettings = localizationSettings; - } + _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 virtual ConcurrentDictionary GetAllProperties() + protected override void OnClearCache() { - var result = _cacheManager.Get(LOCALIZEDPROPERTY_ALL_KEY, () => + _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_ALLSEGMENTS_PATTERN); + } + + protected virtual IDictionary GetCacheSegment(string localeKeyGroup, string localeKey, int entityId, int languageId) + { + Guard.NotEmpty(localeKeyGroup, nameof(localeKeyGroup)); + Guard.NotEmpty(localeKey, nameof(localeKey)); + + var segmentKey = GetSegmentKeyPart(localeKeyGroup, localeKey, entityId, out var minEntityId, out var maxEntityId); + var cacheKey = BuildCacheSegmentKey(segmentKey, languageId); + + // TODO: (MC) skip caching product.fulldescription (?), OR + // ...additionally segment by entity id ranges. + + return _cacheManager.Get(cacheKey, () => { - if (_localizationSettings.LoadAllLocalizedPropertiesOnStartup) + var properties = _localizedPropertyRepository.TableUntracked + .Where(x => x.EntityId >= minEntityId && x.EntityId <= maxEntityId && x.LocaleKey == localeKey && x.LocaleKeyGroup == localeKeyGroup && x.LanguageId == languageId) + .ToList(); + + var dict = new Dictionary(properties.Count); + + foreach (var prop in properties) { - var props = _localizedPropertyRepository.TableUntracked.ToDictionarySafe( - x => GenerateKey(x.LanguageId, x.LocaleKeyGroup, x.LocaleKey, x.EntityId), - x => x.LocaleValue.EmptyNull()); - return new ConcurrentDictionary(props); + dict[prop.EntityId] = prop.LocaleValue.EmptyNull(); + } + + return dict; + }); + } + + /// + /// Clears the cached segment from the cache + /// + protected virtual void ClearCacheSegment(string localeKeyGroup, string localeKey, int entityId, int? languageId = null) + { + try + { + if (IsInScope) + return; + + var segmentKey = GetSegmentKeyPart(localeKeyGroup, localeKey, entityId); + + if (languageId.HasValue && languageId.Value > 0) + { + _cacheManager.Remove(BuildCacheSegmentKey(segmentKey, languageId.Value)); } else { - return new ConcurrentDictionary(); + _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_SEGMENT_PATTERN.FormatInvariant(segmentKey)); } - }); - - return result; + } + catch { } } public virtual string GetLocalizedValue(int languageId, int entityId, string localeKeyGroup, string localeKey) { - var props = GetAllProperties(); - string key = GenerateKey(languageId, localeKeyGroup, localeKey, entityId); - string val = null; - - if (_localizationSettings.LoadAllLocalizedPropertiesOnStartup) + if (_prefetchedCollections.TryGetValue(localeKeyGroup, out var collection)) { - if (!props.TryGetValue(key, out val)) + var cachedItem = collection.Find(languageId, entityId, localeKey); + if (cachedItem != null) { - return string.Empty; + return cachedItem.LocaleValue; } } - else + + if (IsInScope) { - val = props.GetOrAdd(key, k => { - var query = from lp in _localizedPropertyRepository.TableUntracked - where - lp.EntityId == entityId && - lp.LocaleKey == localeKey && - lp.LocaleKeyGroup == localeKeyGroup && - lp.LanguageId == languageId - select lp.LocaleValue; - - return query.FirstOrDefault().EmptyNull(); - }); + return GetLocalizedValueUncached(languageId, entityId, localeKeyGroup, localeKey); + } + + if (languageId <= 0) + return string.Empty; + + var props = GetCacheSegment(localeKeyGroup, localeKey, entityId, languageId); + + if (!props.TryGetValue(entityId, out var val)) + { + return string.Empty; } return val; } - public virtual IList GetLocalizedProperties(int entityId, string localeKeyGroup) + protected string GetLocalizedValueUncached(int languageId, int entityId, string localeKeyGroup, string localeKey) + { + if (languageId <= 0) + return string.Empty; + + var query = from lp in _localizedPropertyRepository.TableUntracked + where + lp.EntityId == entityId && + lp.LocaleKey == localeKey && + lp.LocaleKeyGroup == localeKeyGroup && + lp.LanguageId == languageId + select lp.LocaleValue; + + return query.FirstOrDefault().EmptyNull(); + } + + public virtual IList GetLocalizedProperties(int entityId, string localeKeyGroup) { 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 @@ -111,61 +267,40 @@ protected virtual LocalizedProperty GetLocalizedProperty(int languageId, int ent return query.FirstOrDefault(); } - public virtual void InsertLocalizedProperty(LocalizedProperty localizedProperty) + public virtual void InsertLocalizedProperty(LocalizedProperty property) { - Guard.ArgumentNotNull(() => localizedProperty); + Guard.NotNull(property, nameof(property)); - try - { - // db - _localizedPropertyRepository.Insert(localizedProperty); - - // cache - var key = GenerateKey(localizedProperty); - var val = localizedProperty.LocaleValue.EmptyNull(); - GetAllProperties().AddOrUpdate( - key, - val, - (k, v) => val); - } - catch { } + // db + _localizedPropertyRepository.Insert(property); + HasChanges = true; + + // cache + ClearCacheSegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); } - public virtual void UpdateLocalizedProperty(LocalizedProperty localizedProperty) + public virtual void UpdateLocalizedProperty(LocalizedProperty property) { - Guard.ArgumentNotNull(() => localizedProperty); + Guard.NotNull(property, nameof(property)); - try - { - // db - _localizedPropertyRepository.Update(localizedProperty); - - // cache - var key = GenerateKey(localizedProperty); - var val = localizedProperty.LocaleValue.EmptyNull(); - GetAllProperties().AddOrUpdate( - key, - val, - (k, v) => val); - } - catch { } + // db + _localizedPropertyRepository.Update(property); + HasChanges = true; + + // cache + ClearCacheSegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); } - public virtual void DeleteLocalizedProperty(LocalizedProperty localizedProperty) + public virtual void DeleteLocalizedProperty(LocalizedProperty property) { - Guard.ArgumentNotNull(() => localizedProperty); + Guard.NotNull(property, nameof(property)); - try - { - // cache - var key = GenerateKey(localizedProperty); - string val = null; - GetAllProperties().TryRemove(key, out val); + // cache + ClearCacheSegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); - // db - _localizedPropertyRepository.Delete(localizedProperty); - } - catch { } + // db + _localizedPropertyRepository.Delete(property); + HasChanges = true; } public virtual LocalizedProperty GetLocalizedPropertyById(int localizedPropertyId) @@ -192,35 +327,29 @@ public virtual void SaveLocalizedValue( TPropType localeValue, int languageId) where T : BaseEntity, ILocalizedEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotZero(languageId, "languageId"); + Guard.NotNull(entity, nameof(entity)); + Guard.NotZero(languageId, nameof(languageId)); var member = keySelector.Body as MemberExpression; if (member == null) { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - keySelector)); + throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); } var propInfo = member.Member as PropertyInfo; if (propInfo == null) { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); + throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); } - string localeKeyGroup = typeof(T).Name; - string localeKey = propInfo.Name; - - var prop = GetLocalizedProperty(languageId, entity.Id, localeKeyGroup, localeKey); - - string localeValueStr = localeValue.Convert(); + var keyGroup = entity.GetEntityName(); + var key = propInfo.Name; + var valueStr = localeValue.Convert(); + var prop = GetLocalizedProperty(languageId, entity.Id, keyGroup, key); if (prop != null) { - if (localeValueStr.IsEmpty()) + if (valueStr.IsEmpty()) { // delete DeleteLocalizedProperty(prop); @@ -228,39 +357,45 @@ public virtual void SaveLocalizedValue( else { // update - if (prop.LocaleValue != localeValueStr) + if (prop.LocaleValue != valueStr) { - prop.LocaleValue = localeValueStr; + prop.LocaleValue = valueStr; UpdateLocalizedProperty(prop); } } } else { - if (localeValueStr.HasValue()) + if (valueStr.HasValue()) { // insert prop = new LocalizedProperty { EntityId = entity.Id, LanguageId = languageId, - LocaleKey = localeKey, - LocaleKeyGroup = localeKeyGroup, - LocaleValue = localeValueStr + LocaleKey = key, + LocaleKeyGroup = keyGroup, + LocaleValue = valueStr }; InsertLocalizedProperty(prop); } } } - private string GenerateKey(LocalizedProperty prop) + private string BuildCacheSegmentKey(string segment, int languageId) + { + return String.Format(LOCALIZEDPROPERTY_SEGMENT_KEY, segment, languageId); + } + + private string GetSegmentKeyPart(string localeKeyGroup, string localeKey, int entityId) { - return GenerateKey(prop.LanguageId, prop.LocaleKeyGroup, prop.LocaleKey, prop.EntityId); + return GetSegmentKeyPart(localeKeyGroup, localeKey, entityId, out var minId, out var maxId); } - private string GenerateKey(int languageId, string localeKeyGroup, string localeKey, int entityId) + private string GetSegmentKeyPart(string localeKeyGroup, string localeKey, int entityId, out int minId, out int maxId) { - return "{0}.{1}.{2}.{3}".FormatInvariant(languageId, localeKeyGroup, localeKey, entityId); + maxId = entityId.GetRange(_performanceSettings.CacheSegmentSize, out minId); + return (localeKeyGroup + "." + localeKey + "." + maxId.ToString()).ToLowerInvariant(); } - } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs similarity index 86% rename from src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs rename to src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs index 8cf645774a..6f908c3ccc 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Threading.Tasks; +using System.Web; using SmartStore.Core.Localization; -namespace SmartStore.Web.Framework.Localization +namespace SmartStore.Services.Localization { - public class LocalizedUrlHelper + public class LocalizedUrlHelper { - public LocalizedUrlHelper(HttpRequestBase httpRequest, bool rawUrl = false) : this(httpRequest.ApplicationPath, rawUrl ? httpRequest.RawUrl : httpRequest.AppRelativeCurrentExecutionFilePath, rawUrl) { - Guard.ArgumentNotNull(() => httpRequest); + Guard.NotNull(httpRequest, nameof(httpRequest)); } public LocalizedUrlHelper(string applicationPath, string relativePath, bool rawUrl = false) { - Guard.ArgumentNotNull(() => applicationPath); - Guard.ArgumentNotNull(() => relativePath); + Guard.NotNull(applicationPath, nameof(applicationPath)); + Guard.NotNull(relativePath, nameof(relativePath)); this.ApplicationPath = applicationPath; @@ -72,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()) @@ -87,7 +81,7 @@ public string StripSeoCode() public string PrependSeoCode(string seoCode, bool safe = false) { - Guard.ArgumentNotEmpty(() => seoCode); + Guard.NotEmpty(seoCode, nameof(seoCode)); if (safe) { @@ -105,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; } @@ -113,10 +107,12 @@ public string GetAbsolutePath() { string path = this.ApplicationPath.EnsureEndsWith("/"); path = path + this.RelativePath; + if (path.Length > 1 && path[0] != '/') { path = "/" + path; } + return path; } @@ -133,8 +129,8 @@ private string RemoveApplicationPathFromRawUrl(string rawUrl) { result = "/" + result; } + return result; } - } } 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 new file mode 100644 index 0000000000..d725fadef6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Logging/DbLogService.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Logging; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.Logging +{ + public partial class DbLogService : ILogService + { + private const int _deleteNumberOfEntries = 1000; + + private readonly IRepository _logRepository; + private readonly IDbContext _dbContext; + private readonly ILoggerFactory _loggerFactory; + + public DbLogService(IRepository logRepository, ILoggerFactory loggerFactory) + { + _logRepository = logRepository; + _dbContext = logRepository.Context; + _loggerFactory = loggerFactory; + } + + public virtual void DeleteLog(Log log) + { + Guard.NotNull(log, nameof(log)); + + _logRepository.Delete(log); + } + + public virtual void ClearLog() + { + try + { + _dbContext.ExecuteSqlCommand("TRUNCATE TABLE [Log]"); + } + catch + { + try + { + for (int i = 0; i < 100000; ++i) + { + if (_dbContext.ExecuteSqlCommand("Delete Top ({0}) From [Log]", false, null, _deleteNumberOfEntries) < _deleteNumberOfEntries) + break; + } + } + catch { } + + try + { + _dbContext.ExecuteSqlCommand("DBCC CHECKIDENT('Log', RESEED, 0)"); + } + catch + { + try + { + _dbContext.ExecuteSqlCommand("Alter Table [Log] Alter Column [Id] Identity(1,1)"); + } + catch { } + } + } + + _dbContext.ShrinkDatabase(); + } + + public virtual void ClearLog(DateTime toUtc, LogLevel logLevel) + { + try + { + string sqlDelete = "Delete Top ({0}) From [Log] Where LogLevelId < {1} And CreatedOnUtc <= {2}"; + + for (int i = 0; i < 100000; ++i) + { + if (_dbContext.ExecuteSqlCommand(sqlDelete, false, null, _deleteNumberOfEntries, (int)logLevel, toUtc) < _deleteNumberOfEntries) + break; + } + + _dbContext.ShrinkDatabase(); + } + catch { } + } + + public virtual IPagedList GetAllLogs( + DateTime? fromUtc, + DateTime? toUtc, + string logger, + string message, + LogLevel? logLevel, + int pageIndex, + int pageSize) + { + // force flush to get most recent entries + _loggerFactory.FlushAll(); + + var query = _logRepository.Table; + + if (fromUtc.HasValue) + query = query.Where(l => fromUtc.Value <= l.CreatedOnUtc); + + if (toUtc.HasValue) + query = query.Where(l => toUtc.Value >= l.CreatedOnUtc); + + if (logger.HasValue()) + query = query.Where(l => l.Logger.Contains(logger)); + + if (logLevel.HasValue) + { + int logLevelId = (int)logLevel.Value; + query = query.Where(l => logLevelId == l.LogLevelId); + } + + if (message.HasValue()) + query = query.Where(l => l.ShortMessage.Contains(message) || l.FullMessage.Contains(message)); + + query = query.OrderByDescending(l => l.CreatedOnUtc); + + //query = _logRepository.Expand(query, x => x.Customer); + + var log = new PagedList(query, pageIndex, pageSize); + return log; + } + + public virtual Log GetLogById(int logId) + { + if (logId == 0) + return null; + + var log = _logRepository.GetById(logId); + return log; + } + + public virtual IList GetLogByIds(int[] logIds) + { + if (logIds == null || logIds.Length == 0) + return new List(); + + var query = from l in _logRepository.Table + where logIds.Contains(l.Id) + select l; + var logItems = query.ToList(); + + // sort by passed identifier sequence + return logItems.OrderBySequence(logIds).ToList(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs b/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs deleted file mode 100644 index 8387de3969..0000000000 --- a/src/Libraries/SmartStore.Services/Logging/DefaultLogger.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Logging; -using SmartStore.Core.Logging; -using SmartStore.Data; - -namespace SmartStore.Services.Logging -{ - /// - /// Default logger - /// - public partial class DefaultLogger : ILogger - { - #region Fields - - private const int _deleteNumberOfEntries = 1000; - - private readonly IRepository _logRepository; - private readonly IWebHelper _webHelper; - private readonly IDbContext _dbContext; - private readonly IWorkContext _workContext; - - private readonly IList _entries = new List(); - - #endregion - - #region Ctor - - public DefaultLogger(IRepository logRepository, IWebHelper webHelper, IDbContext dbContext, IWorkContext workContext) - { - this._logRepository = logRepository; - this._webHelper = webHelper; - this._dbContext = dbContext; - this._workContext = workContext; - } - - #endregion - - #region Methods - - public virtual bool IsEnabled(LogLevel level) - { - switch (level) - { - case LogLevel.Debug: - return false; - default: - return true; - } - } - - public virtual void DeleteLog(Log log) - { - if (log == null) - throw new ArgumentNullException("log"); - - _logRepository.Delete(log); - } - - public virtual void ClearLog() - { - try - { - _dbContext.ExecuteSqlCommand("TRUNCATE TABLE [Log]"); - } - catch - { - try - { - for (int i = 0; i < 100000; ++i) - { - if (_dbContext.ExecuteSqlCommand("Delete Top ({0}) From [Log]", false, null, _deleteNumberOfEntries) < _deleteNumberOfEntries) - break; - } - } - catch { } - - try - { - _dbContext.ExecuteSqlCommand("DBCC CHECKIDENT('Log', RESEED, 0)"); - } - catch - { - try - { - _dbContext.ExecuteSqlCommand("Alter Table [Log] Alter Column [Id] Identity(1,1)"); - } - catch{ } - } - } - - _dbContext.ShrinkDatabase(); - } - - public virtual void ClearLog(DateTime toUtc, LogLevel logLevel) - { - try - { - string sqlDelete = "Delete Top ({0}) From [Log] Where LogLevelId < {1} And CreatedOnUtc <= {2}"; - - for (int i = 0; i < 100000; ++i) - { - if (_dbContext.ExecuteSqlCommand(sqlDelete, false, null, _deleteNumberOfEntries, (int)logLevel, toUtc) < _deleteNumberOfEntries) - break; - } - - _dbContext.ShrinkDatabase(); - } - catch { } - } - - public virtual IPagedList GetAllLogs(DateTime? fromUtc, DateTime? toUtc, string message, LogLevel? logLevel, int pageIndex, int pageSize, int minFrequency) - { - var query = _logRepository.Table; - - if (fromUtc.HasValue) - query = query.Where(l => fromUtc.Value <= l.CreatedOnUtc || fromUtc.Value <= l.UpdatedOnUtc); - if (toUtc.HasValue) - query = query.Where(l => toUtc.Value >= l.CreatedOnUtc || toUtc.Value >= l.UpdatedOnUtc); - if (logLevel.HasValue) - { - int logLevelId = (int)logLevel.Value; - query = query.Where(l => logLevelId == l.LogLevelId); - } - if (!String.IsNullOrEmpty(message)) - query = query.Where(l => l.ShortMessage.Contains(message) || l.FullMessage.Contains(message)); - - query = query.OrderByDescending(l => l.UpdatedOnUtc).ThenByDescending(l => l.CreatedOnUtc); - - if (minFrequency > 0) - query = query.Where(l => l.Frequency >= minFrequency); - - //query = _logRepository.Expand(query, x => x.Customer); - - var log = new PagedList(query, pageIndex, pageSize); - return log; - } - - public virtual Log GetLogById(int logId) - { - if (logId == 0) - return null; - - var log = _logRepository.GetById(logId); - return log; - } - - public virtual IList GetLogByIds(int[] logIds) - { - if (logIds == null || logIds.Length == 0) - return new List(); - - var query = from l in _logRepository.Table - where logIds.Contains(l.Id) - select l; - var logItems = query.ToList(); - //sort by passed identifiers - var sortedLogItems = new List(); - foreach (int id in logIds) - { - var log = logItems.Find(x => x.Id == id); - if (log != null) - sortedLogItems.Add(log); - } - return sortedLogItems; - } - - public virtual void InsertLog(LogLevel logLevel, string shortMessage, string fullMessage = "", Customer customer = null) - { - var context = new LogContext - { - LogLevel = logLevel, - ShortMessage = shortMessage, - FullMessage = fullMessage, - Customer = customer - }; - - InsertLog(context); - } - - public virtual void InsertLog(LogContext context) - { - _entries.Add(context); - if (_entries.Count == 50) - { - Flush(); - } - } - - public void Flush() - { - if (_entries.Count == 0) - return; - - string ipAddress = ""; - string pageUrl = ""; - string referrerUrl = ""; - var currentCustomer = _workContext.CurrentCustomer; - - try - { - ipAddress = _webHelper.GetCurrentIpAddress(); - pageUrl = _webHelper.GetThisPageUrl(true); - referrerUrl = _webHelper.GetUrlReferrer(); - } - catch { } - - using (var scope = new DbContextScope(autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) - { - foreach (var context in _entries) - { - if (context.ShortMessage.IsEmpty() && context.FullMessage.IsEmpty()) - continue; - - Log log = null; - - try - { - string shortMessage = context.ShortMessage.NaIfEmpty(); - string fullMessage = context.FullMessage.EmptyNull(); - string contentHash = null; - - if (context.HashNotFullMessage || context.HashIpAddress) - { - contentHash = (shortMessage - + (context.HashNotFullMessage ? "" : fullMessage) - + (context.HashIpAddress ? ipAddress.EmptyNull() : "") - ).Hash(Encoding.Unicode, true); - } - else - { - contentHash = (shortMessage + fullMessage).Hash(Encoding.Unicode, true); - } - - log = _logRepository.Table.OrderByDescending(x => x.CreatedOnUtc).FirstOrDefault(x => x.ContentHash == contentHash); - - if (log == null) - { - log = new Log - { - Frequency = 1, - LogLevel = context.LogLevel, - ShortMessage = shortMessage, - FullMessage = fullMessage, - IpAddress = ipAddress, - Customer = context.Customer ?? currentCustomer, - PageUrl = pageUrl, - ReferrerUrl = referrerUrl, - CreatedOnUtc = DateTime.UtcNow, - ContentHash = contentHash - }; - - _logRepository.Insert(log); - } - else - { - if (log.Frequency < 2147483647) - log.Frequency = log.Frequency + 1; - - log.LogLevel = context.LogLevel; - log.IpAddress = ipAddress; - log.Customer = context.Customer ?? currentCustomer; - log.PageUrl = pageUrl; - log.ReferrerUrl = referrerUrl; - log.UpdatedOnUtc = DateTime.UtcNow; - - _logRepository.Update(log); - } - } - catch (Exception ex) - { - ex.Dump(); - } - } - - try - { - // FIRE! - _logRepository.Context.SaveChanges(); - } - catch { } - } - - _entries.Clear(); - } - - #endregion - - } -} diff --git a/src/Libraries/SmartStore.Services/Logging/DeleteLogsTask.cs b/src/Libraries/SmartStore.Services/Logging/DeleteLogsTask.cs index c5c9d19676..23faa952be 100644 --- a/src/Libraries/SmartStore.Services/Logging/DeleteLogsTask.cs +++ b/src/Libraries/SmartStore.Services/Logging/DeleteLogsTask.cs @@ -10,11 +10,11 @@ namespace SmartStore.Services.Logging /// public partial class DeleteLogsTask : ITask { - private readonly ILogger _logger; + private readonly ILogService _logService; - public DeleteLogsTask(ILogger logger) + public DeleteLogsTask(ILogService logService) { - this._logger = logger; + _logService = logService; } /// @@ -25,7 +25,7 @@ public void Execute(TaskExecutionContext ctx) var olderThanDays = 7; // TODO: move to settings var toUtc = DateTime.UtcNow.AddDays(-olderThanDays); - _logger.ClearLog(toUtc, LogLevel.Error); + _logService.ClearLog(toUtc, LogLevel.Error); } } } diff --git a/src/Libraries/SmartStore.Services/Logging/ILogService.cs b/src/Libraries/SmartStore.Services/Logging/ILogService.cs new file mode 100644 index 0000000000..926f87aea0 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Logging/ILogService.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SmartStore.Core; +using SmartStore.Core.Domain.Logging; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.Logging +{ + public interface ILogService + { + /// + /// Deletes a log item + /// + /// Log item + void DeleteLog(Log log); + + /// + /// Clears a log + /// + void ClearLog(); + void ClearLog(DateTime toUtc, LogLevel logLevel); + + /// + /// Gets all log items + /// + /// Log item creation from; null to load all records + /// Log item creation to; null to load all records + /// Message + /// Logger name + /// Log level; null to load all records + /// Page index + /// Page size + /// Log item collection + IPagedList GetAllLogs( + DateTime? fromUtc, + DateTime? toUtc, + string logger, + string message, + LogLevel? logLevel, + int pageIndex, + int pageSize); + + /// + /// Gets a log item + /// + /// Log item identifier + /// Log item + Log GetLogById(int logId); + + /// + /// Get log items by identifiers + /// + /// Log item identifiers + /// Log items + IList GetLogByIds(int[] logIds); + } +} diff --git a/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs b/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs index 2653b3dba4..8761739ab8 100644 --- a/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs +++ b/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs @@ -1,4 +1,5 @@ using System; +using SmartStore.Core.IO; namespace SmartStore.Services.Media { @@ -7,33 +8,83 @@ namespace SmartStore.Services.Media /// /// /// An instance of this object is always returned, even when - /// the requested image does not physically exists in the repository. + /// the requested image does not physically exists in the storage. /// public class CachedImageResult { - /// - /// true when the image exists in the cache, false otherwise. - /// - public bool Exists { get; set; } + private bool? _exists; + private string _mimeType; - /// - /// The name of the file - /// - public string FileName { get; set; } + public CachedImageResult(IFile file) + { + Guard.NotNull(file, nameof(file)); - /// - /// The file extension (without 'dot') - /// - public string Extension { get; set; } - - /// - /// The path relative to the cache root folder - /// - public string Path { get; set; } + File = file; + } + + /// + /// The abstracted file object + /// + public IFile File { get; internal set; } + + /// + /// true when the image exists in the cache, false otherwise. + /// + public bool Exists + { + get + { + return _exists ?? (_exists = File.Exists).Value; + } + // For internal use + set + { + _exists = value; + } + } /// - /// The local (physical) full path + /// The name of the file (without path) /// - public string LocalPath { get; set; } + public string FileName + { + get { return System.IO.Path.GetFileName(this.Path); } + } + + public long FileSize + { + get { return !Exists ? 0 : File.Size; } + } + + /// + /// The file extension (without 'dot') + /// + public string Extension { 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 + /// + public DateTime? LastModifiedUtc + { + get { return Exists ? File.LastUpdated : (DateTime?)null; } + } + + /// + /// Checks whether the file is remote (outside the application's physical root) + /// + public bool IsRemote { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs b/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs new file mode 100644 index 0000000000..1c5e8dc893 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs @@ -0,0 +1,318 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Drawing; +using ImageProcessor; +using ImageProcessor.Imaging; +using ImageProcessor.Imaging.Formats; +using SmartStore.Core.Logging; +using ImageProcessor.Configuration; +using SmartStore.Core.Events; +using SmartStore.Utilities; + +namespace SmartStore.Services.Media +{ + public partial class DefaultImageProcessor : IImageProcessor + { + private static long _totalProcessingTime; + + private readonly IEventPublisher _eventPublisher; + + public DefaultImageProcessor(IEventPublisher eventPublisher) + { + _eventPublisher = eventPublisher; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public bool IsSupportedImage(string fileName) + { + var ext = Path.GetExtension(fileName); + if (ext != null) + { + var extension = ext.Trim('.').ToLower(); + return ImageProcessorBootstrapper.Instance.SupportedImageFormats + .SelectMany(x => x.FileExtensions) + .Any(x => x == extension); + } + + return false; + } + + public ProcessImageResult ProcessImage(ProcessImageQuery query) + { + Guard.NotNull(query, nameof(query)); + + ValidateQuery(query); + + var watch = new Stopwatch(); + byte[] inBuffer = null; + + try + { + watch.Start(); + + using (var processor = new ImageFactory(preserveExifData: false, fixGamma: false)) + { + var source = query.Source; + + // Load source + if (source is byte[] b) + { + inBuffer = b; + } + else if (source is Stream s) + { + inBuffer = s.ToByteArray(); + } + else if (source is Image img) + { + processor.Load(img); + } + else if (source is string str) + { + 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)); + + var result = new ProcessImageResult + { + Query = query, + SourceWidth = processor.Image.Width, + SourceHeight = processor.Image.Height, + SourceMimeType = processor.CurrentImageFormat.MimeType + }; + + // Core processing + 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; + + 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; + } + } + catch (Exception ex) + { + var pex = new ProcessImageException(query, ex); + Logger.Error(pex); + throw pex; + } + finally + { + if (query.DisposeSource && query.Source is IDisposable source) + { + source.Dispose(); + } + + watch.Stop(); + _totalProcessingTime += watch.ElapsedMilliseconds; + } + } + + /// + /// Processes the loaded image. Inheritors should NOT save the image, this is done by the main method. + /// + /// Query + /// Processor instance + /// + /// 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) + : Size.Empty; + + if (!size.IsEmpty) + { + 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 + if (query.Format != null) + { + var format = query.Format as ISupportedImageFormat; + + if (format == null && query.Format is string) + { + var requestedFormat = ((string)query.Format).ToLowerInvariant(); + switch (requestedFormat) + { + case "jpg": + case "jpeg": + format = new JpegFormat(); + break; + case "png": + format = new PngFormat(); + break; + case "gif": + format = new GifFormat(); + break; + } + } + + if (format != null) + { + processor.Format(format); + } + } + + // Set Quality + if (query.Quality.HasValue) + { + processor.Quality(query.Quality.Value); + } + } + + 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) + { + throw new ArgumentException("During image processing 'ProcessImageQuery.Source' must not be null.", nameof(query)); + } + } + + public long TotalProcessingTimeMs + { + get { return _totalProcessingTime; } + } + + private ResizeMode ConvertScaleMode(string mode) + { + switch (mode.EmptyNull().ToLower()) + { + case "boxpad": + return ResizeMode.BoxPad; + case "crop": + return ResizeMode.Crop; + case "min": + return ResizeMode.Min; + case "pad": + return ResizeMode.Pad; + case "stretch": + return ResizeMode.Stretch; + default: + 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 b7f03930e8..16a0feee63 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; +using NuGet; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; @@ -9,34 +10,46 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Events; +using SmartStore.Core.Plugins; +using SmartStore.Services.Configuration; +using SmartStore.Services.Media.Storage; namespace SmartStore.Services.Media { - /// - /// Download service - /// public partial class DownloadService : IDownloadService { - #region Fields - private readonly IRepository _downloadRepository; private readonly IEventPublisher _eventPubisher; + private readonly Provider _storageProvider; - #endregion - - #region Ctor - - public DownloadService(IRepository downloadRepository, IEventPublisher eventPubisher) + public DownloadService( + IRepository downloadRepository, + IEventPublisher eventPubisher, + ISettingService settingService, + IProviderManager providerManager) { _downloadRepository = downloadRepository; _eventPubisher = eventPubisher; - } - #endregion + var systemName = settingService.GetSettingByKey("Media.Storage.Provider", DatabaseMediaStorageProvider.SystemName); + + _storageProvider = providerManager.GetProvider(systemName); + } - #region Methods + private void UpdateDownloadCore(Download download, byte[] downloadBinary, bool updateDataStorage) + { + download.UpdatedOnUtc = DateTime.UtcNow; - public virtual Download GetDownloadById(int downloadId) + _downloadRepository.Update(download); + + if (updateDataStorage) + { + // save to storage + _storageProvider.Value.Save(download.ToMedia(), downloadBinary); + } + } + + public virtual Download GetDownloadById(int downloadId) { if (downloadId == 0) return null; @@ -57,13 +70,70 @@ where downloadIds.Contains(dl.Id) var downloads = query.ToList(); // sort by passed identifier sequence - var sortQuery = from i in downloadIds - join d in downloads on i equals d.Id - select d; + return downloads.OrderBySequence(downloadIds).ToList(); + } + + public virtual IList GetDownloadsFor(TEntity entity) where TEntity : BaseEntity + { + Guard.NotNull(entity, nameof(entity)); - return sortQuery.ToList(); + 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) { if (downloadGuid == Guid.Empty) @@ -78,37 +148,41 @@ public virtual Download GetDownloadByGuid(Guid downloadGuid) public virtual void DeleteDownload(Download download) { - if (download == null) - throw new ArgumentNullException("download"); + Guard.NotNull(download, nameof(download)); - _downloadRepository.Delete(download); + // delete from storage + _storageProvider.Value.Remove(download.ToMedia()); - _eventPubisher.EntityDeleted(download); + // delete entity + _downloadRepository.Delete(download); } - public virtual void InsertDownload(Download download) + public virtual void InsertDownload(Download download, byte[] downloadBinary) { - if (download == null) - throw new ArgumentNullException("download"); + Guard.NotNull(download, nameof(download)); - download.UpdatedOnUtc = DateTime.UtcNow; _downloadRepository.Insert(download); - _eventPubisher.EntityInserted(download); + // save to storage + _storageProvider.Value.Save(download.ToMedia(), downloadBinary); } - public virtual void UpdateDownload(Download download) - { - if (download == null) - throw new ArgumentNullException("download"); + public virtual void UpdateDownload(Download download) + { + Guard.NotNull(download, nameof(download)); - download.UpdatedOnUtc = DateTime.UtcNow; - _downloadRepository.Update(download); + // we use an overload because a byte array cannot be nullable + UpdateDownloadCore(download, null, false); + } - _eventPubisher.EntityUpdated(download); + public virtual void UpdateDownload(Download download, byte[] downloadBinary) + { + Guard.NotNull(download, nameof(download)); + + UpdateDownloadCore(download, downloadBinary, true); } - public virtual bool IsDownloadAllowed(OrderItem orderItem) + public virtual bool IsDownloadAllowed(OrderItem orderItem) { if (orderItem == null) return false; @@ -183,6 +257,11 @@ public virtual bool IsLicenseDownloadAllowed(OrderItem orderItem) orderItem.LicenseDownloadId > 0; } - #endregion - } + public virtual byte[] LoadDownloadBinary(Download download) + { + Guard.NotNull(download, nameof(download)); + + return _storageProvider.Value.Load(download.ToMedia()); + } + } } diff --git a/src/Libraries/SmartStore.Services/Media/Events.cs b/src/Libraries/SmartStore.Services/Media/Events.cs new file mode 100644 index 0000000000..0ab3361917 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Events.cs @@ -0,0 +1,99 @@ +using System; +using System.Drawing; +using System.Web; +using ImageProcessor; + +namespace SmartStore.Services.Media +{ + /// + /// Published for every uploaded image which does NOT exceed maximum + /// allowed size. This gives subscribers the chance to still process the image, + /// e.g. to achive better compression before saving image data to storage. + /// This event does NOT get published when the uploaded image is about to be processed anyway. + /// + /// + /// A subscriber should NOT resize the image. But if you do - and you shouldn't :-) - , don't forget to set . + /// + public class ImageUploadValidatedEvent + { + public ImageUploadValidatedEvent(ProcessImageQuery query, Size size) + { + Query = query; + Size = size; + } + + /// + /// Contains the source (as byte[]), max size, format and default image quality instructions. + /// + public ProcessImageQuery Query { get; private set; } + + /// + /// The original size of the uploaded image. May be empty. + /// + public Size Size { get; private set; } + + /// + /// The processing result. If null, the original data + /// from Query.Source will be put to storage. + /// + public byte[] ResultBuffer { get; set; } + + /// + /// Size of the result image. + /// + public Size ResultSize { get; set; } + } + + /// + /// Published after image query has been created and initialized + /// by the media middleware controller with data from HttpContent.Request.QueryString. + /// This event implies that a thumbnail is about to be created. + /// + public class ImageQueryCreatedEvent + { + public ImageQueryCreatedEvent(ProcessImageQuery query, HttpContextBase httpContext, string mimeType, string extension) + { + Query = query; + HttpContext = httpContext; + MimeType = mimeType; + Extension = extension; + } + + public ProcessImageQuery Query { get; private set; } + public HttpContextBase HttpContext { get; private set; } + public string MimeType { get; private set; } + public string Extension { get; private set; } + } + + /// + /// Published before processing begins, but after the source has been loaded. + /// + public class ImageProcessingEvent + { + public ImageProcessingEvent(ProcessImageQuery query, ImageFactory processor) + { + Query = query; + Processor = processor; + } + + public ProcessImageQuery Query { get; private set; } + public ImageFactory Processor { get; private set; } + } + + /// + /// Published after processing finishes and the result is saved to a stream (ProcessImageResult.Result) + /// + public class ImageProcessedEvent + { + public ImageProcessedEvent(ProcessImageQuery query, ImageFactory processor, ProcessImageResult result) + { + Query = query; + Processor = processor; + Result = result; + } + + public ProcessImageQuery Query { get; private set; } + public ImageFactory Processor { get; private set; } + public ProcessImageResult Result { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs index 80faf7f50a..245bfe9bb3 100644 --- a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq.Expressions; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Media { - /// - /// Download service interface - /// public partial interface IDownloadService { /// @@ -26,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 /// @@ -43,13 +72,21 @@ public partial interface IDownloadService /// Inserts a download /// /// Download - void InsertDownload(Download download); + /// Download binary, can be null + void InsertDownload(Download download, byte[] downloadBinary); - /// - /// Updates the download - /// - /// Download - void UpdateDownload(Download download); + /// + /// Updates the download but leaves the binary data untouched + /// + /// Download + void UpdateDownload(Download download); + + /// + /// Updates the download including the associated binary data + /// + /// Download + /// New download binary data associated with the download entity + void UpdateDownload(Download download, byte[] downloadBinary); /// /// Gets a value indicating whether download is allowed @@ -65,5 +102,11 @@ public partial interface IDownloadService /// True if license download is allowed; otherwise, false. bool IsLicenseDownloadAllowed(OrderItem orderItem); - } + /// + /// Load binary data of a download + /// + /// Download + /// Binary data + byte[] LoadDownloadBinary(Download download); + } } diff --git a/src/Libraries/SmartStore.Services/Media/IImageCache.cs b/src/Libraries/SmartStore.Services/Media/IImageCache.cs index 412919321a..97b08e2cf7 100644 --- a/src/Libraries/SmartStore.Services/Media/IImageCache.cs +++ b/src/Libraries/SmartStore.Services/Media/IImageCache.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using SmartStore.Core.Domain.Media; +using SmartStore.Core.IO; namespace SmartStore.Services.Media { @@ -12,48 +14,84 @@ namespace SmartStore.Services.Media /// public interface IImageCache { - /// - /// Resolves an http url to the image - /// - /// The path of the image relative to the cache root path - /// Store location URL; null to use determine the current store location automatically - /// The image http url - string GetImageUrl(string imagePath, string storeLocation = null); - - /// - /// Adds an image to the cache. - /// - /// An instance of the object, which is returned by the GetCachedImage() method. - /// The image binary data. - void AddImageToCache(CachedImageResult cachedImage, byte[] buffer); - - /// - /// Gets an instance of the object, which contains information about a cached image. - /// - /// The picture id of the image to be resolved. - /// The seo friendly picture name of the image to be resolved. - /// The extension of the image to be resolved. - /// The image processing settings. - /// 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 GetCachedImage(int? pictureId, string seoFileName, string extension, object settings = null); - - /// - /// Deletes all cached images for the given - /// - /// The for which to delete cached images - void DeleteCachedImages(Picture picture); - - /// - /// Deletes all cached images (nukes all files in the cache folder) - /// - void DeleteCachedImages(); - - /// - /// Calculates statistics about the image cache data. - /// - /// The total count of files in the cache. - /// The total size of files in the cache (in bytes) - void CacheStatistics(out long fileCount, out long totalSize); - } + /// + /// Adds an image to the cache. + /// + /// An instance of the object, which is returned by the Get() method. + /// The image binary buffer. + /// true when the operation succeded, false otherwise + void Put(CachedImageResult cachedImage, byte[] buffer); + + /// + /// Asynchronously adds an image to the cache. + /// + /// An instance of the object, which is returned by the Get() method. + /// The image binary buffer. + /// true when the operation succeded, false otherwise + Task PutAsync(CachedImageResult cachedImage, byte[] buffer); + + /// + /// Gets an instance of the object, which contains information about a cached image. + /// + /// The picture id of the image to be resolved. + /// The seo friendly picture name of the image to be resolved. + /// The extension of the image to be resolved. + /// 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(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 + /// + /// An instance of the object, which is returned by the GetCachedImage() method. + /// File stream + Stream Open(CachedImageResult cachedImage); + + /// + /// Deletes all cached images for the given + /// + /// The for which to delete cached images + void Delete(Picture picture); + + /// + /// 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. + /// + void RefreshInfo(CachedImageResult cachedImage); + + /// + /// Calculates statistics about the image cache data. + /// + /// The total count of files in the cache. + /// The total size of files in the cache (in bytes) + void CacheStatistics(out long fileCount, out long totalSize); + + /// + /// Resolves a publicly accessible http url to the image + /// + /// The path of the image relative to the cache root path + /// The image http url + string GetPublicUrl(string imagePath); + } } diff --git a/src/Libraries/SmartStore.Services/Media/IImageProcessor.cs b/src/Libraries/SmartStore.Services/Media/IImageProcessor.cs new file mode 100644 index 0000000000..3ff8fe25f9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/IImageProcessor.cs @@ -0,0 +1,29 @@ +using System; + +namespace SmartStore.Services.Media +{ + /// + /// A service interface responsible for resizing/processing images. + /// + public interface IImageProcessor + { + /// + /// Determines whether the given file name is processable by the image resizer + /// + /// The name of the file (without path but including extension) + /// A value indicating whether processing is possible + bool IsSupportedImage(string fileName); + + /// + /// Processes an image + /// + /// Resize request + /// The resizing result encapsulated in type + ProcessImageResult ProcessImage(ProcessImageQuery query); + + /// + /// Gets the cumulative total processing time since app start in miliseconds + /// + long TotalProcessingTimeMs { get; } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/IImageResizerService.cs b/src/Libraries/SmartStore.Services/Media/IImageResizerService.cs deleted file mode 100644 index 438db1a049..0000000000 --- a/src/Libraries/SmartStore.Services/Media/IImageResizerService.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace SmartStore.Services.Media -{ - - /// - /// A service interface responsible for resizing images. - /// - public interface IImageResizerService - { - /// - /// Determines whether the given file name is processable by the image resizer - /// - /// The name of the file (without path but including extension) - /// A value indicating whether processing is possible - bool IsSupportedImage(string fileName); - - /// - /// Resizes an image - /// - /// The source stream - /// The max width of the destination image - /// The max height of the destination image - /// The output quality - /// The resize mode - /// A provider specific settings object. - /// The result image as a MemoryStream object - MemoryStream ResizeImage(Stream source, int? maxWidth = null, int? maxHeight = null, int? quality = 0, object settings = null); - } -} diff --git a/src/Libraries/SmartStore.Services/Media/IPictureService.cs b/src/Libraries/SmartStore.Services/Media/IPictureService.cs index ce9f231349..84c6e2013c 100644 --- a/src/Libraries/SmartStore.Services/Media/IPictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/IPictureService.cs @@ -1,13 +1,16 @@ using System.Collections.Generic; using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using SmartStore.Collections; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; +using SmartStore.Core.IO; namespace SmartStore.Services.Media { - /// - /// Picture service interface - /// public partial interface IPictureService { /// @@ -16,16 +19,16 @@ public partial interface IPictureService /// Picture binary /// MIME type /// Picture binary or throws an exception - byte[] ValidatePicture(byte[] pictureBinary); + byte[] ValidatePicture(byte[] pictureBinary, string mimeType); /// - /// Finds an equal picture by comparing the binary buffer + /// Validates input picture dimensions and prevents that the image size exceeds global max size /// - /// The picture to find a duplicate for - /// The sequence of pictures to seek within for duplicates - /// Id of equal picture if any - /// The picture binary for path when no picture equals in the sequence, null otherwise. - byte[] FindEqualPicture(string path, IEnumerable pictures, out int equalPictureId); + /// Picture binary + /// MIME type + /// The size of the original input OR the resized picture + /// Picture binary or throws an exception + byte[] ValidatePicture(byte[] pictureBinary, string mimeType, out Size size); /// /// Finds an equal picture by comparing the binary buffer @@ -35,90 +38,131 @@ public partial interface IPictureService /// Id of equal picture if any /// The picture binary for path when no picture equals in the sequence, null otherwise. byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId); - - /// - /// Gets the loaded picture binary depending on picture storage settings - /// - /// Picture - /// Picture binary - byte[] LoadPictureBinary(Picture picture); - /// - /// Get picture SEO friendly name - /// - /// Name - /// Result - string GetPictureSeName(string name); + /// + /// Get picture SEO friendly name + /// + /// Name + /// Picture SEO name + string GetPictureSeName(string name); - /// - /// Gets the size of a picture - /// - /// - /// - Size GetPictureSize(Picture picture); + /// + /// Updates a SEO filename of a picture + /// + /// The picture identifier + /// The SEO filename + /// Picture + Picture SetSeoFilename(int pictureId, string seoFilename); - /// - /// Gets the default picture URL - /// - /// The target picture size (longest side) - /// Default picture type - /// Store location URL; null to use determine the current store location automatically - /// Picture URL - string GetDefaultPictureUrl(int targetSize = 0, - PictureType defaultPictureType = PictureType.Entity, - string storeLocation = null); + /// + /// Opens the picture stream from the underlying storage provider for reading + /// + /// Picture + /// Picture stream + Stream OpenPictureStream(Picture picture); - /// - /// Get a picture URL - /// - /// Picture identifier - /// The target picture size (longest side) - /// A value indicating whether the default picture is shown - /// Store location URL; null to use determine the current store location automatically - /// Default picture type - /// Picture URL - string GetPictureUrl(int pictureId, - int targetSize = 0, - bool showDefaultPicture = true, - string storeLocation = null, - PictureType defaultPictureType = PictureType.Entity); + /// + /// Loads the picture binary from the underlying storage provider + /// + /// Picture + /// Picture binary + byte[] LoadPictureBinary(Picture picture); - /// - /// Get a picture URL - /// - /// Picture instance - /// The target picture size (longest side) - /// A value indicating whether the default picture is shown - /// Store location URL; null to use determine the current store location automatically - /// Default picture type - /// Picture URL - string GetPictureUrl(Picture picture, - int targetSize = 0, - bool showDefaultPicture = true, - string storeLocation = null, - PictureType defaultPictureType = PictureType.Entity); + /// + /// Asynchronously loads the picture binary from the underlying storage provider + /// + /// Picture + /// Picture binary + Task LoadPictureBinaryAsync(Picture picture); - /// - /// Get a picture local path - /// - /// Picture instance - /// The target picture size (longest side) - /// A value indicating whether the default picture should be shown - /// - string GetThumbLocalPath(Picture picture, int targetSize = 0, bool showDefaultPicture = true); + /// + /// Gets the size of a picture + /// + /// The buffer + /// Passing MIME type can slightly speed things up + /// Size + Size GetPictureSize(byte[] pictureBinary, string mimeType = null); - /// - /// Gets a picture - /// - /// Picture identifier - /// Picture - Picture GetPictureById(int pictureId); + /// + /// TODO: (mc) + /// + /// + /// + /// + /// + /// + /// + IDictionary GetPictureInfos(IEnumerable pictureIds); - /// - /// Deletes a picture - /// - /// Picture - void DeletePicture(Picture picture); + /// + /// TODO: (mc) + /// + /// + /// + PictureInfo GetPictureInfo(int? pictureId); + + /// + /// TODO: (mc) + /// + /// + /// + /// + /// + /// + /// + PictureInfo GetPictureInfo(Picture picture); + + /// + /// Builds a url for a given . + /// + /// Picture identifier + /// The target picture size (longest side) + /// Store location URL; null to use determine the current store location automatically + /// Specifies the kind of fallback url to return if the argument is 0 or a picture with the passed id does not exist in the storage. + /// Picture URL + string GetUrl(int pictureId, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null); + + /// + /// Builds a url for a given instance. + /// + /// Picture instance + /// The target picture size (longest side) + /// Store location URL; null to use determine the current store location automatically + /// Specifies the kind of fallback url to return if the argument is null. + /// Picture URL + string GetUrl(Picture picture, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null); + + /// + /// Builds a url for a given instance. + /// + /// The PictureInfo instance to build a url for + /// The maximum size of the picture. If greather than null, a query is appended to the generated url. + /// The host (including scheme) to prepend to the url. + /// Specifies the kind of fallback url to return if the argument is null. + /// Generated url which can be processed by the media middleware controller + string GetUrl(PictureInfo info, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null); + + /// + /// Gets the fallback picture URL + /// + /// The target picture size (longest side) + /// Store location URL; null to use determine the current store location automatically + /// Default picture type + /// Picture URL + string GetFallbackUrl(int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null); + + /// + /// Clears the url cache completely or for a particular store + /// + /// The total count of removed cache entries + int ClearUrlCache(); + + /// + /// Gets a picture + /// + /// Picture identifier + /// Picture + Picture GetPictureById(int pictureId); /// /// Gets a collection of pictures @@ -136,12 +180,48 @@ string GetPictureUrl(Picture picture, /// Pictures IList GetPicturesByProductId(int productId, int recordsToReturn = 0); + /// + /// Gets a pictures map by product identifiers + /// + /// The ids of products to retrieve pictures for + /// Max number of pictures to retrieve per product, null to load all pictures + /// Whether the blob in MediaStorage table should be eager loaded + /// A lookup map of product ids and pictures + Multimap GetPicturesByProductIds(int[] productIds, int? maxPicturesPerProduct = null, bool withBlobs = false); + /// /// Gets pictures by picture identifier /// /// Picture identifier + /// Whether the blob in MediaStorage table should be eager loaded /// Pictures - IList GetPicturesByIds(int[] pictureIds); + IList GetPicturesByIds(int[] pictureIds, bool withBlobs = false); + + /// + /// Deletes a picture + /// + /// Picture + void DeletePicture(Picture picture); + + /// + /// Inserts a picture + /// + /// The picture binary + /// The picture MIME type + /// The SEO filename + /// A value indicating whether the picture is new + /// Picture width + /// Picture height + /// A value indicating whether the picture is initially in transient state + /// Picture + Picture InsertPicture( + byte[] pictureBinary, + string mimeType, + string seoFilename, + bool isNew, + int width = 0, + int height = 0, + bool isTransient = true); /// /// Inserts a picture @@ -153,31 +233,97 @@ string GetPictureUrl(Picture picture, /// A value indicating whether the picture is initially in transient state /// A value indicating whether to validated provided picture binary /// Picture - Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool isTransient = true, bool validateBinary = true); + Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool isTransient = true, bool validateBinary = true); - /// - /// Updates the picture - /// - /// The picture identifier - /// The picture binary - /// The picture MIME type - /// The SEO filename - /// A value indicating whether the picture is new - /// A value indicating whether to validated provided picture binary - /// Picture - Picture UpdatePicture(int pictureId, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true); + /// + /// Updates the picture + /// + /// The picture + /// The picture binary + /// The picture MIME type + /// The SEO filename + /// A value indicating whether the picture is new + /// A value indicating whether to validated provided picture binary + void UpdatePicture(Picture picture, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true); + } - /// - /// Updates a SEO filename of a picture - /// - /// The picture identifier - /// The SEO filename - /// Picture - Picture SetSeoFilename(int pictureId, string seoFilename); + public static class IPictureServiceExtensions + { + public static Picture UpdatePicture(this IPictureService pictureService, + int pictureId, + byte[] pictureBinary, + string mimeType, + string seoFilename, + bool isNew, + bool validateBinary = true) + { + var picture = pictureService.GetPictureById(pictureId); - /// - /// Gets or sets a value indicating whether the images should be stored in data base. - /// - bool StoreInDb { get; set; } - } + if (picture != null) + { + pictureService.UpdatePicture(picture, pictureBinary, mimeType, seoFilename, isNew, validateBinary); + } + + return picture; + } + + public static Size GetPictureSize(this IPictureService pictureService, Picture picture) + { + return ImageHeader.GetDimensions(pictureService.OpenPictureStream(picture), picture.MimeType, false); + } + + /// + /// TODO: (mc) + /// + /// + /// + public static IDictionary GetPictureInfos(this IPictureService pictureService, IEnumerable products) + { + Guard.NotNull(products, nameof(products)); + + return pictureService.GetPictureInfos(products.Select(x => x.MainPictureId.GetValueOrDefault())); + } + + /// + /// Builds a picture url + /// + /// The picture id to build a url for + /// The maximum size of the picture. If greather than null, a query is appended to the generated url. + /// The host (including scheme) to prepend to the url. + /// Specifies whether to return a fallback url if the picture does not exist in the storage (default: true). + /// Generated url which can be processed by the media middleware controller + public static string GetUrl(this IPictureService pictureService, int pictureId, int targetSize, bool fallback, string host = null) + { + var fallbackType = fallback ? FallbackPictureType.Entity : FallbackPictureType.NoFallback; + return pictureService.GetUrl(pictureId, targetSize, fallbackType, host); + } + + /// + /// Builds a picture url + /// + /// The picture to build a url for + /// The maximum size of the picture. If greather than null, a query is appended to the generated url. + /// The host (including scheme) to prepend to the url. + /// Specifies whether to return a fallback url if the picture does not exist in the storage (default: true). + /// Generated url which can be processed by the media middleware controller + public static string GetUrl(this IPictureService pictureService, Picture picture, int targetSize, bool fallback, string host = null) + { + var fallbackType = fallback ? FallbackPictureType.Entity : FallbackPictureType.NoFallback; + return pictureService.GetUrl(picture, targetSize, fallbackType, host); + } + + /// + /// Builds a picture url + /// + /// The picture info to build a url for + /// The maximum size of the picture. If greather than null, a query is appended to the generated url. + /// The host (including scheme) to prepend to the url. + /// Specifies whether to return a fallback url if the picture does not exist in the storage (default: true). + /// Generated url which can be processed by the media middleware controller + public static string GetUrl(this IPictureService pictureService, PictureInfo info, int targetSize, bool fallback, string host = null) + { + var fallbackType = fallback ? FallbackPictureType.Entity : FallbackPictureType.NoFallback; + return pictureService.GetUrl(info, targetSize, fallbackType, host); + } + } } diff --git a/src/Libraries/SmartStore.Services/Media/ImageCache.cs b/src/Libraries/SmartStore.Services/Media/ImageCache.cs index 23e88f2295..68c98f8926 100644 --- a/src/Libraries/SmartStore.Services/Media/ImageCache.cs +++ b/src/Libraries/SmartStore.Services/Media/ImageCache.cs @@ -1,170 +1,249 @@ using System; -using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; using System.Linq; -using System.Text; +using System.Threading.Tasks; using System.Web; -using ImageResizer; +using System.Web.Hosting; using SmartStore.Core; using SmartStore.Core.Domain.Media; -using SmartStore.Services.Stores; +using SmartStore.Core.IO; +using SmartStore.Core.Logging; namespace SmartStore.Services.Media { - - public class ImageCache : IImageCache + public class ImageCache : IImageCache { - private const int MULTIPLE_THUMB_DIRECTORIES_LENGTH = 4; + public const string IdFormatString = "0000000"; + internal const int MaxDirLength = 4; - private readonly MediaSettings _mediaSettings; - private readonly IWebHelper _webHelper; - private readonly DirectoryInfo _cacheRootDir; - private readonly IStoreContext _storeContext; - private readonly HttpContextBase _httpContext; + private readonly MediaSettings _mediaSettings; + private readonly string _thumbsRootDir; + private readonly IMediaFileSystem _fileSystem; + private readonly IImageProcessor _imageProcessor; - public ImageCache(MediaSettings mediaSettings, IWebHelper webHelper, IStoreContext storeContext, HttpContextBase httpContext) + public ImageCache(MediaSettings mediaSettings, IMediaFileSystem fileSystem, IImageProcessor imageProcessor) { - this._mediaSettings = mediaSettings; - this._webHelper = webHelper; - this._storeContext = storeContext; - this._httpContext = httpContext; + _mediaSettings = mediaSettings; + _fileSystem = fileSystem; + _imageProcessor = imageProcessor; - _cacheRootDir = new DirectoryInfo(_webHelper.MapPath("~/Media/Thumbs")); - } + _thumbsRootDir = "Thumbs/"; - public void AddImageToCache(CachedImageResult cachedImage, byte[] buffer) - { - Guard.ArgumentNotNull(() => cachedImage); + Logger = NullLogger.Instance; + } - if (buffer == null || buffer.Length == 0) - { - throw new ArgumentException("The image buffer cannot be empty.", "buffer"); - } + public ILogger Logger + { + get; + set; + } - if (cachedImage.Exists) - { - File.Delete(cachedImage.LocalPath); - } + public void Put(CachedImageResult cachedImage, byte[] buffer) + { + if (PreparePut(cachedImage, buffer)) + { + var path = BuildPath(cachedImage.Path); - // create directory if necessary - string imageDir = Path.GetDirectoryName(cachedImage.LocalPath); - if (imageDir.HasValue() && !System.IO.Directory.Exists(imageDir)) - { - System.IO.Directory.CreateDirectory(imageDir); - } + _fileSystem.WriteAllBytes(path, buffer); - // save file - File.WriteAllBytes(cachedImage.LocalPath, buffer); - } + cachedImage.Exists = true; + cachedImage.File = _fileSystem.GetFile(path); + } + } + + public async Task PutAsync(CachedImageResult cachedImage, byte[] buffer) + { + if (PreparePut(cachedImage, buffer)) + { + var path = BuildPath(cachedImage.Path); + + // save file + await _fileSystem.WriteAllBytesAsync(path, buffer); + + // Refresh info + cachedImage.Exists = true; + cachedImage.File = _fileSystem.GetFile(path); + } + } - public virtual CachedImageResult GetCachedImage(int? pictureId, string seoFileName, string extension, object settings = null) + private bool PreparePut(CachedImageResult cachedImage, byte[] buffer) + { + Guard.NotNull(cachedImage, nameof(cachedImage)); + + if (buffer == null || buffer.Length == 0) + { + return false; + } + + if (cachedImage.Exists) + { + _fileSystem.DeleteFile(BuildPath(cachedImage.Path)); + } + + // create folder if needed + string imageDir = System.IO.Path.GetDirectoryName(cachedImage.Path); + if (imageDir.HasValue()) + { + _fileSystem.TryCreateFolder(BuildPath(imageDir)); + } + + return true; + } + + public virtual CachedImageResult Get(int? pictureId, string seoFileName, string extension, ProcessImageQuery query = null) { - var imagePath = this.GetCachedImagePath(pictureId, seoFileName, extension, ImageResizerUtils.CreateResizeSettings(settings)); - var localPath = this.GetImageLocalPath(imagePath); + Guard.NotEmpty(extension, nameof(extension)); - var result = new CachedImageResult - { - Path = imagePath, //"Media/Thumbs/" + imagePath, - LocalPath = localPath, - FileName = Path.GetFileName(imagePath), - Extension = GetCleanFileExtension(imagePath), - Exists = File.Exists(localPath) - }; - - return result; + extension = query?.GetResultExtension() ?? extension.TrimStart('.').ToLower(); + var imagePath = GetCachedImagePath(pictureId, seoFileName, extension, query); + + var file = _fileSystem.GetFile(BuildPath(imagePath)); + + var result = new CachedImageResult(file) + { + Path = imagePath, + Extension = extension, + IsRemote = _fileSystem.IsCloudStorage + }; + + if (file.Exists && file.Size <= 0) + { + result.Exists = false; + } + + return result; } - public virtual string GetImageUrl(string imagePath, string storeLocation = null) + 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)); + + return _fileSystem.GetFile(BuildPath(cachedImage.Path)).OpenRead(); + } + + public virtual string GetPublicUrl(string imagePath) { if (imagePath.IsEmpty()) return null; - var root = storeLocation; + return _fileSystem.GetPublicUrl(BuildPath(imagePath), true).EmptyNull(); + } + + public virtual void RefreshInfo(CachedImageResult cachedImage) + { + Guard.NotNull(cachedImage, nameof(cachedImage)); + + var file = _fileSystem.GetFile(cachedImage.File.Path); + cachedImage.File = file; + cachedImage.Exists = file.Exists && file.Size > 0; + } + + public virtual void Delete(Picture picture) + { + var filter = string.Format("{0}*.*", picture.Id.ToString(IdFormatString)); - if (root.IsEmpty()) + var files = _fileSystem.SearchFiles(_thumbsRootDir, filter); + foreach (var file in files) { - var cdnUrl = _storeContext.CurrentStore.ContentDeliveryNetwork; - if (cdnUrl.HasValue() && !_httpContext.IsDebuggingEnabled && !_httpContext.Request.IsLocal) - { - root = cdnUrl; - } + _fileSystem.DeleteFile(file); } + } - root = root.NullEmpty() ?? _httpContext.Request.ApplicationPath; - root = root.TrimEnd('/', '\\'); - var url = root + "/Media/Thumbs/"; + 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); - url = url + imagePath; - return url; - } - - public virtual void DeleteCachedImages(Picture picture) - { - string filter = string.Format("{0}*.*", picture.Id.ToString("0000000")); - - var files = _cacheRootDir.EnumerateFiles(filter, SearchOption.AllDirectories); - foreach (var file in files) - { - file.Delete(); - } - } + var files = _fileSystem.SearchFiles(BuildPath(file.Directory), filter); + foreach (var f in files) + { + _fileSystem.DeleteFile(f); + } + } - public virtual void DeleteCachedImages() + public virtual void Clear() { - for (int i = 0; i < 10; i++) + for (int i = 0; i < 10; i++) { try { - foreach (var file in _cacheRootDir.GetFiles()) + foreach (var file in _fileSystem.ListFiles(_thumbsRootDir)) { if (!file.Name.IsCaseInsensitiveEqual("placeholder") && !file.Name.IsCaseInsensitiveEqual("placeholder.txt")) { - file.Delete(); + _fileSystem.DeleteFile(file.Path); } } - foreach (var dir in _cacheRootDir.GetDirectories()) + foreach (var dir in _fileSystem.ListFolders(_thumbsRootDir)) { - dir.Delete(true); - } + _fileSystem.DeleteFolder(dir.Path); + } + return; } - catch + catch (Exception ex) { + Logger.Error(ex); } } } public void CacheStatistics(out long fileCount, out long totalSize) { - var allFiles = _cacheRootDir.GetFiles("*.*", SearchOption.AllDirectories); - var allImageFiles = allFiles.Where(x => !x.Name.IsCaseInsensitiveEqual("placeholder") && !x.Name.IsCaseInsensitiveEqual("placeholder.txt")); + fileCount = 0; + totalSize = 0; - fileCount = allImageFiles.Count(); + if (!_fileSystem.FolderExists(_thumbsRootDir)) + { + return; + } - if (fileCount == 0) - totalSize = 0; - else - totalSize = allImageFiles.Sum(x => x.Length); + fileCount = _fileSystem.SearchFiles(_thumbsRootDir, "*.*").Count(); + totalSize = _fileSystem.ListFolders(_thumbsRootDir).Sum(x => x.Size); } - #region Utils - - /// - /// Returns the file name with the subfolder (when multidirs are enabled) - /// - /// - /// - /// - internal string GetCachedImagePath(int? pictureId, string seoFileName, string extension, ResizeSettings settings = null) + #region Utils + + /// + /// Returns the file name with the subfolder (when multidirs are enabled) + /// + /// + /// File name without extension + /// Dot-less file extension + /// + /// + private string GetCachedImagePath(int? pictureId, string seoFileName, string extension, ProcessImageQuery query = null) { - Guard.ArgumentNotEmpty(extension, "extension"); - string imageFileName = null; string firstPart = ""; if (pictureId.GetValueOrDefault() > 0) { - firstPart = pictureId.Value.ToString("0000000") + (seoFileName.IsEmpty() ? "" : "-"); + firstPart = pictureId.Value.ToString(IdFormatString) + (seoFileName.IsEmpty() ? "" : "-"); } if (firstPart.IsEmpty() && seoFileName.IsEmpty()) @@ -174,63 +253,55 @@ internal string GetCachedImagePath(int? pictureId, string seoFileName, string ex } seoFileName = seoFileName.EmptyNull(); - extension = extension.TrimStart('.'); - if (!NeedsProcessing(settings)) + if (query == null || !query.NeedsProcessing()) { - imageFileName = "{0}{1}.{2}".FormatInvariant(firstPart, seoFileName, extension); + imageFileName = String.Concat(firstPart, seoFileName); } else { - string hashedProps = CreateSettingsHash(settings); - imageFileName = "{0}{1}-{2}.{3}".FormatInvariant(firstPart, seoFileName, hashedProps, extension); - } + imageFileName = String.Concat(firstPart, seoFileName, query.CreateHash()); + } - if (_mediaSettings.MultipleThumbDirectories) + if (_mediaSettings.MultipleThumbDirectories && imageFileName != null && imageFileName.Length > MaxDirLength) { - //get the first four letters of the file name - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(imageFileName); - if (fileNameWithoutExtension != null && fileNameWithoutExtension.Length > MULTIPLE_THUMB_DIRECTORIES_LENGTH) - { - var subDirectoryName = fileNameWithoutExtension.Substring(0, MULTIPLE_THUMB_DIRECTORIES_LENGTH); - imageFileName = subDirectoryName + "/" + imageFileName; - } + // Get the first four letters of the file name + var subDirectoryName = imageFileName.Substring(0, MaxDirLength); + imageFileName = String.Concat(subDirectoryName, "/", imageFileName); } - return imageFileName; + return String.Concat(imageFileName, ".", extension); } - private string CreateSettingsHash(ResizeSettings settings) - { - if (settings.Count == 2 && settings.MaxWidth > 0 && settings.MaxWidth == settings.MaxHeight) - { - return settings.MaxWidth.ToString(); - } - return settings.ToString().Hash(Encoding.ASCII); - } + /// + /// 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 - private bool NeedsProcessing(ResizeSettings settings) - { - return settings != null && settings.Count > 0; - } + var imageFileName = String.Concat(file.Title, query.CreateHash()); + var extension = (query.GetResultExtension() ?? file.Extension).EnsureStartsWith(".").ToLower(); + var path = _fileSystem.Combine(file.Directory, imageFileName + extension); - private string GetImageLocalPath(string imagePath) - { - if (imagePath.IsEmpty()) - return null; - var imageFilePath = Path.Combine(_cacheRootDir.FullName, imagePath.Replace('/', '\\')); - return imageFilePath; - } + return path.TrimStart('/', '\\'); + } - private static string GetCleanFileExtension(string url) - { - var extension = Path.GetExtension(url); - if (extension != null) - { - return extension.Replace(".", "").ToLower(); - } - return string.Empty; - } + private string BuildPath(string imagePath) + { + if (imagePath.IsEmpty()) + return null; + + return String.Concat(_thumbsRootDir, imagePath); + } #endregion diff --git a/src/Libraries/SmartStore.Services/Media/ImageCacheExtensions.cs b/src/Libraries/SmartStore.Services/Media/ImageCacheExtensions.cs index ef46a8ddea..98934a6561 100644 --- a/src/Libraries/SmartStore.Services/Media/ImageCacheExtensions.cs +++ b/src/Libraries/SmartStore.Services/Media/ImageCacheExtensions.cs @@ -6,45 +6,44 @@ namespace SmartStore.Services.Media { public static class ImageCacheExtensions { - - /// - /// Gets an instance of the object, which contains information about a cached image. - /// - /// The picture object for which to resolve a cached image. - /// The image processing settings. - /// An instance of the object - /// If the requested image does not exist in the cache, the value of the Exists property will be false. - public static CachedImageResult GetCachedImage(this IImageCache imageCache, Picture picture, object settings = null) + /// + /// Gets an instance of the object, which contains information about a cached image. + /// + /// The picture object for which to resolve a cached image. + /// 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. + public static CachedImageResult Get(this IImageCache imageCache, Picture picture, ProcessImageQuery query = null) { - Guard.ArgumentNotNull(() => picture); - return imageCache.GetCachedImage(picture.Id, picture.SeoFilename, MimeTypes.MapMimeTypeToExtension(picture.MimeType), settings); + Guard.NotNull(picture, nameof(picture)); + + return imageCache.Get(picture.Id, picture.SeoFilename, MimeTypes.MapMimeTypeToExtension(picture.MimeType), query); } - /// - /// Adds an image to the cache. - /// - /// The picture id, which will be part of the resulting file name. - /// The seo friendly picture name, which will be part of the resulting file name. - /// The extension of the resulting file - /// The image binary data. - /// The image processing settings.This object, if not null, is hashed and appended to the resulting file name. - public static void AddImageToCache(this IImageCache imageCache, int? pictureId, string seoFileName, string extension, byte[] buffer, object settings = null) + /// + /// Adds an image to the cache. + /// + /// The picture id, which will be part of the resulting file name. + /// The seo friendly picture name, which will be part of the resulting file name. + /// The extension of the resulting file + /// The image binary data. + /// The image processing query. This object, if not null, is hashed and appended to the resulting file name. + public static void Put(this IImageCache imageCache, int? pictureId, string seoFileName, string extension, byte[] buffer, ProcessImageQuery query = null) { - var cachedImage = imageCache.GetCachedImage(pictureId, seoFileName, extension, settings); - imageCache.AddImageToCache(cachedImage, buffer); + var cachedImage = imageCache.Get(pictureId, seoFileName, extension, query); + imageCache.Put(cachedImage, buffer); } - /// - /// Adds an image to the cache. - /// - /// The picture object needed for building the resulting file name. - /// The image binary data. - /// The image processing settings. This object, if not null, is hashed and appended to the resulting file name. - public static void AddImageToCache(this IImageCache imageCache, Picture picture, byte[] buffer, object settings = null) + /// + /// Adds an image to the cache. + /// + /// The picture object needed for building the resulting file name. + /// The image binary data. + /// The image processing query. This object, if not null, is hashed and appended to the resulting file name. + public static void Put(this IImageCache imageCache, Picture picture, byte[] buffer, ProcessImageQuery query = null) { - Guard.ArgumentNotNull(() => picture); - imageCache.AddImageToCache(picture.Id, picture.SeoFilename, MimeTypes.MapMimeTypeToExtension(picture.MimeType), buffer, settings); + Guard.NotNull(picture, nameof(picture)); + imageCache.Put(picture.Id, picture.SeoFilename, MimeTypes.MapMimeTypeToExtension(picture.MimeType), buffer, query); } - } } diff --git a/src/Libraries/SmartStore.Services/Media/ImageHeader.cs b/src/Libraries/SmartStore.Services/Media/ImageHeader.cs deleted file mode 100644 index f849f49c2a..0000000000 --- a/src/Libraries/SmartStore.Services/Media/ImageHeader.cs +++ /dev/null @@ -1,185 +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 }, - }; - - /// - /// 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) - { - int maxMagicBytesLength = imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; - 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/ImageResizerService.cs b/src/Libraries/SmartStore.Services/Media/ImageResizerService.cs deleted file mode 100644 index 665bcd8e54..0000000000 --- a/src/Libraries/SmartStore.Services/Media/ImageResizerService.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.IO; -using System.Text; -using ImageResizer; -using ImageResizer.Configuration; -using ImageResizer.Plugins.PrettyGifs; -using SmartStore.Core.Logging; - -namespace SmartStore.Services.Media -{ - - public class ImageResizerService : IImageResizerService - { - - static ImageResizerService() - { - new PrettyGifs().Install(Config.Current); - } - - public bool IsSupportedImage(string fileName) - { - var ext = Path.GetExtension(fileName); - if (ext != null) - { - var extension = ext.Trim('.'); - return ImageBuilder.Current.GetSupportedFileExtensions().Any(x => x == extension); - } - - return false; - } - - public MemoryStream ResizeImage(Stream source, int? maxWidth = null, int? maxHeight = null, int? quality = 0, object settings = null) - { - Guard.ArgumentNotNull(() => source); - - ResizeSettings resizeSettings = ImageResizerUtils.CreateResizeSettings(settings); - - if (quality.HasValue) - resizeSettings.Quality = quality.Value; - if (maxHeight.HasValue) - resizeSettings.MaxHeight = maxHeight.Value; - if (maxWidth.HasValue) - resizeSettings.MaxWidth = maxWidth.Value; - - var resultStream = new MemoryStream(); - ImageBuilder.Current.Build(source, resultStream, resizeSettings); - return resultStream; - } - - } - -} diff --git a/src/Libraries/SmartStore.Services/Media/ImageResizerUtils.cs b/src/Libraries/SmartStore.Services/Media/ImageResizerUtils.cs deleted file mode 100644 index 518d4908d7..0000000000 --- a/src/Libraries/SmartStore.Services/Media/ImageResizerUtils.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using ImageResizer; -using ImageResizer.Configuration; - -namespace SmartStore.Services.Media -{ - internal static class ImageResizerUtils - { - public static ResizeSettings CreateResizeSettings(object settings) - { - ResizeSettings resizeSettings; - - if (settings is string) - { - resizeSettings = new ResizeSettings((string)settings); - } - else if (settings is ResizeSettings) - { - resizeSettings = (ResizeSettings)settings; - } - else - { - resizeSettings = new ResizeSettings(); - } - - return resizeSettings; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs b/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs index b776fd8de7..40d85dd39e 100644 --- a/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs +++ b/src/Libraries/SmartStore.Services/Media/MediaExtensions.cs @@ -2,23 +2,19 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Media; using SmartStore.Core.Infrastructure; namespace SmartStore.Services.Media -{ - +{ public static class MediaHelper { - public static void UpdateDownloadTransientStateFor(TEntity entity, Expression> downloadIdProp, bool save = false) where TEntity : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => downloadIdProp); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(downloadIdProp, nameof(downloadIdProp)); var propName = downloadIdProp.ExtractMemberInfo().Name; int currentDownloadId = downloadIdProp.Compile().Invoke(entity); @@ -29,8 +25,8 @@ public static void UpdateDownloadTransientStateFor(TEntity entity, Expr public static void UpdateDownloadTransientStateFor(TEntity entity, Expression> downloadIdProp, bool save = false) where TEntity : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => downloadIdProp); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(downloadIdProp, nameof(downloadIdProp)); var propName = downloadIdProp.ExtractMemberInfo().Name; int currentDownloadId = downloadIdProp.Compile().Invoke(entity).GetValueOrDefault(); @@ -41,8 +37,8 @@ public static void UpdateDownloadTransientStateFor(TEntity entity, Expr public static void UpdatePictureTransientStateFor(TEntity entity, Expression> pictureIdProp, bool save = false) where TEntity : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => pictureIdProp); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(pictureIdProp, nameof(pictureIdProp)); var propName = pictureIdProp.ExtractMemberInfo().Name; int currentPictureId = pictureIdProp.Compile().Invoke(entity); @@ -53,8 +49,8 @@ public static void UpdatePictureTransientStateFor(TEntity entity, Expre public static void UpdatePictureTransientStateFor(TEntity entity, Expression> pictureIdProp, bool save = false) where TEntity : BaseEntity { - Guard.ArgumentNotNull(() => entity); - Guard.ArgumentNotNull(() => pictureIdProp); + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(pictureIdProp, nameof(pictureIdProp)); var propName = pictureIdProp.ExtractMemberInfo().Name; int currentPictureId = pictureIdProp.Compile().Invoke(entity).GetValueOrDefault(); @@ -106,12 +102,9 @@ internal static void UpdateTransientStateForEntityInternal( Action deleteAction, bool save) where TEntity : BaseEntity where TMedia : BaseEntity { - bool editMode = !entity.IsTransientRecord(); - var modifiedProperties = editMode ? rs.Context.GetModifiedProperties(entity) : new Dictionary(); - object obj = null; int prevMediaId = 0; - if (modifiedProperties.TryGetValue(propName, out obj)) + if (rs.Context.TryGetModifiedProperty(entity, propName, out obj)) { prevMediaId = ((int?)obj).GetValueOrDefault(); } @@ -180,8 +173,6 @@ internal static void UpdateTransientStateCore( rs.AutoCommitEnabled = autoCommit; } } - - } } diff --git a/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs b/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs new file mode 100644 index 0000000000..6317105fde --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs @@ -0,0 +1,55 @@ +using System; +using SmartStore.Core.Data; +using SmartStore.Core.IO; +using SmartStore.Utilities; + +namespace SmartStore.Services.Media +{ + public interface IMediaFileSystem : IFileSystem + { + } + + public class MediaFileSystem : LocalFileSystem, IMediaFileSystem + { + private static string _mediaPublicPath; + + public MediaFileSystem() + : base(GetMediaBasePath(), "~/" + GetMediaPublicPath()) + { + this.TryCreateFolder("Storage"); + this.TryCreateFolder("Thumbs"); + this.TryCreateFolder("Uploaded"); + this.TryCreateFolder("QueuedEmailAttachment"); + this.TryCreateFolder("Downloads"); + } + + public static string GetMediaBasePath() + { + var path = CommonHelper.GetAppSetting("sm:MediaStoragePath")?.Trim().NullEmpty(); + if (path == null) + { + path = "/App_Data/Tenants/" + DataSettings.Current.TenantName + "/Media"; + } + + 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 563993234f..9e421212eb 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -1,179 +1,222 @@ using System; using System.Collections.Generic; +using System.Data.Entity; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using ImageResizer; +using System.Web; +using System.Web.Hosting; +using SmartStore.Collections; using SmartStore.Core; +using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; using SmartStore.Core.Events; using SmartStore.Core.IO; using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; using SmartStore.Services.Configuration; +using SmartStore.Services.Media.Storage; using SmartStore.Utilities; namespace SmartStore.Services.Media -{ - /// - /// Picture service - /// - public partial class PictureService : IPictureService +{ + [Serializable] + public class PictureInfo + { + public int Id { get; set; } + /// + /// The virtual path to the image file to be processed by the media middleware controller, e.g. "/media/image/1234/image.jpg" + /// + public string Path { get; set; } + public int? Width { get; set; } + public int? Height { get; set; } + public string MimeType { get; set; } + public string Extension { get; set; } + } + + public partial class PictureService : IPictureService { - #region Const + // 0 = Id + private const string MEDIACACHE_LOOKUP_KEY = "media:info-{0}"; + private const string MEDIACACHE_LOOKUP_KEY_PATTERN = "media:info-*"; - private const int MULTIPLE_THUMB_DIRECTORIES_LENGTH = 4; - - #endregion - - #region Fields - - private static readonly object s_lock = new object(); - - private readonly IRepository _pictureRepository; + private readonly IRepository _pictureRepository; private readonly IRepository _productPictureRepository; private readonly ISettingService _settingService; - private readonly IWebHelper _webHelper; - private readonly ILogger _logger; private readonly IEventPublisher _eventPublisher; private readonly MediaSettings _mediaSettings; - private readonly IImageResizerService _imageResizerService; + private readonly IImageProcessor _imageProcessor; private readonly IImageCache _imageCache; - private readonly INotifier _notifier; + private readonly Provider _storageProvider; + private readonly HttpContextBase _httpContext; + private readonly ICacheManager _cacheManager; - private string _mediaPath; - private string _imagesPath; + private readonly string _host; + private readonly string _appPath; - #endregion - - #region Ctor + private static readonly string _processedImagesRootPath; + private static readonly string _fallbackImagesRootPath; + static PictureService() + { + _processedImagesRootPath = MediaFileSystem.GetMediaPublicPath() + "image/"; + _fallbackImagesRootPath = "content/images/"; + } - public PictureService( + public PictureService( IRepository pictureRepository, IRepository productPictureRepository, ISettingService settingService, - IWebHelper webHelper, - ILogger logger, IEventPublisher eventPublisher, MediaSettings mediaSettings, - IImageResizerService imageResizerService, + IImageProcessor imageProcessor, IImageCache imageCache, - INotifier notifier) + IProviderManager providerManager, + IStoreContext storeContext, + HttpContextBase httpContext, + ICacheManager cacheManager) { - this._pictureRepository = pictureRepository; - this._productPictureRepository = productPictureRepository; - this._settingService = settingService; - this._webHelper = webHelper; - this._logger = logger; - this._eventPublisher = eventPublisher; - this._mediaSettings = mediaSettings; - this._imageResizerService = imageResizerService; - this._imageCache = imageCache; - this._notifier = notifier; - } + _pictureRepository = pictureRepository; + _productPictureRepository = productPictureRepository; + _settingService = settingService; + _eventPublisher = eventPublisher; + _mediaSettings = mediaSettings; + _imageProcessor = imageProcessor; + _imageCache = imageCache; + _httpContext = httpContext; + _cacheManager = cacheManager; - #endregion + var systemName = settingService.GetSettingByKey("Media.Storage.Provider", DatabaseMediaStorageProvider.SystemName); + _storageProvider = providerManager.GetProvider(systemName); - #region Utilities + Logger = NullLogger.Instance; - /// - /// Save picture on file system - /// - /// Picture identifier - /// Picture binary - /// MIME type - protected virtual void SavePictureInFile(int pictureId, byte[] pictureBinary, string mimeType) - { - string filePath; - SavePictureInFile(pictureId, pictureBinary, mimeType, out filePath); - } + string appPath = "/"; + + if (HostingEnvironment.IsHosted) + { + appPath = HostingEnvironment.ApplicationVirtualPath.EmptyNull(); + + var cdn = storeContext.CurrentStore.ContentDeliveryNetwork; + if (cdn.HasValue() && !_httpContext.IsDebuggingEnabled && !_httpContext.Request.IsLocal) + { + _host = cdn; + } + else if (mediaSettings.AutoGenerateAbsoluteUrls) + { + var uri = httpContext.Request.Url; + _host = "//{0}{1}".FormatInvariant(uri.Authority, appPath); + } + else + { + _host = appPath; + } + } - private void SavePictureInFile(int pictureId, byte[] pictureBinary, string mimeType, out string filePath) + _host = _host.EmptyNull().EnsureEndsWith("/"); + _appPath = appPath.EnsureEndsWith("/"); + + } + + public ILogger Logger { get; set; } + + #region Utilities + + public static string FallbackImagesRootPath { - filePath = null; - string lastPart = MimeTypes.MapMimeTypeToExtension(mimeType); - string fileName = string.Format("{0}-0.{1}", pictureId.ToString("0000000"), lastPart); - filePath = GetPictureLocalPath(fileName); - File.WriteAllBytes(filePath, pictureBinary); - } - - /// - /// Delete a picture on file system - /// - /// Picture - protected virtual void DeletePictureOnFileSystem(Picture picture) - { - if (picture == null) - throw new ArgumentNullException("picture"); - - string lastPart = MimeTypes.MapMimeTypeToExtension(picture.MimeType); - string fileName = string.Format("{0}-0.{1}", picture.Id.ToString("0000000"), lastPart); - string filePath = GetPictureLocalPath(fileName); - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - } + get { return "~/" + _fallbackImagesRootPath; } + } - private string GetDefaultImageFileName(PictureType defaultPictureType = PictureType.Entity) + protected virtual string GetFallbackImageFileName(FallbackPictureType defaultPictureType = FallbackPictureType.Entity) { string defaultImageFileName; + switch (defaultPictureType) { - case PictureType.Entity: - defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.jpg"); - break; - case PictureType.Avatar: - defaultImageFileName = _settingService.GetSettingByKey("Media.Customer.DefaultAvatarImageName", "default-avatar.jpg"); + case FallbackPictureType.Entity: + defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.png"); break; default: - defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.jpg"); + defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.png"); break; } return defaultImageFileName; } - public virtual byte[] ValidatePicture(byte[] pictureBinary) - { - Size originalSize = this.GetPictureSize(pictureBinary); - - int maxSize = _mediaSettings.MaximumImageSize; - if (originalSize.IsEmpty || (originalSize.Height <= maxSize && originalSize.Width <= maxSize)) - { - return pictureBinary; - } - - using (var resultStream = _imageResizerService.ResizeImage(new MemoryStream(pictureBinary), maxSize, maxSize, _mediaSettings.DefaultImageQuality)) - { - return resultStream.GetBuffer(); - } - } - #endregion - #region Methods + #region Imaging - public byte[] FindEqualPicture(string path, IEnumerable pictures, out int equalPictureId) + public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType) { - return FindEqualPicture(File.ReadAllBytes(path), pictures, out equalPictureId); + return ValidatePicture(pictureBinary, mimeType, out _); + } + + public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType, out Size size) + { + Guard.NotNull(pictureBinary, nameof(pictureBinary)); + Guard.NotEmpty(mimeType, nameof(mimeType)); + + size = Size.Empty; + + var originalSize = ImageHeader.GetDimensions(pictureBinary, mimeType); + var maxSize = _mediaSettings.MaximumImageSize; + + var query = new ProcessImageQuery(pictureBinary) + { + Quality = _mediaSettings.DefaultImageQuality, + Format = MimeTypes.MapMimeTypeToExtension(mimeType), + IsValidationMode = true + }; + + if (originalSize.IsEmpty || (originalSize.Height <= maxSize && originalSize.Width <= maxSize)) + { + // Give subscribers the chance to (pre)-process + var evt = new ImageUploadValidatedEvent(query, originalSize); + _eventPublisher.Publish(evt); + + if (evt.ResultBuffer != null) + { + // Maybe subscriber forgot to set this, so check + size = evt.ResultSize.IsEmpty ? originalSize : evt.ResultSize; + return evt.ResultBuffer; + } + else + { + size = originalSize; + return pictureBinary; + } + } + + query.MaxWidth = maxSize; + query.MaxHeight = maxSize; + + using (var result = _imageProcessor.ProcessImage(query)) + { + size = new Size(result.Width, result.Height); + var buffer = result.OutputStream.GetBuffer(); + return buffer; + } } - public byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId) + 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)) { @@ -189,609 +232,516 @@ public byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictur { return null; } + finally + { + myStream.Dispose(); + } } - public virtual string GetPictureSeName(string name) + public virtual Stream OpenPictureStream(Picture picture) + { + Guard.NotNull(picture, nameof(picture)); + + return _storageProvider.Value.OpenRead(picture.ToMedia()); + } + + public virtual byte[] LoadPictureBinary(Picture picture) { - return SeoHelper.GetSeName(name, true, false); + Guard.NotNull(picture, nameof(picture)); + + return _storageProvider.Value.Load(picture.ToMedia()); } - public virtual string GetThumbLocalPath(Picture picture, int targetSize = 0, bool showDefaultPicture = true) - { - // 'GetPictureUrl' takes care of creating the thumb when not created already - string url = this.GetPictureUrl(picture, targetSize, showDefaultPicture); + public virtual Task LoadPictureBinaryAsync(Picture picture) + { + Guard.NotNull(picture, nameof(picture)); - if (url.HasValue()) - { - var settings = this.CreateResizeSettings(targetSize); + return _storageProvider.Value.LoadAsync(picture.ToMedia()); + } - var cachedImage = _imageCache.GetCachedImage(picture, settings); - if (cachedImage.Exists) - { - return cachedImage.LocalPath; - } + public virtual Size GetPictureSize(byte[] pictureBinary, string mimeType = null) + { + return ImageHeader.GetDimensions(pictureBinary, mimeType); + } - if (showDefaultPicture) - { - var fileName = this.GetDefaultImageFileName(); - cachedImage = _imageCache.GetCachedImage( - 0, - Path.GetFileNameWithoutExtension(fileName), - Path.GetExtension(fileName).TrimStart('.'), - settings); - if (cachedImage.Exists) - { - return cachedImage.LocalPath; - } - } - } + public IDictionary GetPictureInfos(IEnumerable pictureIds) + { + Guard.NotNull(pictureIds, nameof(pictureIds)); + + var allRequestedInfos = (from id in pictureIds.Distinct().Where(x => x > 0) + let cacheKey = MEDIACACHE_LOOKUP_KEY.FormatInvariant(id) + select new + { + PictureId = id, + CacheKey = cacheKey, + Info = _cacheManager.Contains(cacheKey) ? _cacheManager.Get(cacheKey, independent: true) : (PictureInfo)null + }).ToList(); + + var result = new Dictionary(allRequestedInfos.Count); + var uncachedPictureIds = allRequestedInfos.Where(x => x.Info == null).Select(x => x.PictureId).ToArray(); + var uncachedPictures = new Dictionary(); + + if (uncachedPictureIds.Length > 0) + { + uncachedPictures = GetPicturesByIds(uncachedPictureIds, false).ToDictionary(x => x.Id); + } - return string.Empty; + foreach (var info in allRequestedInfos) + { + if (info.Info != null) + { + result.Add(info.PictureId, info.Info); + } + else + { + // TBD: (mc) Does this need a locking strategy? Apparently yes. But it is hard to accomplish for a random sequence + // without locking the whole thing and loosing performance. Better no lock (?) + var newInfo = CreatePictureInfo(uncachedPictures.Get(info.PictureId)); + result.Add(info.PictureId, newInfo); + _cacheManager.Put(info.CacheKey, newInfo); + } + } - } + return result; + } - public virtual string GetDefaultPictureUrl(int targetSize = 0, PictureType defaultPictureType = PictureType.Entity, string storeLocation = null) - { - string defaultImageFileName = GetDefaultImageFileName(defaultPictureType); - - string filePath = GetDefaultPictureLocalPath(defaultImageFileName); - if (!File.Exists(filePath)) - { - return string.Empty; - } - - var url = this.GetProcessedImageUrl( - filePath, - 0, - Path.GetFileNameWithoutExtension(filePath), - Path.GetExtension(filePath), - targetSize, - storeLocation); - - return url; - } + public PictureInfo GetPictureInfo(int? pictureId) + { + if (pictureId.GetValueOrDefault() < 1) + return null; - protected internal virtual string GetProcessedImageUrl(object source, int? pictureId, string seoFileName, string extension, int targetSize = 0, string storeLocation = null) - { - var cachedImage = _imageCache.GetCachedImage( - pictureId, - seoFileName, - extension, - this.CreateResizeSettings(targetSize)); - - if (!cachedImage.Exists) - { - lock (s_lock) - { - if (!File.Exists(cachedImage.LocalPath)) // check again - { - var buffer = source as byte[]; - if (buffer == null) - { - if (!(source is string)) - { - return string.Empty; - } - - try - { - buffer = File.ReadAllBytes((string)source); - } - catch (Exception ex) - { - _logger.Error("Error reading media file '{0}'.".FormatInvariant(source), ex); - return string.Empty; - } - } - - try - { - if (targetSize == 0) - { - _imageCache.AddImageToCache(cachedImage, buffer); - } - else - { - var sourceStream = new MemoryStream(buffer); - using (var resultStream = _imageResizerService.ResizeImage(sourceStream, targetSize, targetSize, _mediaSettings.DefaultImageQuality)) - { - _imageCache.AddImageToCache(cachedImage, resultStream.GetBuffer()); - } - } - } - catch (Exception ex) - { - _logger.Error("Error processing/writing media file '{0}'.".FormatInvariant(cachedImage.LocalPath), ex); - return string.Empty; - } - } - } - } + var cacheKey = MEDIACACHE_LOOKUP_KEY.FormatInvariant(pictureId.GetValueOrDefault()); + var info = _cacheManager.Get(cacheKey, () => + { + return CreatePictureInfo(GetPictureById(pictureId.GetValueOrDefault())); + }, independent: true); - var url = _imageCache.GetImageUrl(cachedImage.Path, storeLocation); - return url; - } + return info; + } + + public PictureInfo GetPictureInfo(Picture picture) + { + if (picture == null) + return null; + + var cacheKey = MEDIACACHE_LOOKUP_KEY.FormatInvariant(picture.Id); + var info = _cacheManager.Get(cacheKey, () => + { + return CreatePictureInfo(picture); + }, independent: true); + + return info; + } - /// - /// Loads a picture from file - /// - /// Picture identifier - /// MIME type - /// Picture binary - protected virtual byte[] LoadPictureFromFile(int pictureId, string mimeType) + public virtual string GetUrl(int pictureId, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null) { - string filePath; - return LoadPictureFromFile(pictureId, mimeType, out filePath); - } + return GetUrl(GetPictureInfo(pictureId), targetSize, fallbackType, host); + } - private byte[] LoadPictureFromFile(int pictureId, string mimeType, out string filePath) + public virtual string GetUrl(Picture picture, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null) { - filePath = null; - - string lastPart = MimeTypes.MapMimeTypeToExtension(mimeType); - string fileName = string.Format("{0}-0.{1}", pictureId.ToString("0000000"), lastPart); - filePath = GetPictureLocalPath(fileName); - if (!File.Exists(filePath)) + return GetUrl(GetPictureInfo(picture), targetSize, fallbackType, host); + } + + public string GetUrl(PictureInfo info, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null) + { + string path = null; + string query = null; + + if (info?.Path != null) { - filePath = null; - return new byte[0]; + path = info.Path; + } + else if (fallbackType > FallbackPictureType.NoFallback) + { + path = String.Concat(_processedImagesRootPath, "0/", GetFallbackImageFileName(fallbackType)); } - return File.ReadAllBytes(filePath); - } - public virtual byte[] LoadPictureBinary(Picture picture) - { - return LoadPictureBinary(picture, this.StoreInDb); - } + if (path != null) + { + if (targetSize > 0) + { + // TBD: (mc) let pass query string as NameValueCollection (?) + query = "?size=" + targetSize; + } - public virtual byte[] LoadPictureBinary(Picture picture, bool fromDb) - { - if (picture == null) - throw new ArgumentNullException("picture"); - - byte[] result = null; - if (fromDb) - { - result = picture.PictureBinary; - } - else - { - result = LoadPictureFromFile(picture.Id, picture.MimeType); - } - - return result; - } + path = BuildUrlCore(path, query, host); + } - public virtual Size GetPictureSize(Picture picture) - { - byte[] pictureBinary = LoadPictureBinary(picture); - return GetPictureSize(pictureBinary); - } + return path; + } - internal Size GetPictureSize(byte[] pictureBinary) - { - if (pictureBinary == null || pictureBinary.Length == 0) - { - return new Size(); - } - - var stream = new MemoryStream(pictureBinary); - - Size size; - - try - { - using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) - { - size = ImageHeader.GetDimensions(reader); - } - } - catch (Exception) - { - // something went wrong with fast image access, - // so get original size the classic way - using (var b = new Bitmap(stream)) - { - size = new Size(b.Width, b.Height); - } - } - finally - { - stream.Dispose(); - } + public virtual string GetFallbackUrl(int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null) + { + return GetUrl((PictureInfo)null, targetSize, fallbackType, host); + } - return size; - } + /// + /// Creates a cacheable counterpart for a Picture object instance + /// + /// + /// + protected virtual PictureInfo CreatePictureInfo(Picture picture) + { + if (picture == null) + return null; - public virtual string GetPictureUrl( - int pictureId, - int targetSize = 0, - bool showDefaultPicture = true, - string storeLocation = null, - PictureType defaultPictureType = PictureType.Entity) - { - var picture = GetPictureById(pictureId); - return GetPictureUrl(picture, targetSize, showDefaultPicture, storeLocation, defaultPictureType); - } + var extension = MimeTypes.MapMimeTypeToExtension(picture.MimeType); - public virtual string GetPictureUrl( - Picture picture, - int targetSize = 0, - bool showDefaultPicture = true, - string storeLocation = null, - PictureType defaultPictureType = PictureType.Entity) - { - string url = string.Empty; - byte[] pictureBinary = null; - if (picture != null) - pictureBinary = LoadPictureBinary(picture); - if (picture == null || pictureBinary == null || pictureBinary.Length == 0) - { - if (showDefaultPicture) - { - url = GetDefaultPictureUrl(targetSize, defaultPictureType, storeLocation); - } - return url; - } - - if (picture.IsNew) - { - _imageCache.DeleteCachedImages(picture); - - // we do not validate picture binary here to ensure that no exception ("Parameter is not valid") will be thrown - picture = UpdatePicture(picture.Id, - pictureBinary, - picture.MimeType, - picture.SeoFilename, - false, - false); - } - - url = this.GetProcessedImageUrl( - pictureBinary, - picture.Id, - picture.SeoFilename, - MimeTypes.MapMimeTypeToExtension(picture.MimeType), - targetSize, - storeLocation); - - return url; - } + // Build virtual path with pattern "media/image/{id}/{SeoFileName}.{extension}" + var path = "{0}{1}/{2}.{3}".FormatInvariant( + _processedImagesRootPath, + picture.Id, + picture.SeoFilename.NullEmpty() ?? picture.Id.ToString(ImageCache.IdFormatString), + extension); - private ResizeSettings CreateResizeSettings(int targetSize) - { - var settings = new ResizeSettings(); - if (targetSize > 0) - { - settings.MaxWidth = targetSize; - settings.MaxHeight = targetSize; - } - - return settings; - } + // Do some maintenance stuff + EnsurePictureSizeResolved(picture, true); - /// - /// Get picture local path. Used when images stored on file system (not in the database) - /// - /// Filename - /// Local picture path - protected virtual string GetPictureLocalPath(string fileName) - { - var path = _mediaPath ?? (_mediaPath = _webHelper.MapPath("~/Media/")); - var filePath = Path.Combine(path, fileName); - return filePath; - } + if (picture.IsNew) + { + _imageCache.Delete(picture); + + // We do not validate picture binary here to ensure that no exception ("Parameter is not valid") will be thrown + UpdatePicture( + picture, + null, + picture.MimeType, + picture.SeoFilename, + false, + false); + } + + return new PictureInfo + { + Id = picture.Id, + MimeType = picture.MimeType, + Extension = extension, + Path = path, + Width = picture?.Width, + Height = picture?.Height, + }; + } - protected virtual string GetDefaultPictureLocalPath(string fileName) - { - var path = _imagesPath ?? (_imagesPath = _webHelper.MapPath("~/Content/Images")); - var filePath = Path.Combine(path, fileName); - return filePath; - } + protected virtual string BuildUrlCore(string virtualPath, string query, string host) + { + // TBD: (mc) No arg check because of performance?! - /// - /// Gets a picture - /// - /// Picture identifier - /// Picture - public virtual Picture GetPictureById(int pictureId) - { - if (pictureId == 0) - return null; + if (host == null) + { + host = _host; + } + else if (host == string.Empty) + { + host = _appPath; + } + else + { + host = host.EnsureEndsWith("/"); + } - var picture = _pictureRepository.GetById(pictureId); - return picture; - } + var sb = new StringBuilder(host, 100); - public virtual void DeletePicture(Picture picture) - { - if (picture == null) - throw new ArgumentNullException("picture"); + // Strip leading "/", the host/apppath has this already + if (virtualPath[0] == '/') + { + virtualPath = virtualPath.Substring(1); + } - // delete thumbs - _imageCache.DeleteCachedImages(picture); + // Append media path + sb.Append(virtualPath); - // delete from file system - if (!this.StoreInDb) - { - DeletePictureOnFileSystem(picture); - } + // Append query + if (query != null && query.Length > 0) + { + if (query[0] != '?') sb.Append("?"); + sb.Append(query); + } - // delete from database - _pictureRepository.Delete(picture); + return sb.ToString(); + } - // event notification - _eventPublisher.EntityDeleted(picture); - } + private void EnsurePictureSizeResolved(Picture picture, bool saveOnResolve) + { + if (picture.Width == null && picture.Height == null) + { + var mediaItem = picture.ToMedia(); + var stream = _storageProvider.Value.OpenRead(mediaItem); - public virtual IPagedList GetPictures(int pageIndex, int pageSize) - { - var query = from p in _pictureRepository.Table - orderby p.Id descending - select p; - var pics = new PagedList(query, pageIndex, pageSize); - return pics; - } + if (stream != null) + { + try + { + var size = ImageHeader.GetDimensions(stream, picture.MimeType, true); + picture.Width = size.Width; + picture.Height = size.Height; + picture.UpdatedOnUtc = DateTime.UtcNow; - public virtual IList GetPicturesByProductId(int productId, int recordsToReturn = 0) - { - if (productId == 0) - return new List(); + if (saveOnResolve) + { + try + { + _pictureRepository.Update(picture); + } + catch (InvalidOperationException ioe) + { + // Ignore exception for pictures that already have been processed. + if (!ioe.IsAlreadyAttachedEntityException()) + { + throw; + } + } + } + } + finally + { + stream.Dispose(); + } + } + } + } - var query = from p in _pictureRepository.Table - join pp in _productPictureRepository.Table on p.Id equals pp.PictureId - orderby pp.DisplayOrder - where pp.ProductId == productId - select p; + public int ClearUrlCache() + { + return _cacheManager.RemoveByPattern(MEDIACACHE_LOOKUP_KEY_PATTERN); + } - if (recordsToReturn > 0) - query = query.Take(recordsToReturn); + #endregion - var pics = query.ToList(); - return pics; - } + #region Metadata Storage - public virtual IList GetPicturesByIds(int[] pictureIds) + public virtual string GetPictureSeName(string name) { - Guard.ArgumentNotNull(() => pictureIds); + return SeoHelper.GetSeName(name, true, false, false); + } - var query = _pictureRepository.Table - .Where(x => pictureIds.Contains(x.Id)); + public virtual Picture SetSeoFilename(int pictureId, string seoFilename) + { + var picture = GetPictureById(pictureId); - return query.ToList(); + // update if it has been changed + if (picture != null && seoFilename != picture.SeoFilename) + { + UpdatePicture(picture, null, picture.MimeType, seoFilename, true, false); + } + + return picture; } - public virtual Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool isTransient = true, bool validateBinary = true) - { - mimeType = mimeType.EmptyNull(); - mimeType = mimeType.Truncate(20); + public virtual Picture GetPictureById(int pictureId) + { + if (pictureId == 0) + return null; - seoFilename = seoFilename.Truncate(100); + var picture = _pictureRepository.GetById(pictureId); + return picture; + } - if (validateBinary) - { - pictureBinary = ValidatePicture(pictureBinary); - } + public virtual IPagedList GetPictures(int pageIndex, int pageSize) + { + var query = from p in _pictureRepository.Table + orderby p.Id descending + select p; - var picture = _pictureRepository.Create(); - picture.PictureBinary = this.StoreInDb ? pictureBinary : new byte[0]; - picture.MimeType = mimeType; - picture.SeoFilename = seoFilename; - picture.IsNew = isNew; - picture.IsTransient = isTransient; - picture.UpdatedOnUtc = DateTime.UtcNow; + var pics = new PagedList(query, pageIndex, pageSize); + return pics; + } - _pictureRepository.Insert(picture); + public virtual IList GetPicturesByProductId(int productId, int recordsToReturn = 0) + { + if (productId == 0) + return new List(); - if (!this.StoreInDb) - { - SavePictureInFile(picture.Id, pictureBinary, mimeType); - } + var query = from p in _pictureRepository.Table + join pp in _productPictureRepository.Table on p.Id equals pp.PictureId + orderby pp.DisplayOrder + where pp.ProductId == productId + select p; - //event notification - _eventPublisher.EntityInserted(picture); + if (recordsToReturn > 0) + query = query.Take(() => recordsToReturn); - return picture; - } + var pics = query.ToList(); + return pics; + } - public virtual Picture UpdatePicture(int pictureId, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true) - { - mimeType = mimeType.EmptyNull().Truncate(20); - seoFilename = seoFilename.Truncate(100); + public virtual Multimap GetPicturesByProductIds(int[] productIds, int? maxPicturesPerProduct = null, bool withBlobs = false) + { + Guard.NotNull(productIds, nameof(productIds)); - if (validateBinary) - { - pictureBinary = ValidatePicture(pictureBinary); - } - - var picture = GetPictureById(pictureId); - if (picture == null) - return null; - - //delete old thumbs if a picture has been changed - if (seoFilename != picture.SeoFilename) - { - _imageCache.DeleteCachedImages(picture); - } - - picture.PictureBinary = (this.StoreInDb ? pictureBinary : new byte[0]); - picture.MimeType = mimeType; - picture.SeoFilename = seoFilename; - picture.IsNew = isNew; - picture.UpdatedOnUtc = DateTime.UtcNow; + if (maxPicturesPerProduct.HasValue) + { + Guard.IsPositive(maxPicturesPerProduct.Value, nameof(maxPicturesPerProduct)); + } - _pictureRepository.Update(picture); + var map = new Multimap(); - if (!this.StoreInDb) - { - SavePictureInFile(picture.Id, pictureBinary, mimeType); - } + if (!productIds.Any()) + return map; - //event notification - _eventPublisher.EntityUpdated(picture); + int take = maxPicturesPerProduct ?? int.MaxValue; - return picture; - } + var query = from pp in _productPictureRepository.TableUntracked + where productIds.Contains(pp.ProductId) + group pp by pp.ProductId into g + select new + { + ProductId = g.Key, + Pictures = g.OrderBy(x => x.DisplayOrder) + .Take(take) + .Select(x => new { x.PictureId, x.ProductId }) + }; - public virtual Picture SetSeoFilename(int pictureId, string seoFilename) - { - var picture = GetPictureById(pictureId); - if (picture == null) - throw new ArgumentException("No picture found with the specified id"); - - // update if it has been changed - if (seoFilename != picture.SeoFilename) - { - picture = UpdatePicture(picture.Id, LoadPictureBinary(picture), picture.MimeType, seoFilename, true, false); - } - return picture; - } + var groupingResult = query.ToDictionary(x => x.ProductId, x => x.Pictures); - #endregion + using (var scope = new DbContextScope(ctx: _pictureRepository.Context, forceNoTracking: null)) + { + // EF doesn't support eager loading with grouped queries. We must hack a little bit. + var pictureIds = groupingResult.SelectMany(x => x.Value).Select(x => x.PictureId).Distinct().ToArray(); + var pictures = GetPicturesByIds(pictureIds, withBlobs).ToDictionarySafe(x => x.Id); - #region Properties + foreach (var p in groupingResult.SelectMany(x => x.Value)) + { + if (pictures.ContainsKey(p.PictureId)) + { + map.Add(p.ProductId, pictures[p.PictureId]); + } + } + } - public virtual bool StoreInDb - { - get - { - return _settingService.GetSettingByKey("Media.Images.StoreInDB", true); - } - set - { - // check whether the value was changed - if (this.StoreInDb != value) - { - // save the new setting value - _settingService.SetSetting("Media.Images.StoreInDB", value); + return map; + } - // move them all - MovePictures(value); - } - } - } + public virtual IList GetPicturesByIds(int[] pictureIds, bool withBlobs = false) + { + Guard.NotNull(pictureIds, nameof(pictureIds)); + + var query = _pictureRepository.Table + .Where(x => pictureIds.Contains(x.Id)); - protected int MovePictures(bool toDb) + if (withBlobs) + { + query = query.Include(x => x.MediaStorage); + } + + return query.ToList(); + } + + public virtual void DeletePicture(Picture picture) { - // long running operation, therefore some code chunks are redundant here in order to boost performance + Guard.NotNull(picture, nameof(picture)); - // a list of ALL file paths that were either deleted or created - var affectedFiles = new List(1000); + // delete thumbs + _imageCache.Delete(picture); - var ctx = _pictureRepository.Context; - var failed = false; - int i = 0; + // delete from url cache + _cacheManager.Remove(MEDIACACHE_LOOKUP_KEY.FormatInvariant(picture.Id)); - using (var scope = new DbContextScope(ctx: ctx, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) - { - using (var tx = ctx.BeginTransaction()) - { - // we are about to process data in chunks but want to commit ALL at once when ALL chunks have been processed successfully. - try - { - int pageIndex = 0; - IPagedList pictures = null; + // delete from storage + _storageProvider.Value.Remove(picture.ToMedia()); - do - { - if (pictures != null) - { - // detach all entities from previous page to save memory - ctx.DetachEntities(pictures); + // delete entity + _pictureRepository.Delete(picture); + } - // breathe - pictures.Clear(); - pictures = null; - } + public virtual Picture InsertPicture( + byte[] pictureBinary, + string mimeType, + string seoFilename, + bool isNew, + int width, + int height, + bool isTransient = true) + { + var picture = _pictureRepository.Create(); + picture.MimeType = mimeType.EmptyNull().Truncate(20); + picture.SeoFilename = seoFilename.Truncate(100); + picture.IsNew = isNew; + picture.IsTransient = isTransient; + picture.UpdatedOnUtc = DateTime.UtcNow; - // load max 500 picture entities at once - pictures = this.GetPictures(pageIndex, 500); - pageIndex++; + if (width > 0 && height > 0) + { + picture.Width = width; + picture.Height = height; + } - foreach (var picture in pictures) - { - string filePath = null; + _pictureRepository.Insert(picture); - if (!toDb) - { - if (picture.PictureBinary != null && picture.PictureBinary.Length > 0) - { - // save picture as file - SavePictureInFile(picture.Id, picture.PictureBinary, picture.MimeType, out filePath); - } - // remove picture binary from DB - picture.PictureBinary = new byte[0]; - } - else - { - // load picture binary from file and set in DB - var picBinary = LoadPictureFromFile(picture.Id, picture.MimeType, out filePath); - if (picBinary.Length > 0) - { - picture.PictureBinary = picBinary; - } - } + // Save to storage. + _storageProvider.Value.Save(picture.ToMedia(), pictureBinary); - // remember file path: we must be able to rollback IO operations on transaction failure - if (filePath.HasValue()) - { - affectedFiles.Add(filePath); - //picture.IsNew = true; - } + return picture; + } - // explicitly attach modified entity to context, because we disabled AutoCommit - picture.UpdatedOnUtc = DateTime.UtcNow; - _pictureRepository.Update(picture); + public virtual Picture InsertPicture( + byte[] pictureBinary, + string mimeType, + string seoFilename, + bool isNew, + bool isTransient = true, + bool validateBinary = true) + { + var size = Size.Empty; - i++; - } + if (validateBinary) + { + pictureBinary = ValidatePicture(pictureBinary, mimeType, out size); + } - // save the current batch to DB - ctx.SaveChanges(); + return InsertPicture(pictureBinary, mimeType, seoFilename, isNew, size.Width, size.Height, isTransient); + } - } while (pictures.HasNextPage); + public virtual void UpdatePicture( + Picture picture, + byte[] pictureBinary, + string mimeType, + string seoFilename, + bool isNew, + bool validateBinary = true) + { + if (picture == null) + return; - // FIRE! - tx.Commit(); - } - catch (Exception ex) - { - failed = true; - tx.Rollback(); - _settingService.SetSetting("Media.Images.StoreInDB", !toDb); - _notifier.Error(ex.Message); - _logger.Error(ex); - } - } + mimeType = mimeType.EmptyNull().Truncate(20); + seoFilename = seoFilename.Truncate(100); + + var size = Size.Empty; + + if (validateBinary && pictureBinary != null) + { + pictureBinary = ValidatePicture(pictureBinary, mimeType, out size); } - if (affectedFiles.Count > 0) + // delete old thumbs if a picture has been changed + if (seoFilename != picture.SeoFilename) { - if ((toDb && !failed) || (!toDb && failed)) - { - // FS > DB sucessful OR DB > FS failed: delete all physical files - // run a background task for the deletion of files (fire & forget) - Task.Factory.StartNew(state => - { - var files = state as string[]; - foreach (var path in files) - { - if (File.Exists(path)) - File.Delete(path); - } - }, affectedFiles.ToArray()).ConfigureAwait(false); - } + _imageCache.Delete(picture); - // shrink database (only when DB > FS and success) - if (!toDb && !failed) - { - ctx.ShrinkDatabase(); - } + // delete from url cache + _cacheManager.Remove(MEDIACACHE_LOOKUP_KEY.FormatInvariant(picture.Id)); } - return i; + picture.MimeType = mimeType; + picture.SeoFilename = seoFilename; + picture.IsNew = isNew; + picture.UpdatedOnUtc = DateTime.UtcNow; + + if (!size.IsEmpty) + { + picture.Width = size.Width; + picture.Height = size.Height; + } + + _pictureRepository.Update(picture); + + // save to storage + if (pictureBinary != null) + { + _storageProvider.Value.Save(picture.ToMedia(), pictureBinary); + } } - #endregion - } + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs b/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs new file mode 100644 index 0000000000..d16095ff8c --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs @@ -0,0 +1,47 @@ +using System; + +namespace SmartStore.Services.Media +{ + public sealed class ProcessImageException : Exception + { + public ProcessImageException() + : this((string)null, null) + { + } + + public ProcessImageException(ProcessImageQuery query) + : this(query, null) + { + } + + public ProcessImageException(string message, ProcessImageQuery query) + : base(message) + { + Query = query; + } + + public ProcessImageException(ProcessImageQuery query, Exception innerException) + : base(CreateMessage(query, innerException), innerException) + { + Query = query; + } + + private static string CreateMessage(ProcessImageQuery query, Exception innerException) + { + var fileName = query?.FileName; + + var msg = fileName.HasValue() + ? "Error while processing image '{0}'".FormatCurrent(fileName) + : "Error while processing image"; + + if (innerException != null) + { + 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 new file mode 100644 index 0000000000..9b3c5998e2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs @@ -0,0 +1,224 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.IO; +using System.Collections.Specialized; +using System.Text; +using SmartStore.Collections; +using System.Drawing; +using ImageProcessor.Imaging.Formats; + +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()) + { + } + + public ProcessImageQuery(byte[] source) + : this(source, new NameValueCollection()) + { + } + + public ProcessImageQuery(Stream source) + : this(source, new NameValueCollection()) + { + } + + public ProcessImageQuery(Image source) + : this(source, new NameValueCollection()) + { + } + + public ProcessImageQuery(string source) + : this(source, new NameValueCollection()) + { + } + + public ProcessImageQuery(object source, NameValueCollection query) + : base(SanitizeCollection(query)) + { + Guard.NotNull(query, nameof(query)); + + Source = source; + DisposeSource = true; + Notify = true; + } + + public ProcessImageQuery(ProcessImageQuery query) + : base(SanitizeCollection(query)) + { + Guard.NotNull(query, nameof(query)); + + Source = query.Source; + Format = query.Format; + 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. + /// + public object Source { get; set; } + + public string FileName { get; set; } + + /// + /// Whether to dispose the source stream after resizing completes + /// + public bool DisposeSource { get; set; } + + /// + /// Whether to execute an applicable post processor which + /// can reduce the resulting file size drastically, but also + /// can slow down processing time. + /// + public bool ExecutePostProcessor { get; set; } + + public int? MaxWidth + { + get { return Get("w"); } + set { Set("w", value); } + } + + public int? MaxHeight + { + get { return Get("h"); } + set { Set("h", value); } + } + + public int? Quality + { + get { return Get("q"); } + set { Set("q", value); } + } + + /// + /// 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. + /// When format is not specified, the original format of the source image is used (unless it is not a web safe format - jpeg is the fallback in that scenario). + /// + public object Format { get; set; } + + public bool IsValidationMode { get; set; } + + public bool Notify { get; set; } + + private T Get(string name) + { + return base[name].Convert(); + } + + private void Set(string name, T val) + { + if (val == null) + base.Remove(name); + else + base.Add(name, val.Convert(), true); + } + + + public bool NeedsProcessing(bool ignoreQualityFlag = false) + { + 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() + { + var sb = new StringBuilder(); + + foreach (var key in base.AllKeys) + { + if (key == "m" && base["m"] == "max") + continue; // Mode 'max' is default and can be omitted + + sb.Append("-"); + sb.Append(key); + sb.Append(base[key]); + } + + return sb.ToString(); + } + + public string GetResultExtension() + { + if (Format == null) + { + return null; + } + else if (Format is ISupportedImageFormat) + { + return ((ISupportedImageFormat)Format).DefaultExtension; + } + else if (Format is string) + { + return (string)Format; + } + + return null; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs b/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs new file mode 100644 index 0000000000..9ff224d143 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; + +namespace SmartStore.Services.Media +{ + public class ProcessImageResult : DisposableObject + { + public ProcessImageQuery Query { get; set; } + + public MemoryStream OutputStream { get; set; } + + 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) + { + if (disposing && OutputStream != null) + { + OutputStream.Dispose(); + OutputStream = null; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/DatabaseMediaStorageProvider.cs b/src/Libraries/SmartStore.Services/Media/Storage/DatabaseMediaStorageProvider.cs new file mode 100644 index 0000000000..32c4cae971 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/DatabaseMediaStorageProvider.cs @@ -0,0 +1,161 @@ +using System.IO; +using System.Threading.Tasks; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Media.Storage +{ + [SystemName("MediaStorage.SmartStoreDatabase")] + [FriendlyName("Database")] + [DisplayOrder(0)] + public class DatabaseMediaStorageProvider : IMediaStorageProvider, ISupportsMediaMoving + { + private readonly IDbContext _dbContext; + private readonly IRepository _mediaStorageRepository; + + public DatabaseMediaStorageProvider( + IDbContext dbContext, + IRepository mediaStorageRepository) + { + _dbContext = dbContext; + _mediaStorageRepository = mediaStorageRepository; + } + + public static string SystemName + { + get { return "MediaStorage.SmartStoreDatabase"; } + } + + public Stream OpenRead(MediaItem media) + { + Guard.NotNull(media, nameof(media)); + + return media.Entity?.MediaStorage?.Data?.ToStream(); + } + + public byte[] Load(MediaItem media) + { + Guard.NotNull(media, nameof(media)); + + return media.Entity?.MediaStorage?.Data ?? new byte[0]; + } + + public Task LoadAsync(MediaItem media) + { + return Task.FromResult(Load(media)); + } + + public void Save(MediaItem media, byte[] data) + { + Guard.NotNull(media, nameof(media)); + + if (data == null || data.LongLength == 0) + { + // remove media storage if any + if ((media.Entity.MediaStorageId ?? 0) != 0 && media.Entity != null && media.Entity.MediaStorage != null) + { + _mediaStorageRepository.Delete(media.Entity.MediaStorage); + } + } + else + { + if (media.Entity.MediaStorage == null) + { + // entity has no media storage -> insert + var newStorage = new MediaStorage { Data = data }; + + _mediaStorageRepository.Insert(newStorage); + + if (newStorage.Id == 0) + { + // actually we should never get here + _dbContext.SaveChanges(); + } + + media.Entity.MediaStorageId = newStorage.Id; + + //// Required because during import the ChangeTracker doesn't treat media.Entity as changed entry. + //_dbContext.ChangeState((BaseEntity)media.Entity, System.Data.Entity.EntityState.Modified); + + _dbContext.SaveChanges(); + } + else + { + // update existing media storage + media.Entity.MediaStorage.Data = data; + + _mediaStorageRepository.Update(media.Entity.MediaStorage); + } + } + } + + public Task SaveAsync(MediaItem media, byte[] data) + { + Save(media, data); + return Task.FromResult(0); + } + + public void Remove(params MediaItem[] medias) + { + foreach (var media in medias) + { + if ((media.Entity.MediaStorageId ?? 0) != 0) + { + // this also nulls media.Entity.MediaStorageId + _mediaStorageRepository.Delete(media.Entity.MediaStorage); + } + } + } + + + public void MoveTo(ISupportsMediaMoving target, MediaMoverContext context, MediaItem media) + { + Guard.NotNull(target, nameof(target)); + Guard.NotNull(context, nameof(context)); + Guard.NotNull(media, nameof(media)); + + if (media.Entity.MediaStorage != null) + { + // let target store data (into a file for example) + target.Receive(context, media, media.Entity.MediaStorage.Data); + + // remove picture binary from DB + try + { + _mediaStorageRepository.Delete(media.Entity.MediaStorage); + } + catch { } + + media.Entity.MediaStorageId = null; + + context.ShrinkDatabase = true; + } + } + + public void Receive(MediaMoverContext context, MediaItem media, byte[] data) + { + Guard.NotNull(context, nameof(context)); + Guard.NotNull(media, nameof(media)); + + // store data for later bulk commit + if (data != null && data.LongLength > 0) + { + // requires autoDetectChanges set to true or remove explicit entity detaching + media.Entity.MediaStorage = new MediaStorage { Data = data }; + } + } + + public Task ReceiveAsync(MediaMoverContext context, MediaItem media, byte[] data) + { + Receive(context, media, data); + return Task.FromResult(0); + } + + public void OnCompleted(MediaMoverContext context, bool succeeded) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/FileSystemMediaStorageProvider.cs b/src/Libraries/SmartStore.Services/Media/Storage/FileSystemMediaStorageProvider.cs new file mode 100644 index 0000000000..f7f3acc322 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/FileSystemMediaStorageProvider.cs @@ -0,0 +1,199 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.IO; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Media.Storage +{ + [SystemName("MediaStorage.SmartStoreFileSystem")] + [FriendlyName("File system")] + [DisplayOrder(1)] + public class FileSystemMediaStorageProvider : IMediaStorageProvider, ISupportsMediaMoving + { + private readonly IMediaFileSystem _fileSystem; + + public FileSystemMediaStorageProvider(IMediaFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + public static string SystemName + { + get { return "MediaStorage.SmartStoreFileSystem"; } + } + + protected string GetPath(MediaItem media) + { + var fileName = media.GetFileName(); + + var picture = media.Entity as Picture; + if (picture != null) + { + var subfolder = fileName.Substring(0, ImageCache.MaxDirLength); + fileName = _fileSystem.Combine(subfolder, fileName); + } + + return _fileSystem.Combine(media.Path, fileName); + } + + public Stream OpenRead(MediaItem media) + { + var file = _fileSystem.GetFile(GetPath(media)); + + return file.Exists ? file.OpenRead() : null; + } + + public byte[] Load(MediaItem media) + { + Guard.NotNull(media, nameof(media)); + + var filePath = GetPath(media); + return _fileSystem.ReadAllBytes(filePath) ?? new byte[0]; + } + + public async Task LoadAsync(MediaItem media) + { + Guard.NotNull(media, nameof(media)); + + var filePath = GetPath(media); + return (await _fileSystem.ReadAllBytesAsync(filePath)) ?? new byte[0]; + } + + public void Save(MediaItem media, byte[] data) + { + Guard.NotNull(media, nameof(media)); + + // TODO: (?) if the new file extension differs from the old one then the old file never gets deleted + + var filePath = GetPath(media); + + if (data != null && data.LongLength != 0) + { + _fileSystem.WriteAllBytes(filePath, data); + } + else if (_fileSystem.FileExists(filePath)) + { + _fileSystem.DeleteFile(filePath); + } + } + + public async Task SaveAsync(MediaItem media, byte[] data) + { + Guard.NotNull(media, nameof(media)); + + // TODO: (?) if the new file extension differs from the old one then the old file never gets deleted + + var filePath = GetPath(media); + + if (data != null && data.LongLength != 0) + { + await _fileSystem.WriteAllBytesAsync(filePath, data); + } + else if (_fileSystem.FileExists(filePath)) + { + _fileSystem.DeleteFile(filePath); + } + } + + public void Remove(params MediaItem[] medias) + { + foreach (var media in medias) + { + var filePath = GetPath(media); + _fileSystem.DeleteFile(filePath); + } + } + + + public void MoveTo(ISupportsMediaMoving target, MediaMoverContext context, MediaItem media) + { + Guard.NotNull(target, nameof(target)); + Guard.NotNull(context, nameof(context)); + Guard.NotNull(media, nameof(media)); + + var filePath = GetPath(media); + + try + { + // read data from file + var data = _fileSystem.ReadAllBytes(filePath); + + // let target store data (into database for example) + target.Receive(context, media, data); + + // remember file path: we must be able to rollback IO operations on transaction failure + context.AffectedFiles.Add(filePath); + } + catch (Exception exception) + { + Debug.WriteLine(exception.Message); + } + } + + public void Receive(MediaMoverContext context, MediaItem media, byte[] data) + { + Guard.NotNull(context, nameof(context)); + Guard.NotNull(media, nameof(media)); + + // store data into file + if (data != null && data.LongLength != 0) + { + var filePath = GetPath(media); + + if (!_fileSystem.FileExists(filePath)) + { + // TBD: (mc) We only save the file if it doesn't exist yet. + // This should save time and bandwidth in the case where the target + // is a cloud based file system (like Azure BLOB). + // In such a scenario it'd be advisable to copy the files manually + // with other - maybe more performant - tools before performing the provider switch. + _fileSystem.WriteAllBytes(filePath, data); + context.AffectedFiles.Add(filePath); + } + } + } + + public async Task ReceiveAsync(MediaMoverContext context, MediaItem media, byte[] data) + { + Guard.NotNull(context, nameof(context)); + Guard.NotNull(media, nameof(media)); + + // store data into file + if (data != null && data.LongLength != 0) + { + var filePath = GetPath(media); + + await _fileSystem.WriteAllBytesAsync(filePath, data); + + context.AffectedFiles.Add(filePath); + } + } + + public void OnCompleted(MediaMoverContext context, bool succeeded) + { + if (context.AffectedFiles.Any()) + { + var toFileSystem = context.TargetSystemName.IsCaseInsensitiveEqual(SystemName); + + if ((!toFileSystem && succeeded) || (toFileSystem && !succeeded)) + { + // FS > DB sucessful OR DB > FS failed: delete all physical files + // run a background task for the deletion of files (fire & forget) + + Task.Factory.StartNew(state => + { + var files = state as string[]; + foreach (var file in files) + { + _fileSystem.DeleteFile(file); + } + }, context.AffectedFiles.ToArray()).ConfigureAwait(false); + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/IMediaMover.cs b/src/Libraries/SmartStore.Services/Media/Storage/IMediaMover.cs new file mode 100644 index 0000000000..3283aefe32 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/IMediaMover.cs @@ -0,0 +1,15 @@ +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Media.Storage +{ + public interface IMediaMover + { + /// + /// Moves media items from one storage provider to another + /// + /// The source media storage provider + /// The target media storage provider + /// true success, failure + bool Move(Provider sourceProvider, Provider targetProvider); + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/IMediaStorageProvider.cs b/src/Libraries/SmartStore.Services/Media/Storage/IMediaStorageProvider.cs new file mode 100644 index 0000000000..36302f7b38 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/IMediaStorageProvider.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Threading.Tasks; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Media.Storage +{ + public interface IMediaStorageProvider : IProvider + { + /// + /// Opens the media item for reading + /// + /// Media storage item + Stream OpenRead(MediaItem media); + + /// + /// Loads media item data + /// + /// Media storage item + byte[] Load(MediaItem media); + + /// + /// Asynchronously loads media item data + /// + /// Media storage item + Task LoadAsync(MediaItem media); + + /// + /// Saves media item data + /// + /// Media storage item + /// New binary data + void Save(MediaItem media, byte[] data); + + /// + /// Asynchronously saves media item data + /// + /// Media storage item + /// New binary data + Task SaveAsync(MediaItem media, byte[] data); + + /// + /// Remove media storage item(s) + /// + /// Media storage items + void Remove(params MediaItem[] medias); + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/ISupportsMediaMoving.cs b/src/Libraries/SmartStore.Services/Media/Storage/ISupportsMediaMoving.cs new file mode 100644 index 0000000000..e21f745e74 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/ISupportsMediaMoving.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; + +namespace SmartStore.Services.Media.Storage +{ + public interface ISupportsMediaMoving + { + /// + /// Moves a media item to the target provider + /// + /// Target provider + /// Media storage mover context + /// Media storage item + void MoveTo(ISupportsMediaMoving target, MediaMoverContext context, MediaItem media); + + /// + /// Data received by the source provider to be stored by the target provider + /// + /// Media storage mover context + /// Media storage item + /// Binary data + void Receive(MediaMoverContext context, MediaItem media, byte[] data); + + /// + /// Data received by the source provider to be stored by the target provider (async) + /// + /// Media storage mover context + /// Media storage item + /// Binary data + Task ReceiveAsync(MediaMoverContext context, MediaItem media, byte[] data); + + /// + /// Called when batch media moving completes + /// + /// Media storage mover context + /// Whether media moving succeeded + void OnCompleted(MediaMoverContext context, bool succeeded); + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/MediaItem.cs b/src/Libraries/SmartStore.Services/Media/Storage/MediaItem.cs new file mode 100644 index 0000000000..d71c20df3b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/MediaItem.cs @@ -0,0 +1,27 @@ +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Services.Media.Storage +{ + public partial class MediaItem + { + /// + /// Entity of the media storage item + /// + public IHasMedia Entity { get; set; } + + /// + /// Storage path + /// + public string Path { get; set; } + + /// + /// Mime type + /// + public string MimeType { get; set; } + + /// + /// File extension + /// + public string FileExtension { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/MediaMover.cs b/src/Libraries/SmartStore.Services/Media/Storage/MediaMover.cs new file mode 100644 index 0000000000..825f871858 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/MediaMover.cs @@ -0,0 +1,173 @@ +using System; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Media.Storage +{ + public class MediaMover : IMediaMover + { + private const int PAGE_SIZE = 100; + + private readonly IRepository _pictureRepository; + private readonly IRepository _downloadRepository; + private readonly IRepository _queuedEmailAttachmentRepository; + private readonly ICommonServices _services; + private readonly ILogger _logger; + + public MediaMover( + IRepository pictureRepository, + IRepository downloadRepository, + IRepository queuedEmailAttachmentRepository, + ICommonServices services, + ILogger logger) + { + _pictureRepository = pictureRepository; + _downloadRepository = downloadRepository; + _queuedEmailAttachmentRepository = queuedEmailAttachmentRepository; + _services = services; + _logger = logger; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + protected virtual void PageEntities(IOrderedQueryable query, Action moveEntity) where TEntity : BaseEntity, IHasMedia + { + var pageIndex = 0; + IPagedList entities = null; + + do + { + if (entities != null) + { + // detach all entities from previous page to save memory + _services.DbContext.DetachEntities(entities); + entities.Clear(); + entities = null; + } + + // load max 100 entities at once + entities = new PagedList(query, pageIndex++, PAGE_SIZE); + + entities.Each(x => moveEntity(x)); + + // save the current batch to database + _services.DbContext.SaveChanges(); + } + while (entities.HasNextPage); + } + + public virtual bool Move(Provider sourceProvider, Provider targetProvider) + { + Guard.NotNull(sourceProvider, nameof(sourceProvider)); + Guard.NotNull(targetProvider, nameof(targetProvider)); + + var success = false; + var utcNow = DateTime.UtcNow; + var context = new MediaMoverContext(sourceProvider.Metadata.SystemName, targetProvider.Metadata.SystemName); + + var source = sourceProvider.Value as ISupportsMediaMoving; + var target = targetProvider.Value as ISupportsMediaMoving; + + // source and target must support media storage moving + if (source == null) + { + throw new ArgumentException(T("Admin.Media.StorageMovingNotSupported", sourceProvider.Metadata.SystemName)); + } + + if (target == null) + { + throw new ArgumentException(T("Admin.Media.StorageMovingNotSupported", targetProvider.Metadata.SystemName)); + } + + // source and target provider must not be equal + if (sourceProvider.Metadata.SystemName.IsCaseInsensitiveEqual(targetProvider.Metadata.SystemName)) + { + throw new ArgumentException(T("Admin.Media.CannotMoveToSameProvider")); + } + + // we are about to process data in chunks but want to commit ALL at once when ALL chunks have been processed successfully. + // autoDetectChanges true required for newly inserted binary data. + using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: true, proxyCreation: false, validateOnSave: false, autoCommit: false)) + using (var transaction = _services.DbContext.BeginTransaction()) + { + try + { + // pictures + var queryPictures = _pictureRepository.Table + .Expand(x => x.MediaStorage) + .OrderBy(x => x.Id); + + PageEntities(queryPictures, picture => + { + // move item from source to target + source.MoveTo(target, context, picture.ToMedia()); + + picture.UpdatedOnUtc = utcNow; + ++context.MovedItems; + }); + + // downloads + var queryDownloads = _downloadRepository.Table + .Expand(x => x.MediaStorage) + .OrderBy(x => x.Id); + + PageEntities(queryDownloads, download => + { + source.MoveTo(target, context, download.ToMedia()); + + download.UpdatedOnUtc = utcNow; + ++context.MovedItems; + }); + + // queued email attachments + var queryQueuedEmailAttachments = _queuedEmailAttachmentRepository.Table + .Expand(x => x.MediaStorage) + .Where(x => x.StorageLocation == EmailAttachmentStorageLocation.Blob) + .OrderBy(x => x.Id); + + PageEntities(queryQueuedEmailAttachments, attachment => + { + source.MoveTo(target, context, attachment.ToMedia()); + + ++context.MovedItems; + }); + + transaction.Commit(); + success = true; + } + catch (Exception exception) + { + success = false; + transaction.Rollback(); + + _services.Notifier.Error(exception.Message); + _logger.Error(exception); + } + } + + if (success) + { + _services.Settings.SetSetting("Media.Storage.Provider", targetProvider.Metadata.SystemName); + } + + // inform both provider about ending + source.OnCompleted(context, success); + target.OnCompleted(context, success); + + if (success && context.ShrinkDatabase) + { + _services.DbContext.ShrinkDatabase(); + } + + return success; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/MediaMoverContext.cs b/src/Libraries/SmartStore.Services/Media/Storage/MediaMoverContext.cs new file mode 100644 index 0000000000..f0d8fc8068 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/MediaMoverContext.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; + +namespace SmartStore.Services.Media.Storage +{ + public class MediaMoverContext + { + internal MediaMoverContext(string sourceSystemName, string targetSystemName) + { + SourceSystemName = sourceSystemName; + TargetSystemName = targetSystemName; + AffectedFiles = new List(); + CustomProperties = new Dictionary(); + } + + /// + /// The system name of the source provider + /// + public string SourceSystemName { get; private set; } + + /// + /// The system name of the target provider + /// + public string TargetSystemName { get; private set; } + + /// + /// Current number of moved media items + /// + public int MovedItems { get; internal set; } + + /// + /// Paths of affected media files + /// + public List AffectedFiles { get; private set; } + + /// + /// Whether to shrink database after succesful moving + /// + public bool ShrinkDatabase { get; set; } + + /// + /// Use this dictionary for any custom data required along the move operation + /// + public Dictionary CustomProperties { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Media/Storage/MediaStorageExtensions.cs b/src/Libraries/SmartStore.Services/Media/Storage/MediaStorageExtensions.cs new file mode 100644 index 0000000000..eaca77dd54 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Media/Storage/MediaStorageExtensions.cs @@ -0,0 +1,96 @@ +using System.IO; +using SmartStore.Core; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.IO; + +namespace SmartStore.Services.Media.Storage +{ + public static class MediaStorageExtensions + { + /// + /// Converts a picture entity into a media storage item + /// + /// Picture entity + /// Media storage item + public static MediaItem ToMedia(this Picture picture) + { + Guard.NotNull(picture, nameof(picture)); + + var media = new MediaItem + { + Entity = picture, + Path = "Storage", + MimeType = picture.MimeType + }; + + return media; + } + + /// + /// Converts a download entity into a media storage item + /// + /// Download entity + /// Media storage item + public static MediaItem ToMedia(this Download download) + { + Guard.NotNull(download, nameof(download)); + + var media = new MediaItem + { + Entity = download, + MimeType = download.ContentType, + FileExtension = download.Extension, + Path = "Downloads" + }; + + return media; + } + + /// + /// Converts a queued email attachment entity into a media storage item + /// + /// Queued email attachment + /// Media storage item + public static MediaItem ToMedia(this QueuedEmailAttachment attachment) + { + Guard.NotNull(attachment, nameof(attachment)); + + var media = new MediaItem + { + Entity = attachment, + MimeType = attachment.MimeType, + FileExtension = Path.GetExtension(attachment.Name), + Path = "QueuedEmailAttachment" + }; + + return media; + } + + /// + /// Create a file name for a media item + /// + /// Media storage item + /// File name including extension + public static string GetFileName(this MediaItem media) + { + if (media != null) + { + var extension = media.FileExtension; + + if (extension.IsEmpty()) + extension = MimeTypes.MapMimeTypeToExtension(media.MimeType); + + var baseEntity = media.Entity as BaseEntity; + + var fileName = string.Concat( + baseEntity.Id.ToString(ImageCache.IdFormatString), + extension.EmptyNull().EnsureStartsWith(".")); + + return fileName; + } + + return null; + } + } +} 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/CampaignService.cs b/src/Libraries/SmartStore.Services/Messages/CampaignService.cs index 0db4388dc7..ec574a44dc 100644 --- a/src/Libraries/SmartStore.Services/Messages/CampaignService.cs +++ b/src/Libraries/SmartStore.Services/Messages/CampaignService.cs @@ -3,95 +3,67 @@ using System.Linq; using SmartStore.Core.Data; using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Events; using SmartStore.Services.Customers; -using SmartStore.Core; using SmartStore.Core.Email; using SmartStore.Services.Stores; +using SmartStore.Templating; +using SmartStore.Core.Localization; namespace SmartStore.Services.Messages { public partial class CampaignService : ICampaignService { - private readonly IRepository _campaignRepository; - private readonly IEmailSender _emailSender; - private readonly IMessageTokenProvider _messageTokenProvider; - private readonly ITokenizer _tokenizer; + private readonly ICommonServices _services; + private readonly IRepository _campaignRepository; + private readonly IMessageTemplateService _messageTemplateService; + private readonly IEmailSender _emailSender; private readonly IQueuedEmailService _queuedEmailService; private readonly ICustomerService _customerService; - private readonly IStoreContext _storeContext; private readonly IStoreMappingService _storeMappingService; - private readonly IEventPublisher _eventPublisher; - public CampaignService(IRepository campaignRepository, - IEmailSender emailSender, IMessageTokenProvider messageTokenProvider, - ITokenizer tokenizer, IQueuedEmailService queuedEmailService, + public CampaignService( + ICommonServices services, + IRepository campaignRepository, + IMessageTemplateService messageTemplateService, + IEmailSender emailSender, + IQueuedEmailService queuedEmailService, ICustomerService customerService, - IStoreContext storeContext, - IStoreMappingService storeMappingService, - IEventPublisher eventPublisher) + IStoreMappingService storeMappingService) { - this._campaignRepository = campaignRepository; - this._emailSender = emailSender; - this._messageTokenProvider = messageTokenProvider; - this._tokenizer = tokenizer; - this._queuedEmailService = queuedEmailService; - this._customerService = customerService; - this._storeContext = storeContext; - this._storeMappingService = storeMappingService; - this._eventPublisher = eventPublisher; + _services = services; + _campaignRepository = campaignRepository; + _messageTemplateService = messageTemplateService; + _emailSender = emailSender; + _queuedEmailService = queuedEmailService; + _customerService = customerService; + _storeMappingService = storeMappingService; + + T = NullLocalizer.Instance; } - /// - /// Inserts a campaign - /// - /// Campaign + public Localizer T { get; set; } + public virtual void InsertCampaign(Campaign campaign) { - if (campaign == null) - throw new ArgumentNullException("campaign"); + Guard.NotNull(campaign, nameof(campaign)); _campaignRepository.Insert(campaign); - - //event notification - _eventPublisher.EntityInserted(campaign); } - /// - /// Updates a campaign - /// - /// Campaign public virtual void UpdateCampaign(Campaign campaign) { - if (campaign == null) - throw new ArgumentNullException("campaign"); - - _campaignRepository.Update(campaign); + Guard.NotNull(campaign, nameof(campaign)); - //event notification - _eventPublisher.EntityUpdated(campaign); + _campaignRepository.Update(campaign); } - /// - /// Deleted a queued email - /// - /// Campaign public virtual void DeleteCampaign(Campaign campaign) { - if (campaign == null) - throw new ArgumentNullException("campaign"); + Guard.NotNull(campaign, nameof(campaign)); - _campaignRepository.Delete(campaign); - - //event notification - _eventPublisher.EntityDeleted(campaign); + _campaignRepository.Delete(campaign); } - /// - /// Gets a campaign by identifier - /// - /// Campaign identifier - /// Campaign public virtual Campaign GetCampaignById(int campaignId) { if (campaignId == 0) @@ -102,10 +74,6 @@ public virtual Campaign GetCampaignById(int campaignId) } - /// - /// Gets all campaigns - /// - /// Campaign collection public virtual IList GetAllCampaigns() { var query = from c in _campaignRepository.Table @@ -116,26 +84,15 @@ orderby c.CreatedOnUtc return campaigns; } - - /// - /// Sends a campaign to specified emails - /// - /// Campaign - /// Email account - /// Subscriptions - /// Total emails sent - public virtual int SendCampaign(Campaign campaign, EmailAccount emailAccount, IEnumerable subscriptions) - { - if (campaign == null) - throw new ArgumentNullException("campaign"); - if (emailAccount == null) - throw new ArgumentNullException("emailAccount"); + public virtual int SendCampaign(Campaign campaign, IEnumerable subscriptions) + { + Guard.NotNull(campaign, nameof(campaign)); if (subscriptions == null || subscriptions.Count() <= 0) return 0; - int totalEmailsSent = 0; + int totalEmailsQueued = 0; var subscriptionData = subscriptions .Where(x => _storeMappingService.Authorize(campaign, x.StoreId)) @@ -143,70 +100,49 @@ public virtual int SendCampaign(Campaign campaign, EmailAccount emailAccount, IE foreach (var group in subscriptionData) { - var subscription = group.First(); // only one email per email address - - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, _storeContext.CurrentStore); - _messageTokenProvider.AddNewsLetterSubscriptionTokens(tokens, subscription); - - var customer = _customerService.GetCustomerByEmail(subscription.Email); - if (customer != null) - _messageTokenProvider.AddCustomerTokens(tokens, customer); - - string subject = _tokenizer.Replace(campaign.Subject, tokens, false); - string body = _tokenizer.Replace(campaign.Body, tokens, true); - - var email = new QueuedEmail() - { - Priority = 3, - From = emailAccount.Email, - FromName = emailAccount.DisplayName, - To = subscription.Email, - Subject = subject, - Body = body, - CreatedOnUtc = DateTime.UtcNow, - EmailAccountId = emailAccount.Id - }; - - _queuedEmailService.InsertQueuedEmail(email); - totalEmailsSent++; - } - return totalEmailsSent; + var subscription = group.First(); // only one email per email address + var customer = _customerService.GetCustomerByEmail(subscription.Email); + var messageContext = new MessageContext + { + MessageTemplate = GetCampaignTemplate(), + Customer = customer + }; + + var msg = _services.MessageFactory.CreateMessage(messageContext, true, subscription, campaign); + + if (msg.Email?.Id != null) + { + totalEmailsQueued++; + } + } + + return totalEmailsQueued; } - /// - /// Sends a campaign to specified email - /// - /// Campaign - /// Email account - /// Email - public virtual void SendCampaign(Campaign campaign, EmailAccount emailAccount, string email) - { - if (campaign == null) - throw new ArgumentNullException("campaign"); + public virtual CreateMessageResult Preview(Campaign campaign) + { + Guard.NotNull(campaign, nameof(campaign)); - if (emailAccount == null) - throw new ArgumentNullException("emailAccount"); + var messageContext = new MessageContext + { + MessageTemplate = GetCampaignTemplate(), + TestMode = true + }; - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, _storeContext.CurrentStore); - _messageTokenProvider.AddNewsLetterSubscriptionTokens(tokens, new NewsLetterSubscription() { - Email = email - }); + var subscription =_services.MessageFactory.GetTestModels(messageContext).OfType().FirstOrDefault(); - var customer = _customerService.GetCustomerByEmail(email); - if (customer != null) - _messageTokenProvider.AddCustomerTokens(tokens, customer); - - string subject = _tokenizer.Replace(campaign.Subject, tokens, false); - string body = _tokenizer.Replace(campaign.Body, tokens, true); + var result = _services.MessageFactory.CreateMessage(messageContext, false /* do NOT queue */, subscription, campaign); - var to = new EmailAddress(email); - var from = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + return result; + } - var msg = new EmailMessage(to, subject, body, from); + private MessageTemplate GetCampaignTemplate() + { + var messageTemplate = _messageTemplateService.GetMessageTemplateByName(MessageTemplateNames.SystemCampaign, _services.StoreContext.CurrentStore.Id); + if (messageTemplate == null) + throw new SmartException(T("Common.Error.NoMessageTemplate", MessageTemplateNames.SystemCampaign)); - _emailSender.SendEmail(new SmtpContext(emailAccount), msg); - } - } + return messageTemplate; + } + } } diff --git a/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs b/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs new file mode 100644 index 0000000000..b1fffacea2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Events; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Utilities; + +namespace SmartStore.Services.Messages +{ + public class CreateAttachmentsConsumer : IConsumer + { + private readonly PdfSettings _pdfSettings; + private readonly HttpRequestBase _httpRequest; + private readonly Lazy _fileDownloadManager; + + public CreateAttachmentsConsumer( + PdfSettings pdfSettings, + HttpRequestBase httpRequest, + Lazy fileDownloadManager) + { + this._pdfSettings = pdfSettings; + this._httpRequest = httpRequest; + this._fileDownloadManager = fileDownloadManager; + + Logger = NullLogger.Instance; + T = NullLocalizer.Instance; + } + + public ILogger Logger { get; set; } + public Localizer T { get; set; } + + public void HandleEvent(MessageQueuingEvent message) + { + var qe = message.QueuedEmail; + var ctx = message.MessageContext; + var model = message.MessageModel; + + var handledTemplates = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "OrderPlaced.CustomerNotification", _pdfSettings.AttachOrderPdfToOrderPlacedEmail }, + { "OrderCompleted.CustomerNotification", _pdfSettings.AttachOrderPdfToOrderCompletedEmail } + }; + + if (handledTemplates.TryGetValue(ctx.MessageTemplate.Name, out var shouldHandle) && shouldHandle) + { + if (model.Get("Order") is IDictionary order && order.Get("ID") is int orderId) + { + try + { + var qea = CreatePdfInvoiceAttachment(orderId); + qe.Attachments.Add(qea); + } + catch (Exception ex) + { + Logger.Error(ex, T("Admin.System.QueuedEmails.ErrorCreatingAttachment")); + } + } + } + } + + private QueuedEmailAttachment CreatePdfInvoiceAttachment(int orderId) + { + var urlHelper = new UrlHelper(_httpRequest.RequestContext); + var path = urlHelper.Action("Print", "Order", new { id = orderId, pdf = true, area = "" }); + + var fileResponse = _fileDownloadManager.Value.DownloadFile(path, true, 5000); + + if (fileResponse == null) + { + throw new InvalidOperationException(T("Admin.System.QueuedEmails.ErrorEmptyAttachmentResult", path)); + } + + if (!fileResponse.ContentType.IsCaseInsensitiveEqual("application/pdf")) + { + throw new InvalidOperationException(T("Admin.System.QueuedEmails.ErrorNoPdfAttachment")); + } + + return new QueuedEmailAttachment + { + StorageLocation = EmailAttachmentStorageLocation.Blob, + MediaStorage = new MediaStorage { Data = fileResponse.Data }, + MimeType = fileResponse.ContentType, + Name = fileResponse.FileName + }; + } + + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs b/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs index ce8793c7f5..8fafb3b117 100644 --- a/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs +++ b/src/Libraries/SmartStore.Services/Messages/EmailAccountService.cs @@ -1,36 +1,29 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Events; +using SmartStore.Data.Caching; namespace SmartStore.Services.Messages { public partial class EmailAccountService : IEmailAccountService { - private const string EMAILACCOUNT_BY_ID_KEY = "SmartStore.emailaccount.id-{0}"; - private const string EMAILACCOUNT_PATTERN_KEY = "SmartStore.emailaccount."; - private readonly IRepository _emailAccountRepository; private readonly EmailAccountSettings _emailAccountSettings; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; private EmailAccount _defaultEmailAccount; public EmailAccountService( IRepository emailAccountRepository, EmailAccountSettings emailAccountSettings, - IEventPublisher eventPublisher, - IRequestCache requestCache) + IEventPublisher eventPublisher) { this._emailAccountRepository = emailAccountRepository; this._emailAccountSettings = emailAccountSettings; this._eventPublisher = eventPublisher; - this._requestCache = requestCache; } public virtual void InsertEmailAccount(EmailAccount emailAccount) @@ -58,10 +51,7 @@ public virtual void InsertEmailAccount(EmailAccount emailAccount) _emailAccountRepository.Insert(emailAccount); - _requestCache.RemoveByPattern(EMAILACCOUNT_PATTERN_KEY); _defaultEmailAccount = null; - - _eventPublisher.EntityInserted(emailAccount); } public virtual void UpdateEmailAccount(EmailAccount emailAccount) @@ -89,10 +79,7 @@ public virtual void UpdateEmailAccount(EmailAccount emailAccount) _emailAccountRepository.Update(emailAccount); - _requestCache.RemoveByPattern(EMAILACCOUNT_PATTERN_KEY); _defaultEmailAccount = null; - - _eventPublisher.EntityUpdated(emailAccount); } public virtual void DeleteEmailAccount(EmailAccount emailAccount) @@ -105,10 +92,7 @@ public virtual void DeleteEmailAccount(EmailAccount emailAccount) _emailAccountRepository.Delete(emailAccount); - _requestCache.RemoveByPattern(EMAILACCOUNT_PATTERN_KEY); _defaultEmailAccount = null; - - _eventPublisher.EntityDeleted(emailAccount); } public virtual EmailAccount GetEmailAccountById(int emailAccountId) @@ -116,12 +100,8 @@ public virtual EmailAccount GetEmailAccountById(int emailAccountId) if (emailAccountId == 0) return null; - string key = string.Format(EMAILACCOUNT_BY_ID_KEY, emailAccountId); - return _requestCache.Get(key, () => - { - return _emailAccountRepository.GetById(emailAccountId); - }); - } + return _emailAccountRepository.GetByIdCached(emailAccountId, "db.emailaccount.id-" + emailAccountId); + } public virtual EmailAccount GetDefaultEmailAccount() { diff --git a/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs b/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs deleted file mode 100644 index 681cb588b6..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/EventPublisherExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using SmartStore.Core; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Events; - -namespace SmartStore.Services.Messages -{ - public static class EventPublisherExtensions - { - /// - /// Publishes the newsletter subscribe event. - /// - /// The event publisher. - /// The email. - public static void PublishNewsletterSubscribe(this IEventPublisher eventPublisher, string email) - { - eventPublisher.Publish(new EmailSubscribedEvent(email)); - } - - /// - /// Publishes the newsletter unsubscribe event. - /// - /// The event publisher. - /// The email. - public static void PublishNewsletterUnsubscribe(this IEventPublisher eventPublisher, string email) - { - eventPublisher.Publish(new EmailUnsubscribedEvent(email)); - } - - public static void EntityTokensAdded(this IEventPublisher eventPublisher, T entity, IList tokens) where T : BaseEntity - { - eventPublisher.Publish(new EntityTokensAddedEvent(entity, tokens)); - } - - public static void MessageTokensAdded(this IEventPublisher eventPublisher, MessageTemplate message, IList tokens) - { - eventPublisher.Publish(new MessageTokensAddedEvent(message, tokens)); - } - } -} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Messages/Events.cs b/src/Libraries/SmartStore.Services/Messages/Events.cs new file mode 100644 index 0000000000..02bbf52c66 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/Events.cs @@ -0,0 +1,87 @@ +using System; +using SmartStore.Core.Domain.Messages; + +namespace SmartStore.Services.Messages +{ + /// + /// Published after the creation of a single message model part has been completed. + /// + /// Type of source entity + public class MessageModelPartCreatedEvent where T : class + { + public MessageModelPartCreatedEvent(T source, dynamic part) + { + Source = source; + Part = part; + } + + /// + /// The source object for which the model part has been created, e.g. a Product entity. + /// + public T Source { get; private set; } + + /// + /// The resulting model part. + /// + public dynamic Part { get; private set; } + } + + /// + /// Published when a system mapper is missing for a particular model type (e.g. a custom entity in a plugin). + /// Implementors should subscribe to this event in order to provide a corresponding dynamic model part. + /// The result model should be assigned to the property. If this property + /// is still null, the source is used as model part instead. + /// + public class MessageModelPartMappingEvent + { + public MessageModelPartMappingEvent(object source) + { + Source = source; + } + + /// + /// The source object for which a model part should be created. + /// + public object Source { get; private set; } + + /// + /// The resulting model part. + /// + public dynamic Result { get; set; } + + /// + /// The name of the model part. If null the source's type name is used. + /// + public string ModelPartName { get; set; } + } + + /// + /// Published after the message model has been completely created. + /// + public class MessageModelCreatedEvent + { + public MessageModelCreatedEvent(MessageContext messageContext, TemplateModel model) + { + MessageContext = messageContext; + Model = model; + } + + public MessageContext MessageContext{ get; private set; } + + /// + /// The result message model. + /// + public TemplateModel Model { get; private set; } + } + + /// + /// An event message which gets published just before a new instance + /// of is persisted to the database + /// + public class MessageQueuingEvent + { + public QueuedEmail QueuedEmail { get; set; } + public MessageContext MessageContext { get; set; } + public TemplateModel MessageModel { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/GenericMessageContext.cs b/src/Libraries/SmartStore.Services/Messages/GenericMessageContext.cs deleted file mode 100644 index 29018db637..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/GenericMessageContext.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Core.Domain.Customers; - -namespace SmartStore.Services.Messages -{ - public class GenericMessageContext - { - public GenericMessageContext() - { - this.Tokens = new List(); - } - public int? LanguageId { get; set; } - public int? StoreId { get; set; } - public IList Tokens { get; internal set; } - public IMessageTokenProvider MessagenTokenProvider { get; internal set; } - public Customer Customer { get; set; } - public string ToEmail { get; set; } - public string ToName { get; set; } - - /// - /// Gets or sets a value specifying whether customer's email should be used as reply address - /// - /// Value is ignored, if Customer property is null - public bool ReplyToCustomer { get; set; } - - /// - /// Gets or sets the reply email address - /// - /// Value is ignored, if ReplyToCustomer is true AND Customer property is not null - public string ReplyToEmail { get; set; } - - /// - /// Gets or sets the reply to name - /// - /// Value is ignored, if ReplyToCustomer is true AND Customer property is not null - public string ReplyToName { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/ICampaignService.cs b/src/Libraries/SmartStore.Services/Messages/ICampaignService.cs index 3bd2ab3e2a..0954ec1eb7 100644 --- a/src/Libraries/SmartStore.Services/Messages/ICampaignService.cs +++ b/src/Libraries/SmartStore.Services/Messages/ICampaignService.cs @@ -40,17 +40,15 @@ public partial interface ICampaignService /// Sends a campaign to specified emails /// /// Campaign - /// Email account /// Subscriptions /// Total emails sent - int SendCampaign(Campaign campaign, EmailAccount emailAccount, IEnumerable subscriptions); + int SendCampaign(Campaign campaign, IEnumerable subscriptions); - /// - /// Sends a campaign to specified email - /// - /// Campaign - /// Email account - /// Email - void SendCampaign(Campaign campaign, EmailAccount emailAccount, string email); - } + /// + /// Creates a campaign email without sending it for previewing and testing purposes. + /// + /// The campaign to preview + /// The preview result + CreateMessageResult Preview(Campaign campaign); + } } diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs b/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs new file mode 100644 index 0000000000..525e50d09c --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs @@ -0,0 +1,109 @@ +using System; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Email; +using SmartStore.Templating; + +namespace SmartStore.Services.Messages +{ + /// + /// Contains the result data of a call + /// + public class CreateMessageResult + { + /// + /// The queued email instance which can be saved to the database + /// + public QueuedEmail Email { get; set; } + + /// + /// The final model which contains all global and template specific model parts. + /// + public TemplateModel Model { get; set; } + + /// + /// The message context used to create the message. + /// + public MessageContext MessageContext { get; set; } + } + + /// + /// Creates and optionally queues email messages + /// + public interface IMessageFactory + { + /// + /// Creates an email message + /// + /// Contains all data required for creating a message + /// If true, the created email message will automatically be queued for sending (saved in database as a ) + /// + /// All model objects that are necessary to render the template (in no particular order). + /// The passed object instances will be converted to special types which the underlying can handle. + /// is responsible for the conversion. See also . + /// + /// Contains the message creation result. + CreateMessageResult CreateMessage(MessageContext messageContext, bool queue, params object[] modelParts); + + /// + /// Queues a message created by + /// + /// The message context used to create the message. + /// The instance of to queue, e.g. obtained from + void QueueMessage(MessageContext messageContext, QueuedEmail queuedEmail); + + /// + /// Gets an array of suitable test model parts during preview mode. The message template defines + /// which model part types are required (a comma-separated type list in ). + /// The framework tries to load a random entity for each defined type from the database. If the table does not contain any records, + /// gets called internally to obtain a test model wrapper with sample data. + /// + /// The message context used to create the message. + /// An array of model parts which can be passed to + object[] GetTestModels(MessageContext messageContext); + } + + public static class IMessageFactoryExtensions + { + /// + /// Sends the "ContactUs" message to the store owner + /// + public static CreateMessageResult SendContactUsMessage(this IMessageFactory factory, Customer customer, + string senderEmail, string senderName, string subject, string message, EmailAddress senderEmailAddress, int languageId = 0) + { + var model = new NamedModelPart("Message") + { + ["Subject"] = subject.NullEmpty(), + ["Message"] = message.NullEmpty(), + ["SenderEmail"] = senderEmail.NullEmpty(), + ["SenderName"] = senderName.HasValue() ? senderName.NullEmpty() : senderEmail.NullEmpty() + }; + + var messageContext = MessageContext.Create(MessageTemplateNames.SystemContactUs, languageId, customer: customer); + if (senderEmailAddress != null) + { + messageContext.SenderEmailAddress = senderEmailAddress; + } + + return factory.CreateMessage(messageContext, true, model); + } + + /// + /// Sends a newsletter subscription activation message + /// + public static CreateMessageResult SendNewsLetterSubscriptionActivationMessage(this IMessageFactory factory, NewsLetterSubscription subscription, int languageId = 0) + { + Guard.NotNull(subscription, nameof(subscription)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewsLetterSubscriptionActivation, languageId), true, subscription); + } + + /// + /// Sends a newsletter subscription deactivation message + /// + public static CreateMessageResult SendNewsLetterSubscriptionDeactivationMessage(this IMessageFactory factory, NewsLetterSubscription subscription, int languageId = 0) + { + Guard.NotNull(subscription, nameof(subscription)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewsLetterSubscriptionDeactivation, languageId), true, subscription); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs b/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs new file mode 100644 index 0000000000..6819624dea --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using SmartStore.Collections; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Blogs; +using SmartStore.Core.Domain.News; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Common; + +namespace SmartStore.Services.Messages +{ + /// + /// Responsible for building the message template model + /// + public interface IMessageModelProvider + { + /// + /// Creates and adds all global model parts to the template model (): + /// + /// + /// Context (contains meta infos like template name, language etc.) + /// Customer (obtained from property) + /// Store (obtained from property) + /// Email (the ) + /// Theme (some theming variables, mostly colors) + /// + /// + /// + /// Contains all data required for building a model part and creating a message + void AddGlobalModelParts(MessageContext messageContext); + + /// + /// Adds a template specific model part to the template model. + /// The passed object instance () will be converted to a special type which the underlying can handle. + /// + /// 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, the framework will publish the event, giving + /// a subscriber the chance to provide a converted model object and a part name. + /// + /// + /// The model part instance to convert and add to the final model. + /// Contains all data required for building a model part and creating a message + /// + /// The name to use for the model part in the final model. If null, the framework tries to infer the name. + /// See also + /// + 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: + /// + /// When is a plain object: type name + /// When is : ModelPartName property + /// + /// + /// The model part instance to resolve a name for. + /// The inferred name or null + string ResolveModelName(object model); + + /// + /// Build a model metadata tree for a final template model. Model trees are used + /// on the client to provide autocomplete information. + /// + /// The final template model to build a model tree for. + /// A hierarchy of instances. + TreeNode BuildModelTree(TemplateModel model); + + /// + /// Gets the last known model metadata tree for a particular template. + /// See also + /// + /// Name of the template to get metadata for. + /// A hierarchy of instances. + TreeNode GetLastModelTree(string messageTemplateName); + + /// + /// Gets the last known model metadata tree for a particular template. + /// See also + /// + /// The template to get metadata for. + /// A hierarchy of instances. + TreeNode GetLastModelTree(MessageTemplate template); + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs deleted file mode 100644 index 3cf3bd7563..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using SmartStore.Core.Domain.Blogs; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Domain.News; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Stores; - -namespace SmartStore.Services.Messages -{ - public partial interface IMessageTokenProvider - { - void AddStoreTokens(IList tokens, Store store); - - void AddOrderTokens(IList tokens, Order order, Language language); - - void AddShipmentTokens(IList tokens, Shipment shipment, Language language); - - void AddOrderNoteTokens(IList tokens, OrderNote orderNote); - - void AddRecurringPaymentTokens(IList tokens, RecurringPayment recurringPayment); - - void AddReturnRequestTokens(IList tokens, ReturnRequest returnRequest, OrderItem orderItem); - - void AddGiftCardTokens(IList tokens, GiftCard giftCard); - - void AddCustomerTokens(IList tokens, Customer customer); - - void AddNewsLetterSubscriptionTokens(IList tokens, NewsLetterSubscription subscription); - - void AddProductReviewTokens(IList tokens, ProductReview productReview); - - void AddBlogCommentTokens(IList tokens, BlogComment blogComment); - - void AddNewsCommentTokens(IList tokens, NewsComment newsComment); - - void AddProductTokens(IList tokens, Product product, Language language); - - void AddForumTokens(IList tokens, Forum forum, Language language); - - void AddForumTopicTokens(IList tokens, ForumTopic forumTopic, - int? friendlyForumTopicPageIndex = null, int? appendedPostIdentifierAnchor = null); - - void AddForumPostTokens(IList tokens, ForumPost forumPost); - - void AddPrivateMessageTokens(IList tokens, PrivateMessage privateMessage); - - void AddBackInStockTokens(IList tokens, BackInStockSubscription subscription); - - string[] GetListOfCampaignAllowedTokens(); - - string[] GetListOfAllowedTokens(); - - void AddBankConnectionTokens(IList tokens); - - void AddCompanyTokens(IList tokens); - - void AddContactDataTokens(IList tokens); - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/IModelPart.cs b/src/Libraries/SmartStore.Services/Messages/IModelPart.cs new file mode 100644 index 0000000000..16ed798135 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/IModelPart.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using SmartStore.ComponentModel; + +namespace SmartStore.Services.Messages +{ + /// + /// Used to transfer miscellaneous data to the template engine + /// which is merged with the generic "Bag" part. + /// + public interface IModelPart : IDictionary + { + } + + public interface INamedModelPart : IDictionary + { + string ModelPartName { get; } + } + + + #region Impl + + public class ModelPart : HybridExpando, IModelPart + { + } + + public class NamedModelPart : HybridExpando, INamedModelPart + { + public NamedModelPart(string modelPartName) + : base(true) + { + Guard.NotEmpty(modelPartName, nameof(modelPartName)); + ModelPartName = modelPartName; + } + + public string ModelPartName + { + get; + private set; + } + } + + #endregion +} diff --git a/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs b/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs index 12baf20719..82a6de9bac 100644 --- a/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading.Tasks; using SmartStore.Core; using SmartStore.Core.Domain.Messages; namespace SmartStore.Services.Messages { - public partial interface IQueuedEmailService + public partial interface IQueuedEmailService { /// /// Inserts a queued email @@ -53,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 @@ -69,7 +69,14 @@ public partial interface IQueuedEmailService /// /// Deleted a queued email attachment /// - /// Queued email attachment - void DeleteQueuedEmailAttachment(QueuedEmailAttachment qea); - } + /// Queued email attachment + void DeleteQueuedEmailAttachment(QueuedEmailAttachment attachment); + + /// + /// Load binary data of a queued email attachment + /// + /// Queued email attachment + /// Binary data if attachment.StorageLocation is EmailAttachmentStorageLocation.Blob, otherwise null + byte[] LoadQueuedEmailAttachmentBinary(QueuedEmailAttachment attachment); + } } diff --git a/src/Libraries/SmartStore.Services/Messages/ITokenizer.cs b/src/Libraries/SmartStore.Services/Messages/ITokenizer.cs deleted file mode 100644 index 81fc75982b..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/ITokenizer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace SmartStore.Services.Messages -{ - public partial interface ITokenizer - { - /// - /// Replace all of the token key occurences inside the specified template text with corresponded token values - /// - /// The template with token keys inside - /// The sequence of tokens to use - /// The value indicating whether tokens should be HTML encoded - /// Text with all token keys replaces by token value - string Replace(string template, IEnumerable tokens, bool htmlEncode); - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/IWorkflowMessageService.cs b/src/Libraries/SmartStore.Services/Messages/IWorkflowMessageService.cs deleted file mode 100644 index 5dbb25c83a..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/IWorkflowMessageService.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using SmartStore.Core.Domain.Blogs; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Domain.News; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Shipping; - -namespace SmartStore.Services.Messages -{ - public partial interface IWorkflowMessageService - { - #region Customer workflow - - /// - /// Sends 'New customer' notification message to a store owner - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - int SendCustomerRegisteredNotificationMessage(Customer customer, int languageId); - - /// - /// Sends a welcome message to a customer - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - int SendCustomerWelcomeMessage(Customer customer, int languageId); - - /// - /// Sends an email validation message to a customer - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - int SendCustomerEmailValidationMessage(Customer customer, int languageId); - - /// - /// Sends password recovery message to a customer - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - int SendCustomerPasswordRecoveryMessage(Customer customer, int languageId); - - #endregion - - #region Order workflow - - /// - /// Sends an order placed notification to a store owner - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - int SendOrderPlacedStoreOwnerNotification(Order order, int languageId); - - /// - /// Sends an order placed notification to a customer - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - int SendOrderPlacedCustomerNotification(Order order, int languageId); - - /// - /// Sends a shipment sent notification to a customer - /// - /// Shipment - /// Message language identifier - /// Queued email identifier - int SendShipmentSentCustomerNotification(Shipment shipment, int languageId); - - /// - /// Sends a shipment delivered notification to a customer - /// - /// Shipment - /// Message language identifier - /// Queued email identifier - int SendShipmentDeliveredCustomerNotification(Shipment shipment, int languageId); - - /// - /// Sends an order completed notification to a customer - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - int SendOrderCompletedCustomerNotification(Order order, int languageId); - - /// - /// Sends an order cancelled notification to a customer - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - int SendOrderCancelledCustomerNotification(Order order, int languageId); - - /// - /// Sends a new order note added notification to a customer - /// - /// Order note - /// Message language identifier - /// Queued email identifier - int SendNewOrderNoteAddedCustomerNotification(OrderNote orderNote, int languageId); - - /// - /// Sends a "Recurring payment cancelled" notification to a store owner - /// - /// Recurring payment - /// Message language identifier - /// Queued email identifier - int SendRecurringPaymentCancelledStoreOwnerNotification(RecurringPayment recurringPayment, int languageId); - - #endregion - - #region Newsletter workflow - - /// - /// Sends a newsletter subscription activation message - /// - /// Newsletter subscription - /// Language identifier - /// Queued email identifier - int SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, - int languageId); - - /// - /// Sends a newsletter subscription deactivation message - /// - /// Newsletter subscription - /// Language identifier - /// Queued email identifier - int SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, - int languageId); - - #endregion - - #region Send a message to a friend - - /// - /// Sends "email a friend" message - /// - /// Customer instance - /// Message language identifier - /// Product instance - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - int SendProductEmailAFriendMessage(Customer customer, int languageId, - Product product, string customerEmail, string friendsEmail, string personalMessage); - - int SendProductQuestionMessage(Customer customer, int languageId, Product product, - string senderEmail, string senderName, string senderPhone, string question); - - /// - /// Sends wishlist "email a friend" message - /// - /// Customer - /// Message language identifier - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - int SendWishlistEmailAFriendMessage(Customer customer, int languageId, - string customerEmail, string friendsEmail, string personalMessage); - - #endregion - - #region Return requests - - /// - /// Sends 'New Return Request' message to a store owner - /// - /// Return request - /// Order item - /// Message language identifier - /// Queued email identifier - int SendNewReturnRequestStoreOwnerNotification(ReturnRequest returnRequest, OrderItem orderItem, int languageId); - - - /// - /// Sends 'Return Request status changed' message to a customer - /// - /// Return request - /// Order item - /// Message language identifier - /// Queued email identifier - int SendReturnRequestStatusChangedCustomerNotification(ReturnRequest returnRequest, OrderItem orderItem, int languageId); - - #endregion - - #region Forum Notifications - - /// - /// Sends a forum subscription message to a customer - /// - /// Customer instance - /// Forum Topic - /// Forum - /// Message language identifier - /// Queued email identifier - int SendNewForumTopicMessage(Customer customer, - ForumTopic forumTopic, Forum forum, int languageId); - - /// - /// Sends a forum subscription message to a customer - /// - /// Customer instance - /// Forum post - /// Forum Topic - /// Forum - /// Friendly (starts with 1) forum topic page to use for URL generation - /// Message language identifier - /// Queued email identifier - int SendNewForumPostMessage(Customer customer, - ForumPost forumPost, ForumTopic forumTopic, - Forum forum, int friendlyForumTopicPageIndex, - int languageId); - - /// - /// Sends a private message notification - /// - /// Private message - /// Message language identifier - /// Queued email identifier - int SendPrivateMessageNotification(Customer customer, PrivateMessage privateMessage, int languageId); - - #endregion - - #region Misc - - /// - /// Sends a product review notification message to a store owner - /// - /// Product review - /// Message language identifier - /// Queued email identifier - int SendProductReviewNotificationMessage(ProductReview productReview, - int languageId); - - /// - /// Sends a gift card notification - /// - /// Gift card - /// Message language identifier - /// Queued email identifier - int SendGiftCardNotification(GiftCard giftCard, int languageId); - - - /// - /// Sends a "quantity below" notification to a store owner - /// - /// Product - /// Message language identifier - /// Queued email identifier - int SendQuantityBelowStoreOwnerNotification(Product product, int languageId); - - - /// - /// Sends a "new VAT sumitted" notification to a store owner - /// - /// Customer - /// Received VAT name - /// Received VAT address - /// Message language identifier - /// Queued email identifier - int SendNewVatSubmittedStoreOwnerNotification(Customer customer, - string vatName, string vatAddress, int languageId); - - /// - /// Sends a blog comment notification message to a store owner - /// - /// Blog comment - /// Message language identifier - /// Queued email identifier - int SendBlogCommentNotificationMessage(BlogComment blogComment, int languageId); - - /// - /// Sends a news comment notification message to a store owner - /// - /// News comment - /// Message language identifier - /// Queued email identifier - int SendNewsCommentNotificationMessage(NewsComment newsComment, int languageId); - - /// - /// Sends a 'Back in stock' notification message to a customer - /// - /// Subscription - /// Message language identifier - /// Queued email identifier - int SendBackInStockNotification(BackInStockSubscription subscription, int languageId); - - /// - /// Sends a generic message - /// - /// The name of the message template - /// Configurator action for the message - /// Queued email identifier - int SendGenericMessage(string messageTemplateName, Action cfg); - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 979051d1d6..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, 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 new file mode 100644 index 0000000000..056ab70126 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/MessageContext.cs @@ -0,0 +1,101 @@ +using System; +using System.Globalization; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Email; +using SmartStore.Core.Localization; + +namespace SmartStore.Services.Messages +{ + /// + /// A context object which contains all required and optional information + /// for the creation of message templates. + /// + public class MessageContext + { + private IFormatProvider _formatProvider; + + /// + /// The source message template. Required if is empty. + /// + public MessageTemplate MessageTemplate { get; set; } + + /// + /// The source message template name. Required if is null. + /// + public string MessageTemplateName { get; set; } + + /// + /// If null, the email account specifies the sender. + /// + public EmailAddress SenderEmailAddress { get; set; } + + /// + /// If null, obtained from WorkContext.CurrentCustomer. + /// + public Customer Customer { get; set; } + + /// + /// If null, obtained from WorkContext.WorkingLanguage. + /// + public int? LanguageId { get; set; } + + /// + /// If null, obtained from StoreContext.CurrentStore. + /// + public int? StoreId { get; set; } + + internal Language Language { get; set; } + internal Store Store { get; set; } + public EmailAccount EmailAccount { get; internal set; } + + public bool TestMode { get; set; } + + /// + /// If null, obtained from . + /// + public Uri BaseUri { get; set; } + + /// + /// The final template model containing all global and template specific model parts. + /// + public TemplateModel Model { get; set; } + + /// + /// If null, inferred from . + /// + public IFormatProvider FormatProvider + { + get + { + if (_formatProvider == null) + { + var culture = this.Language?.LanguageCulture; + if (culture != null && LocalizationHelper.IsValidCultureCode(culture)) + { + _formatProvider = CultureInfo.GetCultureInfo(culture); + } + } + + return _formatProvider ?? CultureInfo.CurrentCulture; + } + set + { + _formatProvider = value; + } + } + + public static MessageContext Create(string messageTemplateName, int languageId, int? storeId = null, Customer customer = null) + { + return new MessageContext + { + MessageTemplateName = messageTemplateName, + LanguageId = languageId, + StoreId = storeId, + Customer = customer + }; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs new file mode 100644 index 0000000000..a70a2b480e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs @@ -0,0 +1,645 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using Newtonsoft.Json; +using SmartStore.ComponentModel; +using SmartStore.Core; +using SmartStore.Core.Domain.Blogs; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.News; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Email; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Services.Localization; +using SmartStore.Services.Media; +using SmartStore.Templating; +using SmartStore.Utilities; + +namespace SmartStore.Services.Messages +{ + public partial class MessageFactory : IMessageFactory + { + const string LoremIpsum = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."; + + private readonly ICommonServices _services; + private readonly ITemplateEngine _templateEngine; + private readonly ITemplateManager _templateManager; + private readonly IMessageModelProvider _modelProvider; + private readonly IMessageTemplateService _messageTemplateService; + private readonly IQueuedEmailService _queuedEmailService; + private readonly ILanguageService _languageService; + private readonly IEmailAccountService _emailAccountService; + private readonly EmailAccountSettings _emailAccountSettings; + private readonly IDownloadService _downloadService; + + public MessageFactory( + ICommonServices services, + ITemplateEngine templateEngine, + ITemplateManager templateManager, + IMessageModelProvider modelProvider, + IMessageTemplateService messageTemplateService, + IQueuedEmailService queuedEmailService, + ILanguageService languageService, + IEmailAccountService emailAccountService, + EmailAccountSettings emailAccountSettings, + IDownloadService downloadService) + { + _services = services; + _templateEngine = templateEngine; + _templateManager = templateManager; + _modelProvider = modelProvider; + _messageTemplateService = messageTemplateService; + _queuedEmailService = queuedEmailService; + _languageService = languageService; + _emailAccountService = emailAccountService; + _emailAccountSettings = emailAccountSettings; + _downloadService = downloadService; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public virtual CreateMessageResult CreateMessage(MessageContext messageContext, bool queue, params object[] modelParts) + { + Guard.NotNull(messageContext, nameof(messageContext)); + + modelParts = modelParts ?? new object[0]; + + // Handle TestMode + if (messageContext.TestMode && modelParts.Length == 0) + { + modelParts = GetTestModels(messageContext); + } + + ValidateMessageContext(messageContext, ref modelParts); + + // 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); + + // Add specific template models for passed parts + foreach (var part in modelParts) + { + if (model != null) + { + _modelProvider.AddModelPart(part, messageContext); + } + } + + // Give implementors the chance to customize the final template model + _services.EventPublisher.Publish(new MessageModelCreatedEvent(messageContext, model)); + + var messageTemplate = messageContext.MessageTemplate; + var languageId = messageContext.Language.Id; + + // Render templates + var to = RenderEmailAddress(messageTemplate.To, messageContext); + var replyTo = RenderEmailAddress(messageTemplate.ReplyTo, messageContext, false); + var bcc = RenderTemplate(messageTemplate.GetLocalized((x) => x.BccEmailAddresses, languageId), messageContext, false); + + var subject = RenderTemplate(messageTemplate.GetLocalized((x) => x.Subject, languageId), messageContext); + ((dynamic)model).Email.Subject = subject; + + var body = RenderBodyTemplate(messageContext); + + // CSS inliner + body = InlineCss(body, model); + + // Model tree + var modelTree = _modelProvider.BuildModelTree(model); + var modelTreeJson = JsonConvert.SerializeObject(modelTree, Formatting.None); + if (modelTreeJson != messageTemplate.LastModelTree) + { + messageContext.MessageTemplate.LastModelTree = modelTreeJson; + if (!messageTemplate.IsTransientRecord()) + { + _messageTemplateService.UpdateMessageTemplate(messageContext.MessageTemplate); + } + } + + // Create queued email from template + var qe = new QueuedEmail + { + Priority = 5, + From = messageContext.SenderEmailAddress ?? messageContext.EmailAccount.ToEmailAddress(), + To = to.ToString(), + Bcc = bcc, + ReplyTo = replyTo?.ToString(), + Subject = subject, + Body = body, + CreatedOnUtc = DateTime.UtcNow, + EmailAccountId = messageContext.EmailAccount.Id, + SendManually = messageTemplate.SendManually + }; + + // Create and add attachments (if any) + CreateAttachments(qe, messageContext); + + if (queue) + { + // Put to queue + QueueMessage(messageContext, qe); + } + + return new CreateMessageResult { Email = qe, Model = model, MessageContext = messageContext }; + } + + public virtual void QueueMessage(MessageContext messageContext, QueuedEmail queuedEmail) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(queuedEmail, nameof(queuedEmail)); + + // Publish event so that integrators can add attachments, alter the email etc. + _services.EventPublisher.Publish(new MessageQueuingEvent + { + QueuedEmail = queuedEmail, + MessageContext = messageContext, + MessageModel = messageContext.Model + }); + + _queuedEmailService.InsertQueuedEmail(queuedEmail); + } + + private EmailAddress RenderEmailAddress(string email, MessageContext ctx, bool required = true) + { + string parsed = null; + + try + { + parsed = RenderTemplate(email, ctx, required); + + if (required || parsed.HasValue()) + { + return parsed.Convert(); + } + else + { + return null; + } + } + catch (Exception ex) + { + if (ctx.TestMode) + { + return new EmailAddress("john@doe.com", "John Doe"); + } + + var ex2 = new SmartException($"Failed to parse email address for variable '{email}'. Value was '{parsed.EmptyNull()}': {ex.Message}", ex); + _services.Notifier.Error(ex2.Message); + throw ex2; + } + } + + private string RenderTemplate(string template, MessageContext ctx, bool required = true) + { + if (!required && template.IsEmpty()) + { + return null; + } + + return _templateEngine.Render(template, ctx.Model, ctx.FormatProvider); + } + + private string RenderBodyTemplate(MessageContext ctx) + { + var key = BuildTemplateKey(ctx); + var source = ctx.MessageTemplate.GetLocalized((x) => x.Body, ctx.Language); + var fromCache = true; + var template = _templateManager.GetOrAdd(key, GetBodyTemplate); + + if (fromCache && template.Source != source) + { + // The template was resolved from template cache, but it has expired + // because the source text has changed. + template = _templateEngine.Compile(source); + _templateManager.Put(key, template); + } + + return template.Render(ctx.Model, ctx.FormatProvider); + + string GetBodyTemplate() + { + fromCache = false; + return source; + } + } + + private string BuildTemplateKey(MessageContext messageContext) + { + var prefix = messageContext.MessageTemplate.IsTransientRecord() ? "TransientTemplate/" : "MessageTemplate/"; + return prefix + messageContext.MessageTemplate.Name + "/" + messageContext.Language.Id + "/Body"; + } + + private string InlineCss(string html, dynamic model) + { + Uri baseUri = null; + + try + { + // 'Store' is a global model part, so we pretty can be sure it exists + baseUri = new Uri((string)model.Store.Url); + } + catch { } + + var pm = new PreMailer.Net.PreMailer(html, baseUri); + var result = pm.MoveCssInline(true, "#ignore"); + return result.Html; + } + + protected virtual void CreateAttachments(QueuedEmail queuedEmail, MessageContext messageContext) + { + var messageTemplate = messageContext.MessageTemplate; + var languageId = messageContext.Language.Id; + + // create attachments if any + var fileIds = (new int?[] + { + messageTemplate.GetLocalized(x => x.Attachment1FileId, languageId), + messageTemplate.GetLocalized(x => x.Attachment2FileId, languageId), + messageTemplate.GetLocalized(x => x.Attachment3FileId, languageId) + }) + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToArray(); + + if (fileIds.Any()) + { + var files = _downloadService.GetDownloadsByIds(fileIds); + foreach (var file in files) + { + queuedEmail.Attachments.Add(new QueuedEmailAttachment + { + StorageLocation = EmailAttachmentStorageLocation.FileReference, + FileId = file.Id, + Name = (file.Filename.NullEmpty() ?? file.Id.ToString()) + file.Extension.EmptyNull(), + MimeType = file.ContentType.NullEmpty() ?? "application/octet-stream" + }); + } + } + } + + + private void ValidateMessageContext(MessageContext ctx, ref object[] modelParts) + { + var t = ctx.MessageTemplate; + if (t != null) + { + if (t.To.IsEmpty() || t.Subject.IsEmpty() || t.Name.IsEmpty()) + { + throw new InvalidOperationException("Message template validation failed, because at least one of the following properties has not been set: Name, To, Subject."); + } + } + + if (ctx.StoreId.GetValueOrDefault() == 0) + { + ctx.Store = _services.StoreContext.CurrentStore; + ctx.StoreId = ctx.Store.Id; + } + else + { + ctx.Store = _services.StoreService.GetStoreById(ctx.StoreId.Value); + } + + if (ctx.BaseUri == null) + { + ctx.BaseUri = new Uri(_services.StoreService.GetHost(ctx.Store)); + } + + if (ctx.LanguageId.GetValueOrDefault() == 0) + { + ctx.Language = _services.WorkContext.WorkingLanguage; + ctx.LanguageId = ctx.Language.Id; + } + else + { + ctx.Language = _languageService.GetLanguageById(ctx.LanguageId.Value); + } + + EnsureLanguageIsActive(ctx); + + var parts = modelParts?.AsEnumerable() ?? Enumerable.Empty(); + + if (ctx.Customer == null) + { + // Try to move Customer from parts to MessageContext + var customer = parts.OfType().FirstOrDefault(); + if (customer != null) + { + // Exclude the found customer from parts list + parts = parts.Where(x => !object.ReferenceEquals(x, customer)); + } + + ctx.Customer = customer ?? _services.WorkContext.CurrentCustomer; + } + + if (ctx.Customer.IsSystemAccount) + { + throw new ArgumentException("Cannot create messages for system customer accounts.", nameof(ctx)); + } + + if (ctx.MessageTemplate == null) + { + if (ctx.MessageTemplateName.IsEmpty()) + { + throw new ArgumentException("'MessageTemplateName' must not be empty if 'MessageTemplate' is null.", nameof(ctx)); + } + + ctx.MessageTemplate = _messageTemplateService.GetMessageTemplateByName(ctx.MessageTemplateName, ctx.Store.Id); + + if (ctx.MessageTemplate != null && !ctx.TestMode && !ctx.MessageTemplate.IsActive) + { + ctx.MessageTemplate = null; + } + } + + if (ctx.EmailAccount == null && ctx.MessageTemplate != null) + { + ctx.EmailAccount = GetEmailAccountOfMessageTemplate(ctx.MessageTemplate, ctx.Language.Id); + } + + // Sort parts: "IModelPart" instances must come first + var bagParts = parts.OfType(); + if (bagParts.Any()) + { + parts = bagParts.Concat(parts.Except(bagParts)); + } + + 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); + + if (account == null) + { + account = _emailAccountService.GetDefaultEmailAccount(); + } + + if (account == null) + { + throw new SmartException(T("Common.Error.NoEmailAccount")); + } + + return account; + } + + private void EnsureLanguageIsActive(MessageContext ctx) + { + var language = ctx.Language; + + if (language == null || !language.Published) + { + // Load any language from the specified store + language = _languageService.GetLanguageById(_languageService.GetDefaultLanguageId(ctx.StoreId.Value)); + } + + if (language == null || !language.Published) + { + // Load any language + language = _languageService.GetAllLanguages().FirstOrDefault(); + } + + ctx.Language = language ?? throw new SmartException(T("Common.Error.NoActiveLanguage")); + } + + #region TestModels + + public virtual object[] GetTestModels(MessageContext messageContext) + { + var templateName = (messageContext.MessageTemplate?.Name ?? messageContext.MessageTemplateName); + + var factories = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "BlogComment", () => GetRandomEntity(x => true) }, + { "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) }, + { "OrderNote", () => GetRandomEntity(x => !x.Order.Deleted) }, + { "RecurringPayment", () => GetRandomEntity(x => !x.Deleted) }, + { "NewsLetterSubscription", () => GetRandomEntity(x => true) }, + { "Campaign", () => GetRandomEntity(x => true) }, + { "ReturnRequest", () => GetRandomEntity(x => true) }, + { "OrderItem", () => GetRandomEntity(x => !x.Order.Deleted) }, + { "ForumTopic", () => GetRandomEntity(x => true) }, + { "ForumPost", () => GetRandomEntity(x => true) }, + { "PrivateMessage", () => GetRandomEntity(x => true) }, + { "GiftCard", () => GetRandomEntity(x => true) }, + { "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 + .SplitSafe(",") + .Select(x => x.Trim()) + .Distinct() + .ToArray(); + + var models = new Dictionary(); + var result = new List(); + + foreach (var modelName in modelNames) + { + var model = GetModelFromExpression(modelName, models, factories); + if (model != null) + { + result.Add(model); + } + } + + // Some models are special + var isTransientTemplate = messageContext.MessageTemplate != null && messageContext.MessageTemplate.IsTransientRecord(); + + if (!isTransientTemplate) + { + switch (templateName) + { + case MessageTemplateNames.SystemContactUs: + result.Add(new NamedModelPart("Message") + { + ["Subject"] = "Test subject", + ["Message"] = LoremIpsum, + ["SenderEmail"] = "jane@doe.com", + ["SenderName"] = "Jane Doe" + }); + break; + case MessageTemplateNames.ProductQuestion: + result.Add(new NamedModelPart("Message") + { + ["Message"] = LoremIpsum, + ["SenderEmail"] = "jane@doe.com", + ["SenderName"] = "Jane Doe", + ["SenderPhone"] = "123456789" + }); + break; + case MessageTemplateNames.ShareProduct: + result.Add(new NamedModelPart("Message") + { + ["Body"] = LoremIpsum, + ["From"] = "jane@doe.com", + ["To"] = "john@doe.com", + }); + break; + case MessageTemplateNames.ShareWishlist: + result.Add(new NamedModelPart("Wishlist") + { + ["PersonalMessage"] = LoremIpsum, + ["From"] = "jane@doe.com", + ["To"] = "john@doe.com", + }); + break; + case MessageTemplateNames.NewVatSubmittedStoreOwner: + result.Add(new NamedModelPart("VatValidationResult") + { + ["Name"] = "VatName", + ["Address"] = "VatAddress" + }); + break; + } + } + + return result.ToArray(); + } + + private object GetModelFromExpression(string expression, IDictionary models, IDictionary> factories) + { + object currentModel = null; + int dotIndex = 0; + int len = expression.Length; + bool bof = true; + string token = null; + + for (var i = 0; i < len; i++) + { + if (expression[i] == '.') + { + bof = false; + token = expression.Substring(0, i); + } + else if (i == len - 1) + { + // End reached + token = expression; + } + else + { + continue; + } + + if (!models.TryGetValue(token, out currentModel)) + { + if (bof) + { + // It's a simple dot-less expression where the token + // is actually the model name + currentModel = factories.Get(token)?.Invoke(); + } + else + { + // Sub-token, e.g. "Order.Customer" + // Get "Customer" part, this is our property name, NOT the model name + var propName = token.Substring(dotIndex + 1); + // Get parent model "Order" + var parentModel = models.Get(token.Substring(0, dotIndex)); + if (parentModel == null) + break; + + if (parentModel is ITestModel) + { + // When the parent model is a test model, we need to create a random instance + // instead of using the property value (which is null/void in this case) + currentModel = factories.Get(propName)?.Invoke(); + } + else + { + // Get "Customer" property of Order + var fastProp = FastProperty.GetProperty(parentModel.GetType(), propName, PropertyCachingStrategy.Uncached); + if (fastProp != null) + { + // Get "Customer" value + var propValue = fastProp.GetValue(parentModel); + if (propValue != null) + { + currentModel = propValue; + //// Resolve logical model name... + //var modelName = _modelProvider.ResolveModelName(propValue); + //if (modelName != null) + //{ + // // ...and create the value + // currentModel = factories.Get(modelName)?.Invoke(); + //} + } + } + } + } + + if (currentModel == null) + break; + + // Put it in dict as e.g. "Order.Customer" + models[token] = currentModel; + } + + if (!bof) + { + dotIndex = i; + } + } + + return currentModel; + } + + private object GetRandomEntity(Expression> predicate) where T : BaseEntity, new() + { + var dbSet = _services.DbContext.Set(); + + var query = dbSet.Where(predicate); + + // Determine how many entities match the given predicate + var count = query.Count(); + + object result; + + if (count > 0) + { + // Fetch a random one + var skip = CommonHelper.GenerateRandomInteger(0, count); + result = query.OrderBy(x => x.Id).Skip(() => skip).FirstOrDefault(); + } + else + { + // No entity macthes the predicate. Provide a fallback test entity + var entity = Activator.CreateInstance(); + result = _templateEngine.CreateTestModelFor(entity, entity.GetUnproxiedType().Name); + } + + return result; + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs new file mode 100644 index 0000000000..b88fda1d9a --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs @@ -0,0 +1,466 @@ +using System; +using System.Collections.Generic; +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; +using SmartStore.Core.Html; +using SmartStore.Core.Plugins; +using SmartStore.Services.Catalog; +using SmartStore.Services.Common; +using SmartStore.Services.Customers; +using SmartStore.Services.Directory; +using SmartStore.Services.Localization; +using SmartStore.Services.Media; +using SmartStore.Services.Orders; +using SmartStore.Services.Payments; + +namespace SmartStore.Services.Messages +{ + public partial class MessageModelProvider + { + protected virtual object CreateModelPart(Order part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var allow = new HashSet + { + nameof(part.Id), + nameof(part.OrderNumber), + nameof(part.OrderGuid), + nameof(part.StoreId), + nameof(part.OrderStatus), + nameof(part.PaymentStatus), + nameof(part.ShippingStatus), + nameof(part.CustomerTaxDisplayType), + nameof(part.TaxRatesDictionary), + nameof(part.VatNumber), + nameof(part.AffiliateId), + nameof(part.CustomerIp), + nameof(part.CardType), + nameof(part.CardName), + nameof(part.MaskedCreditCardNumber), + nameof(part.DirectDebitAccountHolder), + nameof(part.DirectDebitBankCode), // TODO: (mc) Liquid > Bank data (?) + nameof(part.PurchaseOrderNumber), + nameof(part.ShippingMethod), + nameof(part.PaymentMethodSystemName), + nameof(part.ShippingRateComputationMethodSystemName) + // TODO: (mc) Liquid > More whitelisting? + }; + + var m = new HybridExpando(part, allow, MemberOptMethod.Allow); + var d = m as dynamic; + + d.ID = part.Id; + d.Billing = CreateModelPart(part.BillingAddress, 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); + d.ConditionsOfUse = GetTopic("ConditionsOfUse", messageContext); + d.Status = part.OrderStatus.GetLocalizedEnum(_services.Localization, messageContext.Language.Id); + d.CreatedOn = ToUserDate(part.CreatedOnUtc, messageContext); + d.PaidOn = ToUserDate(part.PaidDateUtc, messageContext); + + // Payment method + var paymentMethodName = part.PaymentMethodSystemName; + var paymentMethod = _services.Resolve().GetProvider(part.PaymentMethodSystemName); + if (paymentMethod != null) + { + paymentMethodName = GetLocalizedValue(messageContext, paymentMethod.Metadata, nameof(paymentMethod.Metadata.FriendlyName), x => x.FriendlyName); + } + d.PaymentMethod = paymentMethodName.NullEmpty(); + + d.Url = part.Customer != null && !part.Customer.IsGuest() + ? BuildActionUrl("Details", "Order", new { id = part.Id, area = "" }, messageContext) + : null; + + // Overrides + m.Properties["OrderNumber"] = part.GetOrderNumber().NullEmpty(); + m.Properties["AcceptThirdPartyEmailHandOver"] = GetBoolResource(part.AcceptThirdPartyEmailHandOver, messageContext); + + // Items, Totals & Co. + d.Items = part.OrderItems.Where(x => x.Product != null).Select(x => CreateModelPart(x, messageContext)).ToList(); + d.Totals = CreateOrderTotalsPart(part, messageContext); + + // Checkout Attributes + if (part.CheckoutAttributeDescription.HasValue()) + { + d.CheckoutAttributes = HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText(part.CheckoutAttributeDescription)).NullEmpty(); + } + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateOrderTotalsPart(Order order, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(order, nameof(order)); + + var language = messageContext.Language; + var currencyService = _services.Resolve(); + var paymentService = _services.Resolve(); + var priceFormatter = _services.Resolve(); + var taxSettings = _services.Settings.LoadSetting(messageContext.Store.Id); + + var taxRates = new SortedDictionary(); + Money cusTaxTotal = null; + Money cusDiscount = null; + Money cusRounding = null; + Money cusTotal = null; + + var subTotals = GetSubTotals(order, messageContext); + + // Shipping + bool dislayShipping = order.ShippingStatus != ShippingStatus.ShippingNotRequired; + + // Payment method fee + bool displayPaymentMethodFee = true; + if (order.PaymentMethodAdditionalFeeExclTax == decimal.Zero) + { + displayPaymentMethodFee = false; + } + + // Tax + bool displayTax = true; + bool displayTaxRates = true; + if (taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + { + displayTax = false; + displayTaxRates = false; + } + else + { + if (order.OrderTax == 0 && taxSettings.HideZeroTax) + { + displayTax = false; + displayTaxRates = false; + } + else + { + taxRates = new SortedDictionary(); + foreach (var tr in order.TaxRatesDictionary) + { + taxRates.Add(tr.Key, currencyService.ConvertCurrency(tr.Value, order.CurrencyRate)); + } + + displayTaxRates = taxSettings.DisplayTaxRates && taxRates.Count > 0; + displayTax = !displayTaxRates; + + cusTaxTotal = FormatPrice(order.OrderTax, order, messageContext); + } + } + + // Discount + bool dislayDiscount = false; + if (order.OrderDiscount > decimal.Zero) + { + cusDiscount = FormatPrice(-order.OrderDiscount, order, messageContext); + dislayDiscount = true; + } + + // Total + var roundingAmount = decimal.Zero; + var orderTotal = order.GetOrderTotalInCustomerCurrency(currencyService, paymentService, out roundingAmount); + cusTotal = FormatPrice(orderTotal, order.CustomerCurrencyCode, messageContext); + + // Rounding + if (roundingAmount != decimal.Zero) + { + cusRounding = FormatPrice(roundingAmount, order.CustomerCurrencyCode, messageContext); + } + + // Model + dynamic m = new ExpandoObject(); + + 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; + m.Total = cusTotal; + m.IsGross = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + + // TaxRates + m.TaxRates = !displayTaxRates ? (object[])null : taxRates.Select(x => + { + return new + { + Rate = T("Order.TaxRateLine", language.Id, priceFormatter.FormatTaxRate(x.Key)).Text, + Value = FormatPrice(x.Value, order, messageContext) + }; + }).ToArray(); + + + // Gift Cards + m.GiftCardUsage = order.GiftCardUsageHistory.Count == 0 ? (object[])null : order.GiftCardUsageHistory.Select(x => + { + return new + { + GiftCard = T("Order.GiftCardInfo", language.Id, x.GiftCard.GiftCardCouponCode).Text, + UsedAmount = FormatPrice(-x.UsedValue, order, messageContext), + RemainingAmount = FormatPrice(x.GiftCard.GetGiftCardRemainingAmount(), order, messageContext) + }; + }).ToArray(); + + // Reward Points + m.RedeemedRewardPoints = order.RedeemedRewardPointsEntry == null ? null : new + { + Title = T("Order.RewardPoints", language.Id, -order.RedeemedRewardPointsEntry.Points).Text, + Amount = FormatPrice(-order.RedeemedRewardPointsEntry.UsedAmount, order, messageContext) + }; + + return m; + } + + 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(); + + var isNet = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax; + + 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 + 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) + Money cusSubTotalDiscount = null; + bool dislaySubTotalDiscount = false; + if (subTotalDiscount > decimal.Zero) + { + cusSubTotalDiscount = FormatPrice(-subTotalDiscount, order, messageContext); + dislaySubTotalDiscount = true; + } + + return (cusSubTotal, cusSubTotalDiscount, cusShipTotal, cusPaymentMethodFee, dislaySubTotalDiscount); + } + + protected virtual object CreateModelPart(OrderItem part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var productAttributeParser = _services.Resolve(); + var downloadService = _services.Resolve(); + 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) }, + { "AttributeDescription", part.AttributeDescription.NullEmpty() }, + { "Weight", part.ItemWeight }, + { "TaxRate", part.TaxRate }, + { "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); + + return m; + } + + protected virtual object CreateModelPart(OrderNote part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Id", part.Id }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) }, + { "Text", part.FormatOrderNoteText().NullEmpty() } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(ShoppingCartItem part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Id", part.Id }, + { "Quantity", part.Quantity }, + { "Product", CreateModelPart(part.Product, messageContext, part.AttributesXml) }, + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Shipment part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var orderItems = new List(); + var orderItemIds = part.ShipmentItems.Select(x => x.OrderItemId).ToArray(); + foreach (var orderItemId in orderItemIds) + { + var orderItem = _services.Resolve().GetOrderItemById(orderItemId); + if (orderItem != null) + { + orderItems.Add(orderItem); + } + } + + var m = new Dictionary + { + { "Id", part.Id }, + { "TrackingNumber", part.TrackingNumber.NullEmpty() }, + { "TotalWeight", part.TotalWeight }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) }, + { "DeliveredOn", ToUserDate(part.DeliveryDateUtc, messageContext) }, + { "ShippedOn", ToUserDate(part.ShippedDateUtc, messageContext) }, + { "Url", BuildActionUrl("ShipmentDetails", "Order", new { id = part.Id, area = "" }, messageContext)}, + { "Items", orderItems.Where(x => x.Product != null).Select(x => CreateModelPart(x, messageContext)).ToList() }, + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(RecurringPayment part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Id", part.Id }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) }, + { "StartedOn", ToUserDate(part.StartDateUtc, messageContext) }, + { "NextOn", ToUserDate(part.NextPaymentDate, messageContext) }, + { "CycleLength", part.CycleLength }, + { "CyclePeriod", part.CyclePeriod.GetLocalizedEnum(_services.Localization, messageContext.Language.Id) }, + { "CyclesRemaining", part.CyclesRemaining }, + { "TotalCycles", part.TotalCycles }, + { "Url", BuildActionUrl("Edit", "RecurringPayment", new { id = part.Id, area = "admin" }, messageContext) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(ReturnRequest part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Id", part.Id }, + { "Reason", part.ReasonForReturn.NullEmpty() }, + { "Status", part.ReturnRequestStatus.GetLocalizedEnum(_services.Localization, messageContext.Language.Id) }, + { "RequestedAction", part.RequestedAction.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) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs new file mode 100644 index 0000000000..9e5a8856e2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs @@ -0,0 +1,188 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Plugins; +using SmartStore.Services.Catalog; +using SmartStore.Services.Common; +using SmartStore.Services.Customers; +using SmartStore.Services.Topics; +using SmartStore.Services.Media; +using SmartStore.Services.Directory; +using SmartStore.Services.Localization; +using SmartStore.Core.Domain.Orders; +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.GetLeftPart(UriPartial.Authority) + url.EnsureStartsWith("/"); + } + + private string BuildRouteUrl(object routeValues, MessageContext ctx) + { + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + _urlHelper.RouteUrl(routeValues); + } + + private string BuildRouteUrl(string routeName, object routeValues, MessageContext ctx) + { + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + _urlHelper.RouteUrl(routeName, routeValues); + } + + private string BuildActionUrl(string action, string controller, object routeValues, MessageContext ctx) + { + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + _urlHelper.Action(action, controller, routeValues); + } + + private void PublishModelPartCreatedEvent(T source, dynamic part) where T : class + { + _services.EventPublisher.Publish(new MessageModelPartCreatedEvent(source, part)); + } + + private string GetLocalizedValue(MessageContext messageContext, ProviderMetadata metadata, string propertyName, Expression> fallback) + { + // TODO: (mc) this actually belongs to PluginMediator, but we simply cannot add a dependency to framework from here. Refactor later! + + Guard.NotNull(metadata, nameof(metadata)); + + string systemName = metadata.SystemName; + var resourceName = metadata.ResourceKeyPattern.FormatInvariant(metadata.SystemName, propertyName); + string result = _services.Localization.GetResource(resourceName, messageContext.Language.Id, false, "", true); + + if (result.IsEmpty()) + result = fallback.Compile()(metadata); + + return result; + } + + private object GetTopic(string topicSystemName, MessageContext ctx) + { + var topicService = _services.Resolve(); + + // Load by store + var topic = topicService.GetTopicBySystemName(topicSystemName, ctx.StoreId ?? 0, false); + + string body = topic?.GetLocalized(x => x.Body, ctx.Language); + if (body.HasValue()) + { + body = HtmlUtils.RelativizeFontSizes(body); + } + + return new + { + Title = topic?.GetLocalized(x => x.Title, ctx.Language).Value.NullEmpty(), + Body = body.NullEmpty() + }; + } + + private string GetDisplayNameForCustomer(Customer customer) + { + return customer.GetFullName().NullEmpty() ?? customer.Username ?? customer.FindEmail(); + } + + private string GetBoolResource(bool value, MessageContext ctx) + { + return _services.Localization.GetResource(value ? "Common.Yes" : "Common.No", ctx.Language.Id); + } + + private DateTime? ToUserDate(DateTime? utcDate, MessageContext messageContext) + { + if (utcDate == null) + return null; + + return _services.DateTimeHelper.ConvertToUserTime( + utcDate.Value, + TimeZoneInfo.Utc, + _services.DateTimeHelper.GetCustomerTimeZone(messageContext.Customer)); + } + + 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, (Currency)null, messageContext, exchangeRate); + } + + private Money FormatPrice(decimal price, string currencyCode, MessageContext messageContext, decimal exchangeRate = 1) + { + 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 new Money(price, currency); + } + + private PictureInfo GetPictureFor(Product product, string attributesXml) + { + var pictureService = _services.PictureService; + var attrParser = _services.Resolve(); + + PictureInfo pictureInfo = null; + + if (attributesXml.HasValue()) + { + var combination = attrParser.FindProductVariantAttributeCombination(product.Id, attributesXml); + + if (combination != null) + { + var picturesIds = combination.GetAssignedPictureIds(); + if (picturesIds != null && picturesIds.Length > 0) + { + pictureInfo = pictureService.GetPictureInfo(picturesIds[0]); + } + } + } + + if (pictureInfo == null) + { + pictureInfo = pictureService.GetPictureInfo(product.MainPictureId); + } + + if (pictureInfo == null && !product.VisibleIndividually && product.ParentGroupedProductId > 0) + { + pictureInfo = pictureService.GetPictureInfo(pictureService.GetPicturesByProductId(product.ParentGroupedProductId, 1).FirstOrDefault()); + } + + return pictureInfo; + } + + private object[] Concat(params object[] values) + { + return values.Where(x => CommonHelper.IsTruthy(x)).ToArray(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs new file mode 100644 index 0000000000..8cc14ed9d5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs @@ -0,0 +1,1182 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Web.Mvc; +using SmartStore.Collections; +using SmartStore.ComponentModel; +using SmartStore.Core; +using SmartStore.Core.Domain.Blogs; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Media; +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; +using SmartStore.Core.Html; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Services.Catalog; +using SmartStore.Services.Catalog.Extensions; +using SmartStore.Services.Common; +using SmartStore.Services.Customers; +using SmartStore.Services.Directory; +using SmartStore.Services.Forums; +using SmartStore.Services.Localization; +using SmartStore.Services.Media; +using SmartStore.Services.Orders; +using SmartStore.Services.Seo; +using SmartStore.Templating; +using SmartStore.Utilities; + +namespace SmartStore.Services.Messages +{ + public enum ModelTreeMemberKind + { + Primitive, + Complex, + Collection, + Root + } + + public class ModelTreeMember + { + public string Name { get; set; } + public ModelTreeMemberKind Kind { get; set; } + } + + public partial class MessageModelProvider : IMessageModelProvider + { + private readonly ICommonServices _services; + private readonly ITemplateEngine _templateEngine; + private readonly IMessageTemplateService _messageTemplateService; + private readonly IEmailAccountService _emailAccountService; + private readonly UrlHelper _urlHelper; + + public MessageModelProvider( + ICommonServices services, + ITemplateEngine templateEngine, + IMessageTemplateService messageTemplateService, + IEmailAccountService emailAccountService, + UrlHelper urlHelper) + { + _services = services; + _templateEngine = templateEngine; + _messageTemplateService = messageTemplateService; + _emailAccountService = emailAccountService; + _urlHelper = urlHelper; + + T = NullLocalizer.InstanceEx; + Logger = NullLogger.Instance; + } + + public LocalizerEx T { get; set; } + public ILogger Logger { get; set; } + + public virtual void AddGlobalModelParts(MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + + var model = messageContext.Model; + + model["Context"] = new Dictionary + { + { "TemplateName", messageContext.MessageTemplate.Name }, + { "LanguageId", messageContext.Language.Id }, + { "LanguageCulture", messageContext.Language.LanguageCulture }, + { "LanguageRtl", messageContext.Language.Rtl }, + { "BaseUrl", messageContext.BaseUri.ToString() } + }; + + dynamic email = new ExpandoObject(); + email.Email = messageContext.EmailAccount.Email; + email.SenderName = messageContext.EmailAccount.DisplayName; + email.DisplayName = messageContext.EmailAccount.DisplayName; // Alias + model["Email"] = email; + + model["Theme"] = CreateThemeModelPart(messageContext); + model["Customer"] = CreateModelPart(messageContext.Customer, 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)); + Guard.NotNull(messageContext, nameof(messageContext)); + + var model = messageContext.Model; + + name = name.NullEmpty() ?? ResolveModelName(part); + + object modelPart = null; + + switch (part) + { + case INamedModelPart x: + modelPart = x; + break; + case IModelPart x: + MergeModelBag(x, model, messageContext); + break; + case Order x: + modelPart = CreateModelPart(x, messageContext); + break; + case Product x: + modelPart = CreateModelPart(x, messageContext); + break; + case Customer x: + modelPart = CreateModelPart(x, messageContext); + break; + case Address x: + modelPart = CreateModelPart(x, messageContext); + break; + case Shipment x: + modelPart = CreateModelPart(x, messageContext); + break; + case OrderNote x: + modelPart = CreateModelPart(x, messageContext); + break; + case RecurringPayment x: + modelPart = CreateModelPart(x, messageContext); + break; + case ReturnRequest x: + modelPart = CreateModelPart(x, messageContext); + break; + case GiftCard x: + modelPart = CreateModelPart(x, messageContext); + break; + case NewsLetterSubscription x: + modelPart = CreateModelPart(x, messageContext); + break; + case Campaign x: + modelPart = CreateModelPart(x, messageContext); + break; + case ProductReview x: + modelPart = CreateModelPart(x, messageContext); + break; + case BlogComment x: + modelPart = CreateModelPart(x, messageContext); + break; + case NewsComment x: + modelPart = CreateModelPart(x, messageContext); + break; + case ForumTopic x: + modelPart = CreateModelPart(x, messageContext); + break; + 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 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 (partType.IsPlainObjectType() && !partType.IsAnonymous()) + { + var evt = new MessageModelPartMappingEvent(part); + _services.EventPublisher.Publish(evt); + + if (evt.Result != null && !object.ReferenceEquals(evt.Result, part)) + { + modelPart = evt.Result; + name = evt.ModelPartName.NullEmpty() ?? ResolveModelName(evt.Result) ?? name; + } + else + { + modelPart = part; + } + + modelPart = evt.Result ?? part; + name = evt.ModelPartName.NullEmpty() ?? name; + } + + break; + } + + if (modelPart != null) + { + if (name.IsEmpty()) + { + throw new SmartException($"Could not resolve a model key for part '{modelPart.GetType().Name}'. Use an instance of 'NamedModelPart' class to pass model with name."); + } + + if (model.TryGetValue(name, out var existing)) + { + // A model part with the same name exists in model already... + if (existing is IDictionary x) + { + // but it's a dictionary which we can easily merge with + x.Merge(FastProperty.ObjectToDictionary(modelPart), true); + } + else + { + // Wrap in HybridExpando and merge + var he = new HybridExpando(existing, true); + he.Merge(FastProperty.ObjectToDictionary(modelPart), true); + model[name] = he; + } + } + else + { + // Put part to model as new property + model[name] = modelPart; + } + } + } + + public string ResolveModelName(object model) + { + Guard.NotNull(model, nameof(model)); + + string name = null; + var type = model.GetType(); + + try + { + if (model is BaseEntity be) + { + name = be.GetUnproxiedType().Name; + } + else if (model is ITestModel te) + { + name = te.ModelName; + } + else if (model is INamedModelPart mp) + { + name = mp.ModelPartName; + } + else if (type.IsPlainObjectType()) + { + name = type.Name; + } + } + catch { } + + return name; + } + + #region Global model part handlers + + protected virtual object CreateThemeModelPart(MessageContext messageContext) + { + var m = new Dictionary + { + { "FontFamily", "-apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif" }, + { "BodyBg", "#f2f4f6" }, + { "BodyColor", "#555" }, + { "TitleColor", "#2f3133" }, + { "ContentBg", "#fff" }, + { "ShadeColor", "#e2e2e2" }, + { "LinkColor", "#0066c0" }, + { "BrandPrimary", "#3f51b5" }, + { "BrandSuccess", "#4caf50" }, + { "BrandWarning", "#ff9800" }, + { "BrandDanger", "#f44336" }, + { "MutedColor", "#a5a5a5" }, + }; + + return m; + } + + protected virtual object CreateCompanyModelPart(MessageContext messageContext) + { + var settings = _services.Settings.LoadSetting(messageContext.Store.Id); + dynamic m = new HybridExpando(settings, true); + + m.NameLine = Concat(settings.Salutation, settings.Title, settings.Firstname, settings.Lastname); + m.StreetLine = Concat(settings.Street, settings.Street2); + m.CityLine = Concat(settings.ZipCode, settings.City); + m.CountryLine = Concat(settings.CountryName, settings.Region); + + PublishModelPartCreatedEvent(settings, m); + return m; + } + + protected virtual object CreateBankModelPart(MessageContext messageContext) + { + var settings = _services.Settings.LoadSetting(messageContext.Store.Id); + var m = new HybridExpando(settings, true); + PublishModelPartCreatedEvent(settings, m); + return m; + } + + protected virtual object CreateContactModelPart(MessageContext messageContext) + { + var settings = _services.Settings.LoadSetting(messageContext.Store.Id); + var contact = new HybridExpando(settings, true) as dynamic; + + // Aliases + contact.Phone = new + { + Company = settings.CompanyTelephoneNumber.NullEmpty(), + Hotline = settings.HotlineTelephoneNumber.NullEmpty(), + Mobile = settings.MobileTelephoneNumber.NullEmpty(), + Fax = settings.CompanyFaxNumber.NullEmpty() + }; + + contact.Email = new + { + Company = settings.CompanyEmailAddress.NullEmpty(), + Webmaster = settings.WebmasterEmailAddress.NullEmpty(), + Support = settings.SupportEmailAddress.NullEmpty(), + Contact = settings.ContactEmailAddress.NullEmpty() + }; + + PublishModelPartCreatedEvent(settings, contact); + + return contact; + } + + #endregion + + #region Generic model part handlers + + protected virtual void MergeModelBag(IModelPart part, IDictionary model, MessageContext messageContext) + { + if (!(model.Get("Bag") is IDictionary bag)) + { + model["Bag"] = bag = new Dictionary(); + } + + var source = part as IDictionary; + bag.Merge(source); + } + + #endregion + + #region Entity specific model part handlers + + protected virtual object CreateModelPart(Store part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var host = messageContext.BaseUri.ToString(); + var logoInfo = _services.PictureService.GetPictureInfo(messageContext.Store.LogoPictureId); + + // Issue: https://github.com/smartstore/SmartStoreNET/issues/1321 + + var m = new Dictionary + { + { "Email", messageContext.EmailAccount.Email }, + { "EmailName", messageContext.EmailAccount.DisplayName }, + { "Name", part.Name }, + { "Url", host }, + { "Cdn", part.ContentDeliveryNetwork }, + { "PrimaryStoreCurrency", part.PrimaryStoreCurrency?.CurrencyCode }, + { "PrimaryExchangeRateCurrency", part.PrimaryExchangeRateCurrency?.CurrencyCode }, + { "Logo", CreateModelPart(logoInfo, messageContext, host, null, new Size(400, 75)) }, + { "Company", CreateCompanyModelPart(messageContext) }, + { "Contact", CreateContactModelPart(messageContext) }, + { "Bank", CreateBankModelPart(messageContext) }, + { "Copyright", T("Content.CopyrightNotice", messageContext.Language.Id, DateTime.Now.Year.ToString(), part.Name).Text } + }; + + var he = new HybridExpando(true); + he.Merge(m, true); + + PublishModelPartCreatedEvent(part, he); + + return he; + } + + protected virtual object CreateModelPart(PictureInfo part, MessageContext messageContext, + string href, + int? targetSize = null, + Size? clientMaxSize = null, + string alt = null) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotEmpty(href, nameof(href)); + + if (part == null) + return null; + + var width = part.Width; + var height = part.Height; + + if (width.HasValue && height.HasValue && (targetSize.HasValue || clientMaxSize.HasValue)) + { + var maxSize = clientMaxSize ?? new Size(targetSize.Value, targetSize.Value); + var size = ImagingHelper.Rescale(new Size(width.Value, height.Value), maxSize); + width = size.Width; + height = size.Height; + } + + var m = new + { + Src = _services.PictureService.GetUrl(part, targetSize.GetValueOrDefault(), FallbackPictureType.NoFallback, messageContext.BaseUri.ToString()), + Href = href, + Width = width, + Height = height, + Alt = alt + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Product part, MessageContext messageContext, string attributesXml = null) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var mediaSettings = _services.Resolve(); + var shoppingCartSettings = _services.Resolve(); + var catalogSettings = _services.Resolve(); + var deliveryTimeService = _services.Resolve(); + var quantityUnitService = _services.Resolve(); + var productUrlHelper = _services.Resolve(); + + var currency = _services.WorkContext.WorkingCurrency; + 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 + { + { "Id", part.Id }, + { "Sku", catalogSettings.ShowProductSku ? part.Sku : null }, + { "Name", name }, + { "Description", part.GetLocalized(x => x.ShortDescription, messageContext.Language).Value.NullEmpty() }, + { "StockQuantity", part.StockQuantity }, + { "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) }, + { "DeliveryTime", null }, + { "QtyUnit", null } + }; + + if (shoppingCartSettings.ShowDeliveryTimes && part.IsShipEnabled) + { + 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 }, + }; + } + } + + if (quantityUnitService.GetQuantityUnitById(part.QuantityUnitId) is QuantityUnit qu) + { + m["QtyUnit"] = qu.GetLocalized(x => x.Name, messageContext.Language).Value; + } + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Customer part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + 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(); + decimal rewardPointsAmountBase = _services.Resolve().ConvertRewardPointsToAmount(rewardPointsBalance); + decimal rewardPointsAmount = _services.Resolve().ConvertFromPrimaryStoreCurrency(rewardPointsAmountBase, _services.WorkContext.WorkingCurrency); + + var m = new Dictionary + { + ["Id"] = part.Id, + ["CustomerGuid"] = part.CustomerGuid, + ["Username"] = part.Username, + ["Email"] = email, + ["IsTaxExempt"] = part.IsTaxExempt, + ["LastIpAddress"] = part.LastIpAddress, + ["CreatedOn"] = ToUserDate(part.CreatedOnUtc, messageContext), + ["LastLoginOn"] = ToUserDate(part.LastLoginDateUtc, messageContext), + ["LastActivityOn"] = ToUserDate(part.LastActivityDateUtc, messageContext), + + ["FullName"] = GetDisplayNameForCustomer(part).NullEmpty(), + ["VatNumber"] = part.GetAttribute(SystemCustomerAttributeNames.VatNumber).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, area = "" }, + messageContext), + ["AccountActivationURL"] = accountActivationToken == null ? null : BuildActionUrl("activation", "customer", + new { token = part.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken), email, area = "" }, + messageContext), + + // Addresses + ["BillingAddress"] = CreateModelPart(part.BillingAddress ?? new Address(), messageContext), + ["ShippingAddress"] = part.ShippingAddress == null ? null : CreateModelPart(part.ShippingAddress, messageContext), + + // Reward Points + ["RewardPointsAmount"] = rewardPointsAmount, + ["RewardPointsBalance"] = FormatPrice(rewardPointsAmount, messageContext), + ["RewardPointsHistory"] = part.RewardPointsHistory.Count == 0 ? null : part.RewardPointsHistory.Select(x => CreateModelPart(x, messageContext)).ToList(), + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(GiftCard part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Id", part.Id }, + { "SenderName", part.SenderName.NullEmpty() }, + { "SenderEmail", part.SenderEmail.NullEmpty() }, + { "RecipientName", part.RecipientName.NullEmpty() }, + { "RecipientEmail", part.RecipientEmail.NullEmpty() }, + { "Amount", FormatPrice(part.Amount, messageContext) }, + { "CouponCode", part.GiftCardCouponCode.NullEmpty() } + }; + + // Message + var message = (string)null; + if (part.Message.HasValue()) + { + message = HtmlUtils.StripTags(part.Message); + } + m["Message"] = message; + + // RemainingAmount + Money remainingAmount = null; + var order = part?.PurchasedWithOrderItem?.Order; + if (order != null) + { + remainingAmount = FormatPrice(part.GetGiftCardRemainingAmount(), order, messageContext); + } + m["RemainingAmount"] = remainingAmount; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(NewsLetterSubscription part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var gid = part.NewsLetterSubscriptionGuid; + + var m = new Dictionary + { + { "Id", part.Id }, + { "Email", part.Email.NullEmpty() }, + { "ActivationUrl", gid == Guid.Empty ? null : BuildRouteUrl("NewsletterActivation", new { token = part.NewsLetterSubscriptionGuid, active = true }, messageContext) }, + { "DeactivationUrl", gid == Guid.Empty ? null : BuildRouteUrl("NewsletterActivation", new { token = part.NewsLetterSubscriptionGuid, active = false }, messageContext) } + }; + + var customer = messageContext.Customer; + if (customer != null && customer.Email.IsCaseInsensitiveEqual(part.Email.EmptyNull())) + { + // Set FullName only if a customer account exists for the subscriber's email address. + m["FullName"] = customer.GetFullName(); + } + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Campaign part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var protocol = messageContext.BaseUri.Scheme; + var host = messageContext.BaseUri.Authority + messageContext.BaseUri.AbsolutePath; + var body = HtmlUtils.RelativizeFontSizes(part.Body.EmptyNull()); + + // We must render the body separately + body = _templateEngine.Render(body, messageContext.Model, messageContext.FormatProvider); + + var m = new Dictionary + { + { "Id", part.Id }, + { "Subject", part.Subject.NullEmpty() }, + { "Body", WebHelper.MakeAllUrlsAbsolute(body, protocol, host).NullEmpty() }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(ProductReview part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Title", part.Title.NullEmpty() }, + { "Text", HtmlUtils.StripTags(part.ReviewText).NullEmpty() }, + { "Rating", part.Rating } + }; + + PublishModelPartCreatedEvent(part, m); + + 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)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Subject", part.Subject.NullEmpty() }, + { "Text", part.FormatPrivateMessageText().NullEmpty() }, + { "FromEmail", part.FromCustomer?.FindEmail().NullEmpty() }, + { "ToEmail", part.ToCustomer?.FindEmail().NullEmpty() }, + { "FromName", part.FromCustomer?.GetFullName().NullEmpty() }, + { "ToName", part.ToCustomer?.GetFullName().NullEmpty() }, + { "Url", BuildActionUrl("View", "PrivateMessages", new { id = part.Id, area = "" }, messageContext) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(BlogComment part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "PostTitle", part.BlogPost.Title.NullEmpty() }, + { "PostUrl", BuildRouteUrl("BlogPost", new { SeName = part.BlogPost.GetSeName(part.BlogPost.LanguageId, ensureTwoPublishedLanguages: false) }, messageContext) }, + { "Text", part.CommentText.NullEmpty() } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(NewsComment part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "NewsTitle", part.NewsItem.Title.NullEmpty() }, + { "Title", part.CommentTitle.NullEmpty() }, + { "Text", HtmlUtils.StripTags(part.CommentText).NullEmpty() }, + { "NewsUrl", BuildRouteUrl("NewsItem", new { SeName = part.NewsItem.GetSeName(messageContext.Language.Id) }, messageContext) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(ForumTopic part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var pageIndex = messageContext.Model.GetFromBag("TopicPageIndex"); + + var url = pageIndex > 0 ? + BuildRouteUrl("TopicSlugPaged", new { id = part.Id, slug = part.GetSeName(), page = pageIndex }, messageContext) : + BuildRouteUrl("TopicSlug", new { id = part.Id, slug = part.GetSeName() }, messageContext); + + var m = new Dictionary + { + { "Subject", part.Subject.NullEmpty() }, + { "NumReplies", part.NumReplies }, + { "NumPosts", part.NumPosts }, + { "NumViews", part.Views }, + { "Body", part.GetFirstPost(_services.Resolve())?.FormatPostText().NullEmpty() }, + { "Url", url }, + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(ForumPost part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Author", part.Customer.FormatUserName().NullEmpty() }, + { "Body", part.FormatPostText().NullEmpty() } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + 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).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) }, + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Address part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var settings = _services.Resolve(); + var languageId = messageContext.Language?.Id ?? messageContext.LanguageId; + + var salutation = part.Salutation.NullEmpty(); + var title = part.Title.NullEmpty(); + var company = settings.CompanyEnabled ? part.Company : null; + var firstName = part.FirstName.NullEmpty(); + var lastName = part.LastName.NullEmpty(); + var street1 = settings.StreetAddressEnabled ? part.Address1 : null; + 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)?.Value.NullEmpty() : null; + var state = settings.StateProvinceEnabled ? part.StateProvince?.GetLocalized(x => x.Name, languageId ?? 0)?.Value.NullEmpty() : null; + + var m = new Dictionary + { + { "Title", title }, + { "Salutation", salutation }, + { "FullSalutation", part.GetFullSalutaion().NullEmpty() }, + { "FullName", part.GetFullName(false).NullEmpty() }, + { "Company", company }, + { "FirstName", firstName }, + { "LastName", lastName }, + { "Street1", street1 }, + { "Street2", street2 }, + { "Country", country }, + { "CountryId", part.Country?.Id }, + { "CountryAbbrev2", settings.CountryEnabled ? part.Country?.TwoLetterIsoCode.NullEmpty() : null }, + { "CountryAbbrev3", settings.CountryEnabled ? part.Country?.ThreeLetterIsoCode.NullEmpty() : null }, + { "State", state }, + { "StateAbbrev", settings.StateProvinceEnabled ? part.StateProvince?.Abbreviation.NullEmpty() : null }, + { "City", city }, + { "ZipCode", zip }, + { "Email", part.Email.NullEmpty() }, + { "Phone", settings.PhoneEnabled ? part.PhoneNumber : null }, + { "Fax", settings.FaxEnabled ? part.FaxNumber : null } + }; + + m["NameLine"] = Concat(salutation, title, firstName, lastName); + m["StreetLine"] = Concat(street1, street2); + m["CityLine"] = Concat(zip, city); + m["CountryLine"] = Concat(country, state); + + PublishModelPartCreatedEvent
(part, m); + + return m; + } + + protected virtual object CreateModelPart(RewardPointsHistory part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "Id", part.Id }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) }, + { "Message", part.Message.NullEmpty() }, + { "Points", part.Points }, + { "PointsBalance", part.PointsBalance }, + { "UsedAmount", part.UsedAmount } + }; + + PublishModelPartCreatedEvent(part, m); + + 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 + + public TreeNode GetLastModelTree(string messageTemplateName) + { + Guard.NotEmpty(messageTemplateName, nameof(messageTemplateName)); + + var template = _messageTemplateService.GetMessageTemplateByName(messageTemplateName, _services.StoreContext.CurrentStore.Id); + + if (template != null) + { + return GetLastModelTree(template); + } + + return null; + } + + public TreeNode GetLastModelTree(MessageTemplate template) + { + Guard.NotNull(template, nameof(template)); + + if (template.LastModelTree.IsEmpty()) + { + return null; + } + + return Newtonsoft.Json.JsonConvert.DeserializeObject>(template.LastModelTree); + } + + public TreeNode BuildModelTree(TemplateModel model) + { + Guard.NotNull(model, nameof(model)); + + var root = new TreeNode(new ModelTreeMember { Name = "Model", Kind = ModelTreeMemberKind.Root }); + + foreach (var kvp in model) + { + root.Append(BuildModelTreePart(kvp.Key, kvp.Value)); + } + + return root; + } + + private TreeNode BuildModelTreePart(string modelName, object instance) + { + var t = instance?.GetType(); + TreeNode node = null; + + if (t == null || t.IsPredefinedType()) + { + node = new TreeNode(new ModelTreeMember { Name = modelName, Kind = ModelTreeMemberKind.Primitive }); + } + else if (t.IsSequenceType() && !(instance is IDictionary)) + { + node = new TreeNode(new ModelTreeMember { Name = modelName, Kind = ModelTreeMemberKind.Collection }); + } + else + { + node = new TreeNode(new ModelTreeMember { Name = modelName, Kind = ModelTreeMemberKind.Complex }); + + if (instance is IDictionary dict) + { + foreach (var kvp in dict) + { + node.Append(BuildModelTreePart(kvp.Key, kvp.Value)); + } + } + else if (instance is IDynamicMetaObjectProvider dyn) + { + foreach (var name in dyn.GetMetaObject(Expression.Constant(dyn)).GetDynamicMemberNames()) + { + // we don't want to go deeper in "pure" dynamic objects + node.Append(new TreeNode(new ModelTreeMember { Name = name, Kind = ModelTreeMemberKind.Primitive })); + } + } + else + { + node.AppendRange(BuildModelTreePartForClass(instance)); + } + } + + return node; + } + + private IEnumerable> BuildModelTreePartForClass(object instance) + { + var type = instance?.GetType(); + + if (type == null) + { + yield break; + } + + foreach (var prop in FastProperty.GetProperties(type).Values) + { + var pi = prop.Property; + + if (pi.PropertyType.IsPredefinedType()) + { + yield return new TreeNode(new ModelTreeMember { Name = prop.Name, Kind = ModelTreeMemberKind.Primitive }); + } + else if (typeof(IDictionary).IsAssignableFrom(pi.PropertyType)) + { + yield return BuildModelTreePart(prop.Name, prop.GetValue(instance)); + } + else if (pi.PropertyType.IsSequenceType()) + { + yield return new TreeNode(new ModelTreeMember { Name = prop.Name, Kind = ModelTreeMemberKind.Collection }); + } + else + { + var node = new TreeNode(new ModelTreeMember { Name = prop.Name, Kind = ModelTreeMemberKind.Complex }); + node.AppendRange(BuildModelTreePartForClass(prop.GetValue(instance))); + yield return node; + } + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs b/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs index 3a69767206..e9e25c6677 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs @@ -13,15 +13,9 @@ namespace SmartStore.Services.Messages { public partial class MessageTemplateService: IMessageTemplateService { - #region Constants - private const string MESSAGETEMPLATES_ALL_KEY = "SmartStore.messagetemplate.all-{0}"; private const string MESSAGETEMPLATES_BY_NAME_KEY = "SmartStore.messagetemplate.name-{0}-{1}"; - private const string MESSAGETEMPLATES_PATTERN_KEY = "SmartStore.messagetemplate."; - - #endregion - - #region Fields + private const string MESSAGETEMPLATES_PATTERN_KEY = "SmartStore.messagetemplate.*"; private readonly IRepository _messageTemplateRepository; private readonly IRepository _storeMappingRepository; @@ -31,10 +25,6 @@ public partial class MessageTemplateService: IMessageTemplateService private readonly IEventPublisher _eventPublisher; private readonly IRequestCache _requestCache; - #endregion - - #region Ctor - public MessageTemplateService( IRequestCache requestCache, IRepository storeMappingRepository, @@ -44,79 +34,46 @@ 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; } - #endregion - - #region Methods - - /// - /// Delete a message template - /// - /// Message template public virtual void DeleteMessageTemplate(MessageTemplate messageTemplate) { - if (messageTemplate == null) - throw new ArgumentNullException("messageTemplate"); + Guard.NotNull(messageTemplate, nameof(messageTemplate)); _messageTemplateRepository.Delete(messageTemplate); _requestCache.RemoveByPattern(MESSAGETEMPLATES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(messageTemplate); } - /// - /// Inserts a message template - /// - /// Message template 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); - - //event notification - _eventPublisher.EntityInserted(messageTemplate); } - /// - /// Updates a message template - /// - /// Message template 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); - - //event notification - _eventPublisher.EntityUpdated(messageTemplate); } - /// - /// Gets a message template - /// - /// Message template identifier - /// Message template public virtual MessageTemplate GetMessageTemplateById(int messageTemplateId) { if (messageTemplateId == 0) @@ -125,16 +82,9 @@ public virtual MessageTemplate GetMessageTemplateById(int messageTemplateId) return _messageTemplateRepository.GetById(messageTemplateId); } - /// - /// Gets a message template - /// - /// Message template name - /// Store identifier - /// Message template 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, () => @@ -155,11 +105,6 @@ public virtual MessageTemplate GetMessageTemplateByName(string messageTemplateNa } - /// - /// Gets all message templates - /// - /// Store identifier; pass 0 to load all records - /// Message template list public virtual IList GetAllMessageTemplates(int storeId) { string key = string.Format(MESSAGETEMPLATES_ALL_KEY, storeId); @@ -168,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 @@ -178,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 @@ -190,19 +135,15 @@ orderby tGroup.Key }); } - /// - /// Create a copy of message template with all depended data - /// - /// Message template - /// Message template copy public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTemplate) { - if (messageTemplate == null) - throw new ArgumentNullException("messageTemplate"); + Guard.NotNull(messageTemplate, nameof(messageTemplate)); var mtCopy = new MessageTemplate { Name = messageTemplate.Name, + To = messageTemplate.To, + ReplyTo = messageTemplate.ReplyTo, BccEmailAddresses = messageTemplate.BccEmailAddresses, Subject = messageTemplate.Subject, Body = messageTemplate.Body, @@ -219,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); } @@ -245,7 +186,5 @@ public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTempla return mtCopy; } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs deleted file mode 100644 index 6fa52460d9..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs +++ /dev/null @@ -1,1277 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using System.Text; -using System.Web; -using SmartStore.Core; -using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Blogs; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Domain.News; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Events; -using SmartStore.Core.Html; -using SmartStore.Core.Plugins; -using SmartStore.Services.Catalog; -using SmartStore.Services.Common; -using SmartStore.Services.Customers; -using SmartStore.Services.Directory; -using SmartStore.Services.Forums; -using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Orders; -using SmartStore.Services.Payments; -using SmartStore.Services.Seo; -using SmartStore.Services.Stores; -using SmartStore.Services.Topics; - -namespace SmartStore.Services.Messages -{ - public partial class MessageTokenProvider : IMessageTokenProvider - { - #region Fields - - private readonly ILanguageService _languageService; - private readonly ILocalizationService _localizationService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly IEmailAccountService _emailAccountService; - private readonly IPriceFormatter _priceFormatter; - private readonly ICurrencyService _currencyService; - private readonly IWebHelper _webHelper; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IDownloadService _downloadService; - private readonly IOrderService _orderService; - private readonly IProviderManager _providerManager; - private readonly IProductAttributeParser _productAttributeParser; - private readonly StoreInformationSettings _storeSettings; - private readonly MessageTemplatesSettings _templatesSettings; - private readonly EmailAccountSettings _emailAccountSettings; - private readonly CatalogSettings _catalogSettings; - private readonly TaxSettings _taxSettings; - private readonly IEventPublisher _eventPublisher; - private readonly CompanyInformationSettings _companyInfoSettings; - private readonly BankConnectionSettings _bankConnectionSettings; - private readonly ContactDataSettings _contactDataSettings; - private readonly ITopicService _topicService; - private readonly ShoppingCartSettings _shoppingCartSettings; - private readonly IDeliveryTimeService _deliveryTimeService; - private readonly IQuantityUnitService _quantityUnitService; - private readonly IUrlRecordService _urlRecordService; - private readonly IStoreService _storeService; - private readonly IGenericAttributeService _attrService; - private readonly IPictureService _pictureService; - private readonly MediaSettings _mediaSettings; - - #endregion - - #region Ctor - - public MessageTokenProvider(ILanguageService languageService, - ILocalizationService localizationService, IDateTimeHelper dateTimeHelper, - IEmailAccountService emailAccountService, - IPriceFormatter priceFormatter, ICurrencyService currencyService, IWebHelper webHelper, - IWorkContext workContext, IStoreContext storeContext, - IDownloadService downloadService, ShoppingCartSettings shoppingCartSettings, - IOrderService orderService, IProviderManager providerManager, - IProductAttributeParser productAttributeParser, - StoreInformationSettings storeSettings, MessageTemplatesSettings templatesSettings, - EmailAccountSettings emailAccountSettings, CatalogSettings catalogSettings, - TaxSettings taxSettings, IEventPublisher eventPublisher, - CompanyInformationSettings companyInfoSettings, BankConnectionSettings bankConnectionSettings, - ContactDataSettings contactDataSettings, ITopicService topicService, - IDeliveryTimeService deliveryTimeService, IQuantityUnitService quantityUnitService, - IUrlRecordService urlRecordService, IStoreService storeService, - IGenericAttributeService attrService, - IPictureService pictureService, - MediaSettings mediaSettings) - { - this._languageService = languageService; - this._localizationService = localizationService; - this._dateTimeHelper = dateTimeHelper; - this._emailAccountService = emailAccountService; - this._priceFormatter = priceFormatter; - this._currencyService = currencyService; - this._webHelper = webHelper; - this._workContext = workContext; - this._storeContext = storeContext; - this._downloadService = downloadService; - this._orderService = orderService; - this._providerManager = providerManager; - this._productAttributeParser = productAttributeParser; - this._storeSettings = storeSettings; - this._templatesSettings = templatesSettings; - this._emailAccountSettings = emailAccountSettings; - this._catalogSettings = catalogSettings; - this._taxSettings = taxSettings; - this._eventPublisher = eventPublisher; - this._companyInfoSettings = companyInfoSettings; - this._bankConnectionSettings = bankConnectionSettings; - this._contactDataSettings = contactDataSettings; - this._topicService = topicService; - this._shoppingCartSettings = shoppingCartSettings; - this._deliveryTimeService = deliveryTimeService; - this._quantityUnitService = quantityUnitService; - this._urlRecordService = urlRecordService; - this._storeService = storeService; - this._attrService = attrService; - this._pictureService = pictureService; - this._mediaSettings = mediaSettings; - } - - #endregion - - #region Utilities - - protected virtual Picture GetPictureFor(Product product, string attributesXml) - { - Picture picture = null; - - if (attributesXml.HasValue()) - { - var combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributesXml); - - if (combination != null) - { - var picturesIds = combination.GetAssignedPictureIds(); - if (picturesIds != null && picturesIds.Length > 0) - picture = _pictureService.GetPictureById(picturesIds[0]); - } - } - - if (picture == null) - { - picture = _pictureService.GetPicturesByProductId(product.Id, 1).FirstOrDefault(); - } - - if (picture == null && !product.VisibleIndividually && product.ParentGroupedProductId > 0) - { - picture = _pictureService.GetPicturesByProductId(product.ParentGroupedProductId, 1).FirstOrDefault(); - } - - return picture; - } - - protected virtual string ProductPictureToHtml(Picture picture, Language language, string productName, string productUrl, string storeLocation) - { - if (picture != null && _mediaSettings.MessageProductThumbPictureSize > 0) - { - var imageUrl = _pictureService.GetPictureUrl(picture, _mediaSettings.MessageProductThumbPictureSize, false, storeLocation); - if (imageUrl.HasValue()) - { - var title = _localizationService.GetResource("Media.Product.ImageLinkTitleFormat", language.Id).FormatInvariant(productName); - var alternate = _localizationService.GetResource("Media.Product.ImageAlternateTextFormat", language.Id).FormatInvariant(productName); - - var polaroid = "padding: 3px; background-color: #fff; border: 1px solid #ccc; border: 1px solid rgba(0,0,0,.2);"; - var style = "max-width: {0}px; max-height: {0}px; {1}".FormatInvariant(_mediaSettings.MessageProductThumbPictureSize, polaroid); - - var image = "\"{1}\"".FormatInvariant(imageUrl, alternate, title, style); - - if (productUrl.IsEmpty()) - return image; - - return "{1}".FormatInvariant(productUrl, image); - } - } - return ""; - } - - /// - /// Convert a collection to a HTML table - /// - /// Order - /// Language identifier - /// HTML table of products - protected virtual string ProductListToHtmlTable(Order order, Language language) - { - var sb = new StringBuilder(); - var storeLocation = _webHelper.GetStoreLocation(false); - - sb.AppendLine(""); - - #region Products - - sb.AppendLine(string.Format("", _templatesSettings.Color1)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Name", language.Id))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Price", language.Id))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Quantity", language.Id))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Total", language.Id))); - sb.AppendLine(""); - - var table = order.OrderItems.ToList(); - for (int i = 0; i <= table.Count - 1; i++) - { - var orderItem = table[i]; - var product = orderItem.Product; - if (product == null) - continue; - - DeliveryTime deliveryTime = null; - - product.MergeWithCombination(orderItem.AttributesXml, _productAttributeParser); - - if (_shoppingCartSettings.ShowDeliveryTimes && product.IsShipEnabled) - { - deliveryTime = _deliveryTimeService.GetDeliveryTimeById(product.DeliveryTimeId ?? 0); - } - - sb.AppendLine(string.Format("", _templatesSettings.Color2)); - - var productName = product.GetLocalized(x => x.Name, language.Id); - var productUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.AttributesXml, product.Id, product.GetSeName()); - - sb.AppendLine(""); - - string unitPriceStr = string.Empty; - switch (order.CustomerTaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - var unitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceExclTax, order.CurrencyRate); - unitPriceStr = _priceFormatter.FormatPrice(unitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - } - break; - case TaxDisplayType.IncludingTax: - { - var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); - unitPriceStr = _priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - } - break; - } - sb.AppendLine(string.Format("", unitPriceStr)); - - var quantityUnit = _quantityUnitService.GetQuantityUnitById(product.QuantityUnitId); - - sb.AppendLine(string.Format("", - orderItem.Quantity, quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name))); - - string priceStr = string.Empty; - switch (order.CustomerTaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - var priceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceExclTax, order.CurrencyRate); - priceStr = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - } - break; - case TaxDisplayType.IncludingTax: - { - var priceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceInclTax, order.CurrencyRate); - priceStr = _priceFormatter.FormatPrice(priceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - } - break; - } - sb.AppendLine(string.Format("", priceStr)); - - sb.AppendLine(""); - } - - #endregion - - #region Checkout Attributes - - if (!String.IsNullOrEmpty(order.CheckoutAttributeDescription)) - { - sb.AppendLine(""); - } - - #endregion - - #region Totals - - string cusSubTotal = string.Empty; - bool dislaySubTotalDiscount = false; - string cusSubTotalDiscount = string.Empty; - string cusShipTotal = string.Empty; - string cusPaymentMethodAdditionalFee = string.Empty; - var taxRates = new SortedDictionary(); - string cusTaxTotal = string.Empty; - string cusDiscount = string.Empty; - string cusTotal = string.Empty; - - //subtotal, shipping, payment method fee - switch (order.CustomerTaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - //subtotal - var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); - cusSubTotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - //discount (applied to order subtotal) - var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); - if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero) - { - cusSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - dislaySubTotalDiscount = true; - } - //shipping - var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); - cusShipTotal = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - //payment method additional fee - var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); - cusPaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - } - break; - case TaxDisplayType.IncludingTax: - { - //subtotal - var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); - cusSubTotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - //discount (applied to order subtotal) - var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); - if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero) - { - cusSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - dislaySubTotalDiscount = true; - } - //shipping - var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); - cusShipTotal = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - //payment method additional fee - var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); - cusPaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - } - break; - } - - //shipping - bool dislayShipping = order.ShippingStatus != ShippingStatus.ShippingNotRequired; - - //payment method fee - bool displayPaymentMethodFee = true; - if (order.PaymentMethodAdditionalFeeExclTax == decimal.Zero) - { - displayPaymentMethodFee = false; - } - - //tax - bool displayTax = true; - bool displayTaxRates = true; - if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) - { - displayTax = false; - displayTaxRates = false; - } - else - { - if (order.OrderTax == 0 && _taxSettings.HideZeroTax) - { - displayTax = false; - displayTaxRates = false; - } - else - { - taxRates = new SortedDictionary(); - foreach (var tr in order.TaxRatesDictionary) - taxRates.Add(tr.Key, _currencyService.ConvertCurrency(tr.Value, order.CurrencyRate)); - - 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; - } - } - - //discount - 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); - dislayDiscount = true; - } - - //total - var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - cusTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - - //subtotal - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.SubTotal", language.Id), cusSubTotal)); - - //discount (applied to order subtotal) - if (dislaySubTotalDiscount) - { - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.SubTotalDiscount", language.Id), cusSubTotalDiscount)); - } - - - //shipping - if (dislayShipping) - { - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Shipping", language.Id), cusShipTotal)); - } - - //payment method fee - if (displayPaymentMethodFee) - { - string paymentMethodFeeTitle = _localizationService.GetResource("Messages.Order.PaymentMethodAdditionalFee", language.Id); - - sb.AppendLine(string.Format("", paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); - } - - //tax - if (displayTax) - { - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Tax", language.Id), cusTaxTotal)); - } - if (displayTaxRates) - { - foreach (var item in taxRates) - { - string taxRate = String.Format(_localizationService.GetResource("Messages.Order.TaxRateLine"), _priceFormatter.FormatTaxRate(item.Key)); - string taxValue = _priceFormatter.FormatPrice(item.Value, true, order.CustomerCurrencyCode, false, language); - - sb.AppendLine(string.Format("", taxRate, taxValue)); - } - } - - //discount - if (dislayDiscount) - { - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.TotalDiscount", language.Id), cusDiscount)); - } - - //gift cards - var gcuhC = order.GiftCardUsageHistory; - foreach (var gcuh in gcuhC) - { - string giftCardText = String.Format(_localizationService.GetResource("Messages.Order.GiftCardInfo", language.Id), HttpUtility.HtmlEncode(gcuh.GiftCard.GiftCardCouponCode)); - string giftCardAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language); - - var remaining = _currencyService.ConvertCurrency(gcuh.GiftCard.GetGiftCardRemainingAmount(), order.CurrencyRate); - var remainingFormatted = _priceFormatter.FormatPrice(remaining, true, false); - var remainingText = _localizationService.GetResource("ShoppingCart.Totals.GiftCardInfo.Remaining", language.Id).FormatInvariant(remainingFormatted); - - sb.AppendLine(string.Format("", - giftCardText, remainingText, giftCardAmount)); - } - - //reward points - if (order.RedeemedRewardPointsEntry != null) - { - string rpTitle = string.Format(_localizationService.GetResource("Messages.Order.RewardPoints", language.Id), -order.RedeemedRewardPointsEntry.Points); - string rpAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language); - - sb.AppendLine(string.Format("", rpTitle, rpAmount)); - } - - //total - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderTotal", language.Id), cusTotal)); - - #endregion - - sb.AppendLine("
{0}{0}{0}{0}
"); - - if (_mediaSettings.MessageProductThumbPictureSize > 0) - { - var pictureHtml = ProductPictureToHtml(GetPictureFor(product, orderItem.AttributesXml), language, productName, productUrl, storeLocation); - if (pictureHtml.HasValue()) - { - sb.AppendLine("
{0}
".FormatInvariant(pictureHtml)); - } - } - - sb.AppendLine("{1}".FormatInvariant(productUrl, HttpUtility.HtmlEncode(productName))); - - //add download link - if (_downloadService.IsDownloadAllowed(orderItem)) - { - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - string downloadUrl = string.Format("{0}download/getdownload/{1}", storeLocation, orderItem.OrderItemGuid); - string downloadLink = string.Format("{1}", downloadUrl, _localizationService.GetResource("Messages.Order.Product(s).Download", language.Id)); - sb.AppendLine("  ("); - sb.AppendLine(downloadLink); - sb.AppendLine(")"); - } - - //deliverytime - if (deliveryTime != null) - { - string deliveryTimeName = HttpUtility.HtmlEncode(deliveryTime.GetLocalized(x => x.Name)); - - sb.AppendLine("
"); - sb.AppendLine("
"); - sb.AppendLine("" + _localizationService.GetResource("Products.DeliveryTime", language.Id) + ""); - sb.AppendLine(""); - sb.AppendLine("" + deliveryTimeName + ""); - sb.AppendLine("
"); - } - - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) - { - sb.AppendLine("
"); - sb.AppendLine(orderItem.AttributeDescription); - } - //sku - if (_catalogSettings.ShowProductSku) - { - if (!String.IsNullOrEmpty(product.Sku)) - { - sb.AppendLine("
"); - sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", language.Id), HttpUtility.HtmlEncode(product.Sku))); - } - } - sb.AppendLine("
{0}{0} {1}{0}
 "); - sb.AppendLine(HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText(order.CheckoutAttributeDescription))); - sb.AppendLine("
 {0} {1}
 {0} {1}
 {0} {1}
 {0} {1}
 {0} {1}
 {0} {1}
 {0} {1}
 {0}
{1}
{2}
 {0} {1}
 {1}{2}
"); - - return sb.ToString(); - } - - /// - /// Convert a collection to a HTML table - /// - /// Shipment - /// Language identifier - /// HTML table of products - protected virtual string ProductListToHtmlTable(Shipment shipment, Language language) - { - var sb = new StringBuilder(); - - sb.AppendLine(""); - - #region Products - - sb.AppendLine(string.Format("", _templatesSettings.Color1)); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Name", language.Id))); - sb.AppendLine(string.Format("", _localizationService.GetResource("Messages.Order.Product(s).Quantity", language.Id))); - sb.AppendLine(""); - - var table = shipment.ShipmentItems.ToList(); - for (int i = 0; i <= table.Count - 1; i++) - { - var si = table[i]; - var orderItem = _orderService.GetOrderItemById(si.OrderItemId); - if (orderItem == null) - continue; - - var product = orderItem.Product; - if (product == null) - continue; - - sb.AppendLine(string.Format("", _templatesSettings.Color2)); - - var productName = product.GetLocalized(x => x.Name, language.Id); - var productUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.AttributesXml, product.Id, product.GetSeName()); - - sb.AppendLine(""); - - sb.AppendLine(string.Format("", si.Quantity)); - - sb.AppendLine(""); - } - - #endregion - - sb.AppendLine("
{0}{0}
"); - sb.AppendLine("{1}".FormatInvariant(productUrl, HttpUtility.HtmlEncode(productName))); - - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) - { - sb.AppendLine("
"); - sb.AppendLine(orderItem.AttributeDescription); - } - //sku - if (_catalogSettings.ShowProductSku) - { - product.MergeWithCombination(orderItem.AttributesXml, _productAttributeParser); - - if (!String.IsNullOrEmpty(product.Sku)) - { - sb.AppendLine("
"); - sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", language.Id), HttpUtility.HtmlEncode(product.Sku))); - } - } - sb.AppendLine("
{0}
"); - - return sb.ToString(); - } - - protected virtual string TopicToHtml(string systemName, int languageId) - { - var result = ""; - var sb = new StringBuilder(); - sb.AppendLine(""); - - //load by store - var topic = _topicService.GetTopicBySystemName(systemName, _storeContext.CurrentStore.Id); - if (topic == null) - //not found. let's find topic assigned to all stores - topic = _topicService.GetTopicBySystemName(systemName, 0); - - if (topic == null) - return string.Empty; - - sb.AppendLine(""); - sb.AppendLine("
"); - sb.AppendLine(topic.Title); - sb.AppendLine("
"); - sb.AppendLine(topic.Body); - sb.AppendLine("
"); - result = sb.ToString(); - return result; - } - - protected virtual string GetSupplierIdentification() - { - var result = ""; - var sb = new StringBuilder(); - sb.AppendLine(""); - sb.AppendLine(""); - - sb.AppendLine(""); - sb.AppendLine("
"); - - sb.AppendLine(String.Format("{0}
", _companyInfoSettings.CompanyName )); - - if (!String.IsNullOrEmpty(_companyInfoSettings.Salutation)) - { - sb.AppendLine(_companyInfoSettings.Salutation); - } - if (!String.IsNullOrEmpty(_companyInfoSettings.Title)) - { - sb.AppendLine(_companyInfoSettings.Title); - } - if (!String.IsNullOrEmpty(_companyInfoSettings.Firstname)) - { - sb.AppendLine(String.Format("{0} ", _companyInfoSettings.Firstname)); - } - if (!String.IsNullOrEmpty(_companyInfoSettings.Lastname)) - { - sb.AppendLine(_companyInfoSettings.Lastname); - } - sb.AppendLine("
"); - - if (!String.IsNullOrEmpty(_companyInfoSettings.Street)) - { - sb.AppendLine(String.Format("{0} {1}
", _companyInfoSettings.Street, _companyInfoSettings.Street2)); - } - if (!String.IsNullOrEmpty(_companyInfoSettings.ZipCode) || !String.IsNullOrEmpty(_companyInfoSettings.City)) - { - sb.AppendLine(String.Format("{0} {1}
", _companyInfoSettings.ZipCode, _companyInfoSettings.City)); - } - if (!String.IsNullOrEmpty(_companyInfoSettings.CountryName)) - { - sb.AppendLine(_companyInfoSettings.CountryName); - - if(!String.IsNullOrEmpty(_companyInfoSettings.Region)) - { - sb.AppendLine(String.Format(", {0}", _companyInfoSettings.Region)); - } - sb.AppendLine("
"); - } - - sb.AppendLine("
"); - - sb.AppendLine(""); - - if (!String.IsNullOrEmpty(_storeContext.CurrentStore.Url)) - { - sb.AppendLine(String.Format("Url: {0}
", _storeContext.CurrentStore.Url)); - } - if (!String.IsNullOrEmpty(_contactDataSettings.CompanyEmailAddress)) - { - sb.AppendLine(String.Format("Mail: {0}
", _contactDataSettings.CompanyEmailAddress)); - } - if (!String.IsNullOrEmpty(_contactDataSettings.CompanyTelephoneNumber)) - { - sb.AppendLine(String.Format("Fon: {0}
", _contactDataSettings.CompanyTelephoneNumber)); - } - if (!String.IsNullOrEmpty(_contactDataSettings.CompanyFaxNumber)) - { - sb.AppendLine(String.Format("Fax: {0}
", _contactDataSettings.CompanyFaxNumber)); - } - - sb.AppendLine("
"); - - sb.AppendLine(""); - - if (!String.IsNullOrEmpty(_bankConnectionSettings.Bankname)) - { - sb.AppendLine(String.Format("{0}
", _bankConnectionSettings.Bankname)); - } - if (!String.IsNullOrEmpty(_bankConnectionSettings.Bankcode)) - { - //TODO: caption - sb.AppendLine(String.Format("{0}
", _bankConnectionSettings.Bankcode)); - } - if (!String.IsNullOrEmpty(_bankConnectionSettings.AccountNumber)) - { - //TODO: caption - sb.AppendLine(String.Format("{0}
", _bankConnectionSettings.AccountNumber)); - } - if (!String.IsNullOrEmpty(_bankConnectionSettings.AccountHolder)) - { - //TODO: caption - sb.AppendLine(String.Format("{0}
", _bankConnectionSettings.AccountHolder)); - } - if (!String.IsNullOrEmpty(_bankConnectionSettings.Iban)) - { - //TODO: caption - sb.AppendLine(String.Format("{0}
", _bankConnectionSettings.Iban)); - } - if (!String.IsNullOrEmpty(_bankConnectionSettings.Bic)) - { - //TODO: caption - sb.AppendLine(String.Format("{0}
", _bankConnectionSettings.Bic)); - } - - sb.AppendLine("
"); - - sb.AppendLine("
"); - result = sb.ToString(); - return result; - } - - #endregion - - #region Methods - - public virtual void AddStoreTokens(IList tokens, Store store) - { - tokens.Add(new Token("Store.Name", store.Name)); - tokens.Add(new Token("Store.URL", store.Url, true)); - var defaultEmailAccount = _emailAccountService.GetDefaultEmailAccount(); - tokens.Add(new Token("Store.SupplierIdentification", GetSupplierIdentification(), true)); - tokens.Add(new Token("Store.Email", defaultEmailAccount.Email)); - } - - public virtual void AddCompanyTokens(IList tokens) - { - tokens.Add(new Token("Company.CompanyName", _companyInfoSettings.CompanyName)); - tokens.Add(new Token("Company.Salutation", _companyInfoSettings.Salutation)); - tokens.Add(new Token("Company.Title", _companyInfoSettings.Title)); - tokens.Add(new Token("Company.Firstname", _companyInfoSettings.Firstname)); - tokens.Add(new Token("Company.Lastname", _companyInfoSettings.Lastname)); - tokens.Add(new Token("Company.CompanyManagementDescription", _companyInfoSettings.CompanyManagementDescription)); - tokens.Add(new Token("Company.CompanyManagement", _companyInfoSettings.CompanyManagement)); - tokens.Add(new Token("Company.Street", _companyInfoSettings.Street)); - tokens.Add(new Token("Company.Street2", _companyInfoSettings.Street2)); - tokens.Add(new Token("Company.ZipCode", _companyInfoSettings.ZipCode)); - tokens.Add(new Token("Company.City", _companyInfoSettings.City)); - tokens.Add(new Token("Company.CountryName", _companyInfoSettings.CountryName)); - tokens.Add(new Token("Company.Region", _companyInfoSettings.Region)); - tokens.Add(new Token("Company.VatId", _companyInfoSettings.VatId)); - tokens.Add(new Token("Company.CommercialRegister", _companyInfoSettings.CommercialRegister)); - tokens.Add(new Token("Company.TaxNumber", _companyInfoSettings.TaxNumber)); - } - - public virtual void AddBankConnectionTokens(IList tokens) - { - tokens.Add(new Token("Bank.Bankname", _bankConnectionSettings.Bankname)); - tokens.Add(new Token("Bank.Bankcode", _bankConnectionSettings.Bankcode)); - tokens.Add(new Token("Bank.AccountNumber", _bankConnectionSettings.AccountNumber)); - tokens.Add(new Token("Bank.AccountHolder", _bankConnectionSettings.AccountHolder)); - tokens.Add(new Token("Bank.Iban", _bankConnectionSettings.Iban)); - tokens.Add(new Token("Bank.Bic", _bankConnectionSettings.Bic)); - } - - public virtual void AddContactDataTokens(IList tokens) - { - tokens.Add(new Token("Contact.CompanyTelephoneNumber", _contactDataSettings.CompanyTelephoneNumber)); - tokens.Add(new Token("Contact.HotlineTelephoneNumber", _contactDataSettings.HotlineTelephoneNumber)); - tokens.Add(new Token("Contact.MobileTelephoneNumber", _contactDataSettings.MobileTelephoneNumber)); - tokens.Add(new Token("Contact.CompanyFaxNumber", _contactDataSettings.CompanyFaxNumber)); - tokens.Add(new Token("Contact.CompanyEmailAddress", _contactDataSettings.CompanyEmailAddress)); - tokens.Add(new Token("Contact.WebmasterEmailAddress", _contactDataSettings.WebmasterEmailAddress)); - tokens.Add(new Token("Contact.SupportEmailAddress", _contactDataSettings.SupportEmailAddress)); - tokens.Add(new Token("Contact.ContactEmailAddress", _contactDataSettings.ContactEmailAddress)); - } - - public virtual void AddOrderTokens(IList tokens, Order order, Language language) - { - tokens.Add(new Token("Order.ID", order.Id.ToString())); - tokens.Add(new Token("Order.OrderNumber", order.GetOrderNumber())); - - tokens.Add(new Token("Order.CustomerFullName", string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName))); - tokens.Add(new Token("Order.CustomerEmail", order.BillingAddress.Email)); - - tokens.Add(new Token("Order.BillingSalutation", order.BillingAddress.Salutation)); - tokens.Add(new Token("Order.BillingTitle", order.BillingAddress.Title)); - tokens.Add(new Token("Order.BillingFirstName", order.BillingAddress.FirstName)); - tokens.Add(new Token("Order.BillingLastName", order.BillingAddress.LastName)); - tokens.Add(new Token("Order.BillingPhoneNumber", order.BillingAddress.PhoneNumber)); - tokens.Add(new Token("Order.BillingEmail", order.BillingAddress.Email)); - tokens.Add(new Token("Order.BillingFaxNumber", order.BillingAddress.FaxNumber)); - tokens.Add(new Token("Order.BillingCompany", order.BillingAddress.Company)); - tokens.Add(new Token("Order.BillingAddress1", order.BillingAddress.Address1)); - tokens.Add(new Token("Order.BillingAddress2", order.BillingAddress.Address2)); - tokens.Add(new Token("Order.BillingCity", order.BillingAddress.City)); - tokens.Add(new Token("Order.BillingStateProvince", order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name) : "")); - tokens.Add(new Token("Order.BillingZipPostalCode", order.BillingAddress.ZipPostalCode)); - tokens.Add(new Token("Order.BillingCountry", order.BillingAddress.Country != null ? order.BillingAddress.Country.GetLocalized(x => x.Name) : "")); - - tokens.Add(new Token("Order.ShippingMethod", order.ShippingMethod)); - tokens.Add(new Token("Order.ShippingSalutation", order.ShippingAddress != null ? order.ShippingAddress.Salutation : "")); - tokens.Add(new Token("Order.ShippingTitle", order.ShippingAddress != null ? order.ShippingAddress.Title : "")); - tokens.Add(new Token("Order.ShippingFirstName", order.ShippingAddress != null ? order.ShippingAddress.FirstName : "")); - tokens.Add(new Token("Order.ShippingLastName", order.ShippingAddress != null ? order.ShippingAddress.LastName : "")); - tokens.Add(new Token("Order.ShippingPhoneNumber", order.ShippingAddress != null ? order.ShippingAddress.PhoneNumber : "")); - tokens.Add(new Token("Order.ShippingEmail", order.ShippingAddress != null ? order.ShippingAddress.Email : "")); - tokens.Add(new Token("Order.ShippingFaxNumber", order.ShippingAddress != null ? order.ShippingAddress.FaxNumber : "")); - tokens.Add(new Token("Order.ShippingCompany", order.ShippingAddress != null ? order.ShippingAddress.Company : "")); - tokens.Add(new Token("Order.ShippingAddress1", order.ShippingAddress != null ? order.ShippingAddress.Address1 : "")); - tokens.Add(new Token("Order.ShippingAddress2", order.ShippingAddress != null ? order.ShippingAddress.Address2 : "")); - tokens.Add(new Token("Order.ShippingCity", order.ShippingAddress != null ? order.ShippingAddress.City : "")); - tokens.Add(new Token("Order.ShippingStateProvince", order.ShippingAddress != null && order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name) : "")); - tokens.Add(new Token("Order.ShippingZipPostalCode", order.ShippingAddress != null ? order.ShippingAddress.ZipPostalCode : "")); - tokens.Add(new Token("Order.ShippingCountry", order.ShippingAddress != null && order.ShippingAddress.Country != null ? order.ShippingAddress.Country.GetLocalized(x => x.Name) : "")); - - string paymentMethodName = null; - var paymentMethod = _providerManager.GetProvider(order.PaymentMethodSystemName); - if (paymentMethod != null) - { - paymentMethodName = GetLocalizedValue(paymentMethod.Metadata, "FriendlyName", x => x.FriendlyName); - } - if (paymentMethodName.IsEmpty()) - { - paymentMethodName = order.PaymentMethodSystemName; - } - - tokens.Add(new Token("Order.PaymentMethod", paymentMethodName)); - tokens.Add(new Token("Order.VatNumber", order.VatNumber)); - tokens.Add(new Token("Order.Product(s)", ProductListToHtmlTable(order, language), true)); - tokens.Add(new Token("Order.CustomerComment", order.CustomerOrderComment, true)); - - if (language != null && !String.IsNullOrEmpty(language.LanguageCulture)) - { - DateTime createdOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, TimeZoneInfo.Utc, _dateTimeHelper.GetCustomerTimeZone(order.Customer)); - tokens.Add(new Token("Order.CreatedOn", createdOn.ToString("D", new CultureInfo(language.LanguageCulture)))); - } - else - { - tokens.Add(new Token("Order.CreatedOn", order.CreatedOnUtc.ToString("D"))); - } - - // TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - tokens.Add(new Token("Order.OrderURLForCustomer", string.Format("{0}order/details/{1}", _webHelper.GetStoreLocation(false), order.Id), true)); - - tokens.Add(new Token("Order.Disclaimer", TopicToHtml("Disclaimer", language.Id), true)); - tokens.Add(new Token("Order.ConditionsOfUse", TopicToHtml("ConditionsOfUse", language.Id), true)); - - //event notification - _eventPublisher.EntityTokensAdded(order, tokens); - } - - private string GetLocalizedValue(ProviderMetadata metadata, string propertyName, Expression> fallback) - { - // TODO: (mc) this actually belongs to PluginMediator, but we simply cannot add a dependency to framework from here. Refactor later! - - Guard.ArgumentNotNull(() => metadata); - - string systemName = metadata.SystemName; - var languageId = _workContext.WorkingLanguage.Id; - var resourceName = metadata.ResourceKeyPattern.FormatInvariant(metadata.SystemName, propertyName); - string result = _localizationService.GetResource(resourceName, languageId, false, "", true); - - if (result.IsEmpty()) - result = fallback.Compile()(metadata); - - return result; - } - - public virtual void AddShipmentTokens(IList tokens, Shipment shipment, Language language) - { - tokens.Add(new Token("Shipment.ShipmentNumber", shipment.Id.ToString())); - tokens.Add(new Token("Shipment.TrackingNumber", shipment.TrackingNumber)); - tokens.Add(new Token("Shipment.Product(s)", ProductListToHtmlTable(shipment, language), true)); - tokens.Add(new Token("Shipment.URLForCustomer", string.Format("{0}order/shipmentdetails/{1}", _webHelper.GetStoreLocation(false), shipment.Id), true)); - - //event notification - _eventPublisher.EntityTokensAdded(shipment, tokens); - } - - public virtual void AddOrderNoteTokens(IList tokens, OrderNote orderNote) - { - tokens.Add(new Token("Order.NewNoteText", orderNote.FormatOrderNoteText(), true)); - - //event notification - _eventPublisher.EntityTokensAdded(orderNote, tokens); - } - - public virtual void AddRecurringPaymentTokens(IList tokens, RecurringPayment recurringPayment) - { - tokens.Add(new Token("RecurringPayment.ID", recurringPayment.Id.ToString())); - - //event notification - _eventPublisher.EntityTokensAdded(recurringPayment, tokens); - } - - public virtual void AddReturnRequestTokens(IList tokens, ReturnRequest returnRequest, OrderItem orderItem) - { - tokens.Add(new Token("ReturnRequest.ID", returnRequest.Id.ToString())); - tokens.Add(new Token("ReturnRequest.Product.Quantity", returnRequest.Quantity.ToString())); - tokens.Add(new Token("ReturnRequest.Product.Name", orderItem.Product.Name)); - tokens.Add(new Token("ReturnRequest.Reason", returnRequest.ReasonForReturn)); - tokens.Add(new Token("ReturnRequest.RequestedAction", returnRequest.RequestedAction)); - tokens.Add(new Token("ReturnRequest.CustomerComment", HtmlUtils.FormatText(returnRequest.CustomerComments, false, true, false, false, false, false), true)); - tokens.Add(new Token("ReturnRequest.StaffNotes", HtmlUtils.FormatText(returnRequest.StaffNotes, false, true, false, false, false, false), true)); - tokens.Add(new Token("ReturnRequest.Status", returnRequest.ReturnRequestStatus.GetLocalizedEnum(_localizationService, _workContext))); - - //event notification - _eventPublisher.EntityTokensAdded(returnRequest, tokens); - } - - public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) - { - var order = (giftCard.PurchasedWithOrderItem != null ? giftCard.PurchasedWithOrderItem.Order : null); - - if (order != null) - { - var remainingAmount = _currencyService.ConvertCurrency(giftCard.GetGiftCardRemainingAmount(), order.CurrencyRate); - - tokens.Add(new Token("GiftCard.RemainingAmount", _priceFormatter.FormatPrice(remainingAmount, true, false))); - } - else - { - tokens.Add(new Token("GiftCard.RemainingAmount", "")); - } - - tokens.Add(new Token("GiftCard.SenderName", giftCard.SenderName)); - tokens.Add(new Token("GiftCard.SenderEmail", giftCard.SenderEmail)); - tokens.Add(new Token("GiftCard.RecipientName", giftCard.RecipientName)); - tokens.Add(new Token("GiftCard.RecipientEmail", giftCard.RecipientEmail)); - tokens.Add(new Token("GiftCard.Amount", _priceFormatter.FormatPrice(giftCard.Amount, true, false))); - tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); - - var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? - HtmlUtils.FormatText(giftCard.Message, false, true, false, false, false, false) : ""; - - tokens.Add(new Token("GiftCard.Message", giftCardMesage, true)); - - //event notification - _eventPublisher.EntityTokensAdded(giftCard, tokens); - } - - public virtual void AddCustomerTokens(IList tokens, Customer customer) - { - tokens.Add(new Token("Customer.ID", customer.Id.ToString())); - tokens.Add(new Token("Customer.Email", customer.Email)); - tokens.Add(new Token("Customer.Username", customer.Username)); - tokens.Add(new Token("Customer.FullName", customer.GetFullName())); - tokens.Add(new Token("Customer.VatNumber", customer.GetAttribute(SystemCustomerAttributeNames.VatNumber))); - tokens.Add(new Token("Customer.VatNumberStatus", ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)).ToString())); - - //note: we do not use SEO friendly URLS because we can get errors caused by having .(dot) in the URL (from the emauk address) - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - string passwordRecoveryUrl = string.Format("{0}customer/passwordrecoveryconfirm?token={1}&email={2}", _webHelper.GetStoreLocation(), - customer.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken), HttpUtility.UrlEncode(customer.Email)); - - string accountActivationUrl = string.Format("{0}customer/activation?token={1}&email={2}", _webHelper.GetStoreLocation(), - customer.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken), HttpUtility.UrlEncode(customer.Email)); - - var wishlistUrl = string.Format("{0}wishlist/{1}", _webHelper.GetStoreLocation(), customer.CustomerGuid); - tokens.Add(new Token("Customer.PasswordRecoveryURL", passwordRecoveryUrl, true)); - tokens.Add(new Token("Customer.AccountActivationURL", accountActivationUrl, true)); - tokens.Add(new Token("Wishlist.URLForCustomer", wishlistUrl, true)); - - //event notification - _eventPublisher.EntityTokensAdded(customer, tokens); - } - - public virtual void AddNewsLetterSubscriptionTokens(IList tokens, NewsLetterSubscription subscription) - { - tokens.Add(new Token("NewsLetterSubscription.Email", subscription.Email)); - - const string urlFormat = "{0}newsletter/subscriptionactivation/{1}/{2}"; - - var activationUrl = String.Format(urlFormat, _webHelper.GetStoreLocation(false), subscription.NewsLetterSubscriptionGuid, "true"); - tokens.Add(new Token("NewsLetterSubscription.ActivationUrl", activationUrl, true)); - - var deActivationUrl = String.Format(urlFormat, _webHelper.GetStoreLocation(false), subscription.NewsLetterSubscriptionGuid, "false"); - tokens.Add(new Token("NewsLetterSubscription.DeactivationUrl", deActivationUrl, true)); - - //event notification - _eventPublisher.EntityTokensAdded(subscription, tokens); - } - - public virtual void AddProductReviewTokens(IList tokens, ProductReview productReview) - { - tokens.Add(new Token("ProductReview.ProductName", productReview.Product.Name)); - - //event notification - _eventPublisher.EntityTokensAdded(productReview, tokens); - } - - public virtual void AddBlogCommentTokens(IList tokens, BlogComment blogComment) - { - tokens.Add(new Token("BlogComment.BlogPostTitle", blogComment.BlogPost.Title)); - - //event notification - _eventPublisher.EntityTokensAdded(blogComment, tokens); - } - - public virtual void AddNewsCommentTokens(IList tokens, NewsComment newsComment) - { - tokens.Add(new Token("NewsComment.NewsTitle", newsComment.NewsItem.Title)); - - //event notification - _eventPublisher.EntityTokensAdded(newsComment, tokens); - } - - public virtual void AddProductTokens(IList tokens, Product product, Language language) - { - // TODO: add a method for getting URL (use routing because it handles all SEO friendly URLs) - var storeLocation = _webHelper.GetStoreLocation(false); - var productUrl = storeLocation + product.GetSeName(); - var productName = product.GetLocalized(x => x.Name, language.Id); - - tokens.Add(new Token("Product.ID", product.Id.ToString())); - tokens.Add(new Token("Product.Sku", product.Sku)); - tokens.Add(new Token("Product.Name", productName)); - tokens.Add(new Token("Product.ShortDescription", product.GetLocalized(x => x.ShortDescription, language.Id), true)); - tokens.Add(new Token("Product.StockQuantity", product.StockQuantity.ToString())); - tokens.Add(new Token("Product.ProductURLForCustomer", productUrl, true)); - - var currency = _workContext.WorkingCurrency; - - var additionalShippingCharge = _currencyService.ConvertFromPrimaryStoreCurrency(product.AdditionalShippingCharge, currency); - var additionalShippingChargeFormatted = _priceFormatter.FormatPrice(additionalShippingCharge, false, currency.CurrencyCode, false, language); - - tokens.Add(new Token("Product.AdditionalShippingCharge", additionalShippingChargeFormatted)); - - if (_mediaSettings.MessageProductThumbPictureSize > 0) - { - var pictureHtml = ProductPictureToHtml(GetPictureFor(product, null), language, productName, productUrl, storeLocation); - - tokens.Add(new Token("Product.Thumbnail", pictureHtml, true)); - } - - //event notification - _eventPublisher.EntityTokensAdded(product, tokens); - } - - public virtual void AddForumTopicTokens(IList tokens, ForumTopic forumTopic, - int? friendlyForumTopicPageIndex = null, int? appendedPostIdentifierAnchor = null) - { - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - string topicUrl = null; - if (friendlyForumTopicPageIndex.HasValue && friendlyForumTopicPageIndex.Value > 1) - topicUrl = string.Format("{0}boards/topic/{1}/{2}/page/{3}", _webHelper.GetStoreLocation(false), forumTopic.Id, forumTopic.GetSeName(), friendlyForumTopicPageIndex.Value); - else - topicUrl = string.Format("{0}boards/topic/{1}/{2}", _webHelper.GetStoreLocation(false), forumTopic.Id, forumTopic.GetSeName()); - if (appendedPostIdentifierAnchor.HasValue && appendedPostIdentifierAnchor.Value > 0) - topicUrl = string.Format("{0}#{1}", topicUrl, appendedPostIdentifierAnchor.Value); - tokens.Add(new Token("Forums.TopicURL", topicUrl, true)); - tokens.Add(new Token("Forums.TopicName", forumTopic.Subject)); - - //event notification - _eventPublisher.EntityTokensAdded(forumTopic, tokens); - } - - public virtual void AddForumPostTokens(IList tokens, ForumPost forumPost) - { - tokens.Add(new Token("Forums.PostAuthor", forumPost.Customer.FormatUserName())); - tokens.Add(new Token("Forums.PostBody", forumPost.FormatPostText(), true)); - - //event notification - _eventPublisher.EntityTokensAdded(forumPost, tokens); - } - - public virtual void AddForumTokens(IList tokens, Forum forum, Language language) - { - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - var forumUrl = string.Format("{0}boards/forum/{1}/{2}", _webHelper.GetStoreLocation(false), forum.Id, forum.GetSeName(language.Id)); - tokens.Add(new Token("Forums.ForumURL", forumUrl, true)); - tokens.Add(new Token("Forums.ForumName", forum.GetLocalized(x => x.Name, language.Id))); - - //event notification - _eventPublisher.EntityTokensAdded(forum, tokens); - } - - public virtual void AddPrivateMessageTokens(IList tokens, PrivateMessage privateMessage) - { - tokens.Add(new Token("PrivateMessage.Subject", privateMessage.Subject)); - tokens.Add(new Token("PrivateMessage.Text", privateMessage.FormatPrivateMessageText(), true)); - - //event notification - _eventPublisher.EntityTokensAdded(privateMessage, tokens); - } - - public virtual void AddBackInStockTokens(IList tokens, BackInStockSubscription subscription) - { - var customerLangId = subscription.Customer.GetAttribute( - SystemCustomerAttributeNames.LanguageId, - _attrService, - _storeContext.CurrentStore.Id); - - var store = _storeService.GetStoreById(subscription.StoreId); - var productLink = "{0}{1}".FormatWith(store.Url, subscription.Product.GetSeName(customerLangId, _urlRecordService, _languageService)); - - tokens.Add(new Token("BackInStockSubscription.ProductName", "{1}".FormatWith(productLink, subscription.Product.Name), true)); - - //event notification - _eventPublisher.EntityTokensAdded(subscription, tokens); - } - - /// - /// Gets list of allowed (supported) message tokens for campaigns - /// - /// List of allowed (supported) message tokens for campaigns - public virtual string[] GetListOfCampaignAllowedTokens() - { - var allowedTokens = new List() - { - "%Store.Name%", - "%Store.URL%", - "%Store.Email%", - "%NewsLetterSubscription.Email%", - "%NewsLetterSubscription.ActivationUrl%", - "%NewsLetterSubscription.DeactivationUrl%", - "%Store.SupplierIdentification%", - }; - return allowedTokens.ToArray(); - } - - public virtual string[] GetListOfAllowedTokens() - { - var allowedTokens = new List() - { - "%Store.Name%", - "%Store.URL%", - "%Store.Email%", - "%Order.OrderNumber%", - "%Order.CustomerFullName%", - "%Order.CustomerEmail%", - "%Order.BillingFirstName%", - "%Order.BillingLastName%", - "%Order.BillingPhoneNumber%", - "%Order.BillingEmail%", - "%Order.BillingFaxNumber%", - "%Order.BillingCompany%", - "%Order.BillingAddress1%", - "%Order.BillingAddress2%", - "%Order.BillingCity%", - "%Order.BillingStateProvince%", - "%Order.BillingZipPostalCode%", - "%Order.BillingCountry%", - "%Order.ShippingMethod%", - "%Order.ShippingFirstName%", - "%Order.ShippingLastName%", - "%Order.ShippingPhoneNumber%", - "%Order.ShippingEmail%", - "%Order.ShippingFaxNumber%", - "%Order.ShippingCompany%", - "%Order.ShippingAddress1%", - "%Order.ShippingAddress2%", - "%Order.ShippingCity%", - "%Order.ShippingStateProvince%", - "%Order.ShippingZipPostalCode%", - "%Order.ShippingCountry%", - "%Order.PaymentMethod%", - "%Order.VatNumber%", - "%Order.CustomerComment%", - "%Order.Product(s)%", - "%Order.CreatedOn%", - "%Order.OrderURLForCustomer%", - "%Order.NewNoteText%", - "%Product.ID%", - "%Product.Sku%", - "%Product.Name%", - "%Product.ShortDescription%", - "%Product.ProductURLForCustomer%", - "%Product.StockQuantity%", - "%Product.AdditionalShippingCharge%", - "%Product.Thumbnail%", - "%RecurringPayment.ID%", - "%Shipment.ShipmentNumber%", - "%Shipment.TrackingNumber%", - "%Shipment.Product(s)%", - "%Shipment.URLForCustomer%", - "%ReturnRequest.ID%", - "%ReturnRequest.Product.Quantity%", - "%ReturnRequest.Product.Name%", - "%ReturnRequest.Reason%", - "%ReturnRequest.RequestedAction%", - "%ReturnRequest.CustomerComment%", - "%ReturnRequest.StaffNotes%", - "%ReturnRequest.Status%", - "%GiftCard.SenderName%", - "%GiftCard.SenderEmail%", - "%GiftCard.RecipientName%", - "%GiftCard.RecipientEmail%", - "%GiftCard.Amount%", - "%GiftCard.RemainingAmount%", - "%GiftCard.CouponCode%", - "%GiftCard.Message%", - "%Customer.Email%", - "%Customer.Username%", - "%Customer.FullName%", - "%Customer.VatNumber%", - "%Customer.VatNumberStatus%", - "%Customer.PasswordRecoveryURL%", - "%Customer.AccountActivationURL%", - "%Wishlist.URLForCustomer%", - "%NewsLetterSubscription.Email%", - "%NewsLetterSubscription.ActivationUrl%", - "%NewsLetterSubscription.DeactivationUrl%", - "%ProductReview.ProductName%", - "%BlogComment.BlogPostTitle%", - "%NewsComment.NewsTitle%", - "%Forums.TopicURL%", - "%Forums.TopicName%", - "%Forums.PostAuthor%", - "%Forums.PostBody%", - "%Forums.ForumURL%", - "%Forums.ForumName%", - "%PrivateMessage.Subject%", - "%PrivateMessage.Text%", - "%BackInStockSubscription.ProductName%", - "%Order.Disclaimer%", - "%Order.ConditionsOfUse%", - "%Company.CompanyName%", - "%Company.Salutation%", - "%Company.Title%", - "%Company.Firstname%", - "%Company.Lastname%", - "%Company.CompanyManagementDescription%", - "%Company.CompanyManagement%", - "%Company.Street%", - "%Company.Street2%", - "%Company.ZipCode%", - "%Company.City%", - "%Company.CountryName%", - "%Company.Region%", - "%Company.VatId%", - "%Company.CommercialRegister%", - "%Company.TaxNumber%", - "%Bank.Bankname%", - "%Bank.Bankcode%", - "%Bank.AccountNumber%", - "%Bank.AccountHolder%", - "%Bank.Iban%", - "%Bank.Bic%", - "%Contact.CompanyTelephoneNumber%", - "%Contact.HotlineTelephoneNumber%", - "%Contact.MobileTelephoneNumber%", - "%Contact.CompanyFaxNumber%", - "%Contact.CompanyEmailAddress%", - "%Contact.WebmasterEmailAddress%", - "%Contact.SupportEmailAddress%", - "%Contact.ContactEmailAddress%", - "%Store.SupplierIdentification%", - - }; - return allowedTokens.ToArray(); - } - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs index dde1d076c9..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,30 +25,24 @@ 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); } - - //Publish event - _eventPublisher.EntityInserted(newsLetterSubscription); } /// @@ -62,48 +52,42 @@ 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); } - - //Publish event - _eventPublisher.EntityUpdated(newsLetterSubscription); } /// @@ -113,16 +97,12 @@ 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); //Publish the unsubscribe event PublishSubscriptionEvent(newsLetterSubscription.Email, false, publishSubscriptionEvents); - - //event notification - _eventPublisher.EntityDeleted(newsLetterSubscription); } public virtual bool? AddNewsLetterSubscriptionFor(bool add, string email, int storeId) @@ -136,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; } @@ -150,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; } } @@ -263,14 +251,14 @@ private void PublishSubscriptionEvent(string email, bool isSubscribe, bool publi { if (publishSubscriptionEvents) { - if (isSubscribe) + if (isSubscribe) { - _eventPublisher.PublishNewsletterSubscribe(email); - } + _services.EventPublisher.Publish(new EmailSubscribedEvent(email)); + } else { - _eventPublisher.PublishNewsletterUnsubscribe(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 b03c247f39..00ee3e397e 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs @@ -1,78 +1,113 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Net.Mail; -using System.IO; +using System.Threading.Tasks; +using System.Web; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Email; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; -using SmartStore.Services.Localization; +using SmartStore.Core.Plugins; +using SmartStore.Services.Media; +using SmartStore.Services.Media.Storage; using SmartStore.Utilities; -using System.Web; -using SmartStore.Core.Localization; namespace SmartStore.Services.Messages { - public partial class QueuedEmailService : IQueuedEmailService + public partial class QueuedEmailService : IQueuedEmailService { private readonly IRepository _queuedEmailRepository; private readonly IRepository _queuedEmailAttachmentRepository; private readonly IEmailSender _emailSender; private readonly ICommonServices _services; + private readonly IDownloadService _downloadService; + private readonly IProviderManager _providerManager; + private readonly Provider _storageProvider; - public QueuedEmailService( + public QueuedEmailService( IRepository queuedEmailRepository, IRepository queuedEmailAttachmentRepository, IEmailSender emailSender, - ICommonServices services) + ICommonServices services, + IDownloadService downloadService, + IProviderManager providerManager) { - this._queuedEmailRepository = queuedEmailRepository; - this._queuedEmailAttachmentRepository = queuedEmailAttachmentRepository; - this._emailSender = emailSender; - this._services = services; + _queuedEmailRepository = queuedEmailRepository; + _queuedEmailAttachmentRepository = queuedEmailAttachmentRepository; + _emailSender = emailSender; + _services = services; + _downloadService = downloadService; + _providerManager = providerManager; T = NullLocalizer.Instance; Logger = NullLogger.Instance; - } + + var systemName = services.Settings.GetSettingByKey("Media.Storage.Provider", DatabaseMediaStorageProvider.SystemName); + + _storageProvider = providerManager.GetProvider(systemName); + } public Localizer T { get; set; } public ILogger Logger { get; set; } public virtual void InsertQueuedEmail(QueuedEmail queuedEmail) { - if (queuedEmail == null) - throw new ArgumentNullException("queuedEmail"); + Guard.NotNull(queuedEmail, nameof(queuedEmail)); - _queuedEmailRepository.Insert(queuedEmail); + _queuedEmailRepository.Insert(queuedEmail); - //event notification - _services.EventPublisher.EntityInserted(queuedEmail); + // blob data always stored in database at this point -> move it if current provider is not database provider + if (!_storageProvider.Metadata.SystemName.IsCaseInsensitiveEqual(DatabaseMediaStorageProvider.SystemName)) + { + var blobs = queuedEmail.Attachments.Where(x => x.StorageLocation == EmailAttachmentStorageLocation.Blob && x.MediaStorage != null); + if (blobs.Any()) + { + var databaseProvider = _providerManager.GetProvider(DatabaseMediaStorageProvider.SystemName); + + foreach (var blob in blobs) + { + var media = blob.ToMedia(); + + try + { + // move it to current storage provider + _storageProvider.Value.Save(media, blob.MediaStorage.Data); + } + catch (Exception exception) + { + Logger.Error(exception, T("Admin.Media.ProviderFailedToSave", _storageProvider.Metadata.SystemName, "QueuedEmailService.InsertQueuedEmail")); + } + finally + { + try + { + // always remove data from database to avoid inconsistent records + databaseProvider.Value.Remove(media); + } + catch { } + } + } + } + } } public virtual void UpdateQueuedEmail(QueuedEmail queuedEmail) { - if (queuedEmail == null) - throw new ArgumentNullException("queuedEmail"); - - _queuedEmailRepository.Update(queuedEmail); + Guard.NotNull(queuedEmail, nameof(queuedEmail)); - //event notification - _services.EventPublisher.EntityUpdated(queuedEmail); + _queuedEmailRepository.Update(queuedEmail); } public virtual void DeleteQueuedEmail(QueuedEmail queuedEmail) { - if (queuedEmail == null) - throw new ArgumentNullException("queuedEmail"); + Guard.NotNull(queuedEmail, nameof(queuedEmail)); _queuedEmailRepository.Delete(queuedEmail); - - //event notification - _services.EventPublisher.EntityDeleted(queuedEmail); } public virtual int DeleteAllQueuedEmails() @@ -101,21 +136,13 @@ where queuedEmailIds.Contains(qe.Id) var queuedEmails = query.ToList(); - // sort by passed identifiers - var sortedQueuedEmails = new List(); - - foreach (int id in queuedEmailIds) - { - var queuedEmail = queuedEmails.Find(x => x.Id == id); - if (queuedEmail != null) - sortedQueuedEmails.Add(queuedEmail); - } - return sortedQueuedEmails; - } + // sort by passed identifier sequence + return queuedEmails.OrderBySequence(queuedEmailIds).ToList(); + } public virtual IPagedList SearchEmails(SearchEmailsQuery query) { - Guard.ArgumentNotNull(() => query); + Guard.NotNull(query, nameof(query)); var q = _queuedEmailRepository.Table; @@ -158,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; @@ -167,14 +194,14 @@ 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; } - catch (Exception exc) + catch (Exception ex) { - Logger.Error(string.Concat(T("Admin.Common.ErrorSendingEmail"), ": ", exc.Message), exc); + Logger.Error(ex, string.Concat(T("Admin.Common.ErrorSendingEmail"), ": ", ex.Message)); } finally { @@ -199,14 +226,14 @@ internal EmailMessage ConvertEmail(QueuedEmail qe) // 'internal' for testing purposes var msg = new EmailMessage( - new EmailAddress(qe.To, qe.ToName), - qe.Subject, + new EmailAddress(qe.To), + qe.Subject.Replace("\r\n", string.Empty), qe.Body, - new EmailAddress(qe.From, qe.FromName)); + new EmailAddress(qe.From)); if (qe.ReplyTo.HasValue()) { - msg.ReplyTo.Add(new EmailAddress(qe.ReplyTo, qe.ReplyToName)); + msg.ReplyTo.Add(new EmailAddress(qe.ReplyTo)); } AddEmailAddresses(qe.CC, msg.Cc); @@ -220,8 +247,9 @@ internal EmailMessage ConvertEmail(QueuedEmail qe) if (qea.StorageLocation == EmailAttachmentStorageLocation.Blob) { - var data = qea.Data; - if (data != null && data.Length > 0) + var data = _storageProvider.Value.Load(qea.ToMedia()); + + if (data != null && data.LongLength > 0) { attachment = new Attachment(data.ToStream(), qea.Name, qea.MimeType); } @@ -245,9 +273,11 @@ internal EmailMessage ConvertEmail(QueuedEmail qe) else if (qea.StorageLocation == EmailAttachmentStorageLocation.FileReference) { var file = qea.File; - if (file != null && file.UseDownloadUrl == false && file.DownloadBinary != null && file.DownloadBinary.Length > 0) + if (file != null && file.UseDownloadUrl == false) { - attachment = new Attachment(file.DownloadBinary.ToStream(), file.Filename + file.Extension, file.ContentType); + var data = _downloadService.LoadDownloadBinary(file); + + attachment = new Attachment(data.ToStream(), file.Filename + file.Extension, file.ContentType); } } @@ -272,14 +302,29 @@ public virtual QueuedEmailAttachment GetQueuedEmailAttachmentById(int id) return qea; } - public virtual void DeleteQueuedEmailAttachment(QueuedEmailAttachment qea) + public virtual void DeleteQueuedEmailAttachment(QueuedEmailAttachment attachment) + { + Guard.NotNull(attachment, nameof(attachment)); + + // delete from storage + if (attachment.StorageLocation == EmailAttachmentStorageLocation.Blob) + { + _storageProvider.Value.Remove(attachment.ToMedia()); + } + + _queuedEmailAttachmentRepository.Delete(attachment); + } + + public virtual byte[] LoadQueuedEmailAttachmentBinary(QueuedEmailAttachment attachment) { - if (qea == null) - throw new ArgumentNullException("qea"); + Guard.NotNull(attachment, nameof(attachment)); - _queuedEmailAttachmentRepository.Delete(qea); + if (attachment.StorageLocation == EmailAttachmentStorageLocation.Blob) + { + return _storageProvider.Value.Load(attachment.ToMedia()); + } - _services.EventPublisher.EntityDeleted(qea); + return null; } #endregion 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 a37e7e82c4..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; @@ -27,13 +29,15 @@ public void Execute(TaskExecutionContext ctx) PageIndex = i, PageSize = pageSize, Expand = "Attachments", - UnsentOnly = true + 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/Messages/QueuingEmailEvent.cs b/src/Libraries/SmartStore.Services/Messages/QueuingEmailEvent.cs deleted file mode 100644 index d9efdacd84..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/QueuingEmailEvent.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using SmartStore.Core.Domain.Messages; - -namespace SmartStore.Services.Messages -{ - /// - /// An event message, which gets published just before a new instance - /// of is persisted to the database - /// - public class QueuingEmailEvent - { - public QueuedEmail QueuedEmail - { - get; - set; - } - - public MessageTemplate MessageTemplate - { - get; - set; - } - - public EmailAccount EmailAccount - { - get; - set; - } - - public IList Tokens - { - get; - set; - } - - public int LanguageId - { - get; - set; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/QueuingEmailEventConsumer.cs b/src/Libraries/SmartStore.Services/Messages/QueuingEmailEventConsumer.cs deleted file mode 100644 index 11eca682cc..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/QueuingEmailEventConsumer.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Mime; -using System.Text; -using System.Threading.Tasks; -using System.Web; -using System.Web.Mvc; -using System.Web.Security; -using SmartStore.Core; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Events; -using SmartStore.Core.Localization; -using SmartStore.Core.Logging; -using SmartStore.Utilities; - -namespace SmartStore.Services.Messages -{ - public class QueuingEmailEventConsumer : IConsumer - { - private readonly PdfSettings _pdfSettings; - private readonly HttpRequestBase _httpRequest; - private readonly Lazy _fileDownloadManager; - - public QueuingEmailEventConsumer( - PdfSettings pdfSettings, - HttpRequestBase httpRequest, - Lazy fileDownloadManager) - { - this._pdfSettings = pdfSettings; - this._httpRequest = httpRequest; - this._fileDownloadManager = fileDownloadManager; - - Logger = NullLogger.Instance; - T = NullLocalizer.Instance; - } - - public ILogger Logger { get; set; } - public Localizer T { get; set; } - - public void HandleEvent(QueuingEmailEvent eventMessage) - { - var qe = eventMessage.QueuedEmail; - var tpl = eventMessage.MessageTemplate; - - var handledTemplates = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "OrderPlaced.CustomerNotification", _pdfSettings.AttachOrderPdfToOrderPlacedEmail }, - { "OrderCompleted.CustomerNotification", _pdfSettings.AttachOrderPdfToOrderCompletedEmail } - }; - - bool shouldHandle = false; - if (handledTemplates.TryGetValue(tpl.Name, out shouldHandle) && shouldHandle) - { - var orderId = eventMessage.Tokens.First(x => x.Key.IsCaseInsensitiveEqual("Order.ID")).Value.ToInt(); - try - { - var qea = CreatePdfInvoiceAttachment(orderId); - qe.Attachments.Add(qea); - } - catch (Exception ex) - { - Logger.Error(T("Admin.System.QueuedEmails.ErrorCreatingAttachment"), ex); - } - } - } - - private QueuedEmailAttachment CreatePdfInvoiceAttachment(int orderId) - { - var urlHelper = new UrlHelper(_httpRequest.RequestContext); - var path = urlHelper.Action("Print", "Order", new { id = orderId, pdf = true, area = "" }); - - var fileResponse = _fileDownloadManager.Value.DownloadFile(path, true, 5000); - - if (fileResponse == null) - { - throw new InvalidOperationException(T("Admin.System.QueuedEmails.ErrorEmptyAttachmentResult", path)); - } - - if (!fileResponse.ContentType.IsCaseInsensitiveEqual("application/pdf")) - { - throw new InvalidOperationException(T("Admin.System.QueuedEmails.ErrorNoPdfAttachment")); - } - - return new QueuedEmailAttachment - { - StorageLocation = EmailAttachmentStorageLocation.Blob, - Data = fileResponse.Data, - MimeType = fileResponse.ContentType, - Name = fileResponse.FileName - }; - } - - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/TemplateModel.cs b/src/Libraries/SmartStore.Services/Messages/TemplateModel.cs new file mode 100644 index 0000000000..61037c1e01 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Messages/TemplateModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using SmartStore.ComponentModel; + +namespace SmartStore.Services.Messages +{ + public class TemplateModel : HybridExpando + { + public T GetFromBag(string key) + { + Guard.NotEmpty(key, nameof(key)); + + if (base.Contains("Bag") && base["Bag"] is IDictionary bag) + { + if (bag.TryGetValue(key, out var value) && value is T result) + { + return result; + } + } + + return default(T); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Messages/Token.cs b/src/Libraries/SmartStore.Services/Messages/Token.cs deleted file mode 100644 index 72e85c5744..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/Token.cs +++ /dev/null @@ -1,40 +0,0 @@ - -namespace SmartStore.Services.Messages -{ - public sealed class Token - { - private readonly string _key; - private readonly string _value; - private readonly bool _neverHtmlEncoded; - - public Token(string key, string value): - this(key, value, false) - { - - } - public Token(string key, string value, bool neverHtmlEncoded) - { - this._key = key; - this._value = value; - this._neverHtmlEncoded = neverHtmlEncoded; - } - - /// - /// Token key - /// - public string Key { get { return _key; } } - /// - /// Token value - /// - public string Value { get { return _value; } } - /// - /// Indicates whether this token should not be HTML encoded - /// - public bool NeverHtmlEncoded { get { return _neverHtmlEncoded; } } - - public override string ToString() - { - return string.Format("{0}: {1}", Key, Value); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/Tokenizer.cs b/src/Libraries/SmartStore.Services/Messages/Tokenizer.cs deleted file mode 100644 index dcc031791c..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/Tokenizer.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Infrastructure; - -namespace SmartStore.Services.Messages -{ - public partial class Tokenizer : ITokenizer - { - private readonly StringComparison _stringComparison; - - /// - /// Ctor - /// - /// Message templates settings - public Tokenizer(MessageTemplatesSettings settings) - { - _stringComparison = settings.CaseInvariantReplacement ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - } - - /// - /// Replace all of the token key occurences inside the specified template text with corresponded token values - /// - /// The template with token keys inside - /// The sequence of tokens to use - /// The value indicating whether tokens should be HTML encoded - /// Text with all token keys replaces by token value - public string Replace(string template, IEnumerable tokens, bool htmlEncode) - { - if (string.IsNullOrWhiteSpace(template)) - throw new ArgumentNullException("template"); - - if (tokens == null) - throw new ArgumentNullException("tokens"); - - foreach (var token in tokens) - { - string tokenValue = token.Value; - //do not encode URLs - if (htmlEncode && !token.NeverHtmlEncoded) - tokenValue = HttpUtility.HtmlEncode(tokenValue); - template = Replace(template, String.Format(@"%{0}%", token.Key), tokenValue); - } - return template; - - } - - private string Replace(string original, string pattern, string replacement) - { - if (_stringComparison == StringComparison.Ordinal) - { - return original.Replace(pattern, replacement); - } - else - { - int count, position0, position1; - count = position0 = position1 = 0; - int inc = (original.Length / pattern.Length) * (replacement.Length - pattern.Length); - char[] chars = new char[original.Length + Math.Max(0, inc)]; - while ((position1 = original.IndexOf(pattern, position0, _stringComparison)) != -1) - { - for (int i = position0; i < position1; ++i) - chars[count++] = original[i]; - for (int i = 0; i < replacement.Length; ++i) - chars[count++] = replacement[i]; - position0 = position1 + pattern.Length; - } - if (position0 == 0) return original; - for (int i = position0; i < original.Length; ++i) - chars[count++] = original[i]; - return new string(chars, 0, count); - } - } - - } -} diff --git a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs deleted file mode 100644 index 1f2746245e..0000000000 --- a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs +++ /dev/null @@ -1,1428 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using SmartStore.Core; -using SmartStore.Core.Domain.Blogs; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Domain.News; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; -using SmartStore.Core.Localization; -using SmartStore.Services.Customers; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Stores; - -namespace SmartStore.Services.Messages -{ - public partial class WorkflowMessageService : IWorkflowMessageService - { - #region Fields - - private readonly IMessageTemplateService _messageTemplateService; - private readonly IQueuedEmailService _queuedEmailService; - private readonly ILanguageService _languageService; - private readonly ITokenizer _tokenizer; - private readonly IEmailAccountService _emailAccountService; - private readonly IMessageTokenProvider _messageTokenProvider; - private readonly IStoreService _storeService; - private readonly IStoreContext _storeContext; - private readonly EmailAccountSettings _emailAccountSettings; - private readonly IEventPublisher _eventPublisher; - private readonly IWorkContext _workContext; - private readonly HttpRequestBase _httpRequest; - private readonly IDownloadService _downloadServioce; - - #endregion - - #region Ctor - - public WorkflowMessageService( - IMessageTemplateService messageTemplateService, - IQueuedEmailService queuedEmailService, - ILanguageService languageService, - ITokenizer tokenizer, - IEmailAccountService emailAccountService, - IMessageTokenProvider messageTokenProvider, - IStoreService storeService, - IStoreContext storeContext, - EmailAccountSettings emailAccountSettings, - IEventPublisher eventPublisher, - IWorkContext workContext, - HttpRequestBase httpRequest, - IDownloadService downloadServioce) - { - this._messageTemplateService = messageTemplateService; - this._queuedEmailService = queuedEmailService; - this._languageService = languageService; - this._tokenizer = tokenizer; - this._emailAccountService = emailAccountService; - this._messageTokenProvider = messageTokenProvider; - this._storeService = storeService; - this._storeContext = storeContext; - this._emailAccountSettings = emailAccountSettings; - this._eventPublisher = eventPublisher; - this._workContext = workContext; - this._httpRequest = httpRequest; - this._downloadServioce = downloadServioce; - - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - #endregion - - #region Utilities - - protected int SendNotification( - MessageTemplate messageTemplate, - EmailAccount emailAccount, - int languageId, - IList tokens, - string toEmailAddress, - string toName, - string replyTo = null, - string replyToName = null) - { - // retrieve localized message template data - var bcc = messageTemplate.GetLocalized((mt) => mt.BccEmailAddresses, languageId); - var subject = messageTemplate.GetLocalized((mt) => mt.Subject, languageId); - var body = messageTemplate.GetLocalized((mt) => mt.Body, languageId); - - // Replace subject and body tokens - var subjectReplaced = _tokenizer.Replace(subject, tokens, false); - var bodyReplaced = _tokenizer.Replace(body, tokens, true); - - bodyReplaced = WebHelper.MakeAllUrlsAbsolute(bodyReplaced, _httpRequest); - - var email = new QueuedEmail - { - Priority = 5, - From = emailAccount.Email, - FromName = emailAccount.DisplayName, - To = toEmailAddress, - ToName = toName, - CC = string.Empty, - Bcc = bcc, - ReplyTo = replyTo, - ReplyToName = replyToName, - Subject = subjectReplaced, - Body = bodyReplaced, - CreatedOnUtc = DateTime.UtcNow, - EmailAccountId = emailAccount.Id, - SendManually = messageTemplate.SendManually - }; - - // create attachments if any - var fileIds = (new int?[] - { - messageTemplate.GetLocalized(x => x.Attachment1FileId, languageId), - messageTemplate.GetLocalized(x => x.Attachment2FileId, languageId), - messageTemplate.GetLocalized(x => x.Attachment3FileId, languageId) - }) - .Where(x => x.HasValue) - .Select(x => x.Value) - .ToArray(); - - if (fileIds.Any()) - { - var files = _downloadServioce.GetDownloadsByIds(fileIds); - foreach (var file in files) - { - email.Attachments.Add(new QueuedEmailAttachment - { - StorageLocation = EmailAttachmentStorageLocation.FileReference, - FileId = file.Id, - Name = (file.Filename.NullEmpty() ?? file.Id.ToString()) + file.Extension.EmptyNull(), - MimeType = file.ContentType.NullEmpty() ?? "application/octet-stream" - }); - } - } - - - // publish event so that integrators can add attachments, alter the email etc. - _eventPublisher.Publish(new QueuingEmailEvent - { - EmailAccount = emailAccount, - LanguageId = languageId, - MessageTemplate = messageTemplate, - QueuedEmail = email, - Tokens = tokens - }); - - _queuedEmailService.InsertQueuedEmail(email); - - return email.Id; - } - - protected MessageTemplate GetActiveMessageTemplate(string messageTemplateName, int storeId) - { - var messageTemplate = _messageTemplateService.GetMessageTemplateByName(messageTemplateName, storeId); - - if (messageTemplate == null) - return null; - - //ensure it's active - var isActive = messageTemplate.IsActive; - if (!isActive) - return null; - - return messageTemplate; - } - - protected EmailAccount GetEmailAccountOfMessageTemplate(MessageTemplate messageTemplate, int languageId) - { - var emailAccounId = messageTemplate.GetLocalized(mt => mt.EmailAccountId, languageId); - var emailAccount = _emailAccountService.GetEmailAccountById(emailAccounId); - if (emailAccount == null) - emailAccount = _emailAccountService.GetDefaultEmailAccount(); - - return emailAccount; - } - - private Tuple GetReplyToEmail(Customer customer) - { - if (customer == null || customer.Email.IsEmpty()) - return new Tuple(null, null); - - string email = customer.Email; - string name = GetDisplayNameForCustomer(customer); - - return new Tuple(email, name); - } - - private string GetDisplayNameForCustomer(Customer customer) - { - if (customer == null) - return string.Empty; - - Func getName = (address) => { - if (address == null) - return null; - - string result = string.Empty; - if (address.FirstName.HasValue() || address.LastName.HasValue()) - { - result = string.Format("{0} {1}", address.FirstName, address.LastName).Trim(); - } - - if (address.Company.HasValue()) - { - result = string.Concat(result, result.HasValue() ? ", " : "", address.Company); - } - - return result; - }; - - string name = getName(customer.BillingAddress); - if (name.IsEmpty()) - { - name = getName(customer.ShippingAddress); - } - if (name.IsEmpty()) - { - name = getName(customer.Addresses.FirstOrDefault()); - } - - name = name.TrimSafe().NullEmpty(); - - return name ?? customer.Username.EmptyNull(); - } - - protected Language EnsureLanguageIsActive(int languageId, int storeId) - { - //load language by specified ID - var language = _languageService.GetLanguageById(languageId); - - if (language == null || !language.Published) - { - //load any language from the specified store - language = _languageService.GetAllLanguages(storeId: storeId).FirstOrDefault(); - } - if (language == null || !language.Published) - { - //load any language - language = _languageService.GetAllLanguages().FirstOrDefault(); - } - - if (language == null) - throw new SmartException(T("Common.Error.NoActiveLanguage")); - - return language; - } - - #endregion - - #region Methods - - #region Customer workflow - - /// - /// Sends 'New customer' notification message to a store owner - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - public virtual int SendCustomerRegisteredNotificationMessage(Customer customer, int languageId) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("NewCustomer.Notification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use customer email as reply address - var replyTo = GetReplyToEmail(customer); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); - } - - /// - /// Sends a welcome message to a customer - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - public virtual int SendCustomerWelcomeMessage(Customer customer, int languageId) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Customer.WelcomeMessage", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = customer.Email; - var toName = customer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends an email validation message to a customer - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - public virtual int SendCustomerEmailValidationMessage(Customer customer, int languageId) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Customer.EmailValidationMessage", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = customer.Email; - var toName = customer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends password recovery message to a customer - /// - /// Customer instance - /// Message language identifier - /// Queued email identifier - public virtual int SendCustomerPasswordRecoveryMessage(Customer customer, int languageId) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Customer.PasswordRecovery", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = customer.Email; - var toName = customer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - #endregion - - #region Order workflow - - /// - /// Sends an order placed notification to a store owner - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - public virtual int SendOrderPlacedStoreOwnerNotification(Order order, int languageId) - { - if (order == null) - throw new ArgumentNullException("order"); - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("OrderPlaced.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, order, language); - _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use buyer's email as reply address - var replyToEmail = order.BillingAddress.Email; - var replyToName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - if (order.BillingAddress.Company.HasValue()) - { - replyToName += ", " + order.BillingAddress.Company; - } - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyToEmail, replyToName); - } - - /// - /// Sends an order placed notification to a customer - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - public virtual int SendOrderPlacedCustomerNotification(Order order, int languageId) - { - if (order == null) - throw new ArgumentNullException("order"); - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("OrderPlaced.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, order, language); - _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); - - _messageTokenProvider.AddCompanyTokens(tokens); - _messageTokenProvider.AddBankConnectionTokens(tokens); - _messageTokenProvider.AddContactDataTokens(tokens); - - // event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = order.BillingAddress.Email; - var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a shipment sent notification to a customer - /// - /// Shipment - /// Message language identifier - /// Queued email identifier - public virtual int SendShipmentSentCustomerNotification(Shipment shipment, int languageId) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - var order = shipment.Order; - if (order == null) - throw new SmartException(T("Order.NotFound", shipment.OrderId)); - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("ShipmentSent.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddShipmentTokens(tokens, shipment, language); - _messageTokenProvider.AddOrderTokens(tokens, shipment.Order, language); - _messageTokenProvider.AddCustomerTokens(tokens, shipment.Order.Customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = order.BillingAddress.Email; - var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a shipment delivered notification to a customer - /// - /// Shipment - /// Message language identifier - /// Queued email identifier - public virtual int SendShipmentDeliveredCustomerNotification(Shipment shipment, int languageId) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - var order = shipment.Order; - if (order == null) - throw new SmartException(T("Order.NotFound", shipment.OrderId)); - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("ShipmentDelivered.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddShipmentTokens(tokens, shipment, language); - _messageTokenProvider.AddOrderTokens(tokens, shipment.Order, language); - _messageTokenProvider.AddCustomerTokens(tokens, shipment.Order.Customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = order.BillingAddress.Email; - var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends an order completed notification to a customer - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - public virtual int SendOrderCompletedCustomerNotification(Order order, int languageId) - { - if (order == null) - throw new ArgumentNullException("order"); - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("OrderCompleted.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, order, language); - _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); - - _messageTokenProvider.AddCompanyTokens(tokens); - _messageTokenProvider.AddBankConnectionTokens(tokens); - _messageTokenProvider.AddContactDataTokens(tokens); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = order.BillingAddress.Email; - var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends an order cancelled notification to a customer - /// - /// Order instance - /// Message language identifier - /// Queued email identifier - public virtual int SendOrderCancelledCustomerNotification(Order order, int languageId) - { - if (order == null) - throw new ArgumentNullException("order"); - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("OrderCancelled.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, order, language); - _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = order.BillingAddress.Email; - var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a new order note added notification to a customer - /// - /// Order note - /// Message language identifier - /// Queued email identifier - public virtual int SendNewOrderNoteAddedCustomerNotification(OrderNote orderNote, int languageId) - { - if (orderNote == null) - throw new ArgumentNullException("orderNote"); - - var order = orderNote.Order; - - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Customer.NewOrderNote", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderNoteTokens(tokens, orderNote); - _messageTokenProvider.AddOrderTokens(tokens, orderNote.Order, language); - _messageTokenProvider.AddCustomerTokens(tokens, orderNote.Order.Customer); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = order.BillingAddress.Email; - var toName = string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a "Recurring payment cancelled" notification to a store owner - /// - /// Recurring payment - /// Message language identifier - /// Queued email identifier - public virtual int SendRecurringPaymentCancelledStoreOwnerNotification(RecurringPayment recurringPayment, int languageId) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - var store = _storeService.GetStoreById(recurringPayment.InitialOrder.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("RecurringPaymentCancelled.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, recurringPayment.InitialOrder, language); - _messageTokenProvider.AddCustomerTokens(tokens, recurringPayment.InitialOrder.Customer); - _messageTokenProvider.AddRecurringPaymentTokens(tokens, recurringPayment); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - #endregion - - #region Newsletter workflow - - /// - /// Sends a newsletter subscription activation message - /// - /// Newsletter subscription - /// Language identifier - /// Queued email identifier - public virtual int SendNewsLetterSubscriptionActivationMessage(NewsLetterSubscription subscription, int languageId) - { - if (subscription == null) - throw new ArgumentNullException("subscription"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("NewsLetterSubscription.ActivationMessage", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddNewsLetterSubscriptionTokens(tokens, subscription); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = subscription.Email; - var toName = ""; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a newsletter subscription deactivation message - /// - /// Newsletter subscription - /// Language identifier - /// Queued email identifier - public virtual int SendNewsLetterSubscriptionDeactivationMessage(NewsLetterSubscription subscription, int languageId) - { - if (subscription == null) - throw new ArgumentNullException("subscription"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("NewsLetterSubscription.DeactivationMessage", store.Id); - if (messageTemplate == null) - return 0; - - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, _storeContext.CurrentStore); - _messageTokenProvider.AddNewsLetterSubscriptionTokens(tokens, subscription); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = subscription.Email; - var toName = ""; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - #endregion - - #region Send a message to a friend - - /// - /// Sends "email a friend" message - /// - /// Customer instance - /// Message language identifier - /// Product instance - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - public virtual int SendProductEmailAFriendMessage(Customer customer, int languageId, - Product product, string customerEmail, string friendsEmail, string personalMessage) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (product == null) - throw new ArgumentNullException("product"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Service.EmailAFriend", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - _messageTokenProvider.AddProductTokens(tokens, product, language); - - tokens.Add(new Token("EmailAFriend.PersonalMessage", personalMessage, true)); - tokens.Add(new Token("EmailAFriend.Email", customerEmail)); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = friendsEmail; - var toName = ""; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - public virtual int SendProductQuestionMessage(Customer customer, int languageId, Product product, - string senderEmail, string senderName, string senderPhone, string question) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (customer.IsSystemAccount) - return 0; - - if (product == null) - throw new ArgumentNullException("product"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Product.AskQuestion", store.Id); - if (messageTemplate == null) - return 0; - - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - _messageTokenProvider.AddProductTokens(tokens, product, language); - - tokens.Add(new Token("ProductQuestion.Message", question, true)); - tokens.Add(new Token("ProductQuestion.SenderEmail", senderEmail)); - tokens.Add(new Token("ProductQuestion.SenderName", senderName)); - tokens.Add(new Token("ProductQuestion.SenderPhone", senderPhone)); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, senderEmail, senderName); - } - - /// - /// Sends wishlist "email a friend" message - /// - /// Customer - /// Message language identifier - /// Customer's email - /// Friend's email - /// Personal message - /// Queued email identifier - public virtual int SendWishlistEmailAFriendMessage(Customer customer, int languageId, - string customerEmail, string friendsEmail, string personalMessage) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Wishlist.EmailAFriend", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - tokens.Add(new Token("Wishlist.PersonalMessage", personalMessage, true)); - tokens.Add(new Token("Wishlist.Email", customerEmail)); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = friendsEmail; - var toName = ""; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - #endregion - - #region Return requests - - /// - /// Sends 'New Return Request' message to a store owner - /// - /// Return request - /// Order item - /// Message language identifier - /// Queued email identifier - public virtual int SendNewReturnRequestStoreOwnerNotification(ReturnRequest returnRequest, OrderItem orderItem, int languageId) - { - if (returnRequest == null) - throw new ArgumentNullException("returnRequest"); - - var store = _storeService.GetStoreById(orderItem.Order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("NewReturnRequest.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, returnRequest.Customer); - _messageTokenProvider.AddReturnRequestTokens(tokens, returnRequest, orderItem); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use customer email as reply address - var replyTo = GetReplyToEmail(returnRequest.Customer); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); - } - - /// - /// Sends 'Return Request status changed' message to a customer - /// - /// Return request - /// Order item - /// Message language identifier - /// Queued email identifier - public virtual int SendReturnRequestStatusChangedCustomerNotification(ReturnRequest returnRequest, OrderItem orderItem, int languageId) - { - if (returnRequest == null) - throw new ArgumentNullException("returnRequest"); - - var store = _storeService.GetStoreById(orderItem.Order.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("ReturnRequestStatusChanged.CustomerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, returnRequest.Customer); - _messageTokenProvider.AddReturnRequestTokens(tokens, returnRequest, orderItem); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = returnRequest.Customer.FindEmail(); - var toName = returnRequest.Customer.GetFullName(); - - if (toEmail.IsEmpty()) - return 0; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - #endregion - - #region Forum Notifications - - /// - /// Sends a forum subscription message to a customer - /// - /// Customer instance - /// Forum Topic - /// Forum - /// Message language identifier - /// Queued email identifier - public int SendNewForumTopicMessage(Customer customer, ForumTopic forumTopic, Forum forum, int languageId) - { - if (customer == null) - { - throw new ArgumentNullException("customer"); - } - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Forums.NewForumTopic", store.Id); - if (messageTemplate == null) - { - return 0; - } - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - _messageTokenProvider.AddForumTopicTokens(tokens, forumTopic); - _messageTokenProvider.AddForumTokens(tokens, forumTopic.Forum, language); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = customer.Email; - var toName = customer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a forum subscription message to a customer - /// - /// Customer instance - /// Forum post - /// Forum Topic - /// Forum - /// Friendly (starts with 1) forum topic page to use for URL generation - /// Message language identifier - /// Queued email identifier - public int SendNewForumPostMessage(Customer customer, ForumPost forumPost, ForumTopic forumTopic, Forum forum, int friendlyForumTopicPageIndex, int languageId) - { - if (customer == null) - { - throw new ArgumentNullException("customer"); - } - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Forums.NewForumPost", store.Id); - if (messageTemplate == null) - { - return 0; - } - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddForumPostTokens(tokens, forumPost); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - _messageTokenProvider.AddForumTopicTokens(tokens, forumPost.ForumTopic, friendlyForumTopicPageIndex, forumPost.Id); - _messageTokenProvider.AddForumTokens(tokens, forumPost.ForumTopic.Forum, language); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = customer.Email; - var toName = customer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a private message notification - /// - /// Private message - /// Message language identifier - /// Queued email identifier - public int SendPrivateMessageNotification(Customer customer, PrivateMessage privateMessage, int languageId) - { - if (privateMessage == null) - { - throw new ArgumentNullException("privateMessage"); - } - - var store = _storeService.GetStoreById(privateMessage.StoreId) ?? _storeContext.CurrentStore; - - var messageTemplate = GetActiveMessageTemplate("Customer.NewPM", store.Id); - if (messageTemplate == null) - { - return 0; - } - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - _messageTokenProvider.AddPrivateMessageTokens(tokens, privateMessage); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId); - var toEmail = privateMessage.ToCustomer.Email; - var toName = privateMessage.ToCustomer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName); - } - - #endregion - - #region Misc - - public virtual int SendGenericMessage(string messageTemplateName, Action cfg) - { - Guard.ArgumentNotNull(() => cfg); - Guard.ArgumentNotEmpty(() => messageTemplateName); - - var ctx = new GenericMessageContext(); - ctx.MessagenTokenProvider = _messageTokenProvider; - - cfg(ctx); - - if (!ctx.StoreId.HasValue) - { - ctx.StoreId = _storeContext.CurrentStore.Id; - } - - if (!ctx.LanguageId.HasValue) - { - ctx.LanguageId = _workContext.WorkingLanguage.Id; - } - - if (ctx.Customer == null) - { - ctx.Customer = _workContext.CurrentCustomer; - } - - if (ctx.Customer.IsSystemAccount) - return 0; - - _messageTokenProvider.AddCustomerTokens(ctx.Tokens, ctx.Customer); - _messageTokenProvider.AddStoreTokens(ctx.Tokens, _storeService.GetStoreById(ctx.StoreId.Value)); - - var language = EnsureLanguageIsActive(ctx.LanguageId.Value, ctx.StoreId.Value); - ctx.LanguageId = language.Id; - - var messageTemplate = GetActiveMessageTemplate(messageTemplateName, ctx.StoreId.Value); - if (messageTemplate == null) - return 0; - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, ctx.LanguageId.Value); - var toEmail = ctx.ToEmail.HasValue() ? ctx.ToEmail : emailAccount.Email; - var toName = ctx.ToName.HasValue() ? ctx.ToName : emailAccount.DisplayName; - - if (ctx.ReplyToCustomer && ctx.Customer != null) - { - // use customer email as reply address - var replyTo = GetReplyToEmail(ctx.Customer); - ctx.ReplyToEmail = replyTo.Item1; - ctx.ReplyToName = replyTo.Item2; - } - - return SendNotification(messageTemplate, emailAccount, ctx.LanguageId.Value, ctx.Tokens, toEmail, toName, ctx.ReplyToEmail, ctx.ReplyToName); - } - - /// - /// Sends a gift card notification - /// - /// Gift card - /// Message language identifier - /// Queued email identifier - public virtual int SendGiftCardNotification(GiftCard giftCard, int languageId) - { - if (giftCard == null) - throw new ArgumentNullException("giftCard"); - - Store store = null; - var order = giftCard.PurchasedWithOrderItem != null ? - giftCard.PurchasedWithOrderItem.Order : - null; - if (order != null) - store = _storeService.GetStoreById(order.StoreId); - if (store == null) - store = _storeContext.CurrentStore; - - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("GiftCard.Notification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddGiftCardTokens(tokens, giftCard); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = giftCard.RecipientEmail; - var toName = giftCard.RecipientName; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a product review notification message to a store owner - /// - /// Product review - /// Message language identifier - /// Queued email identifier - public virtual int SendProductReviewNotificationMessage(ProductReview productReview, int languageId) - { - if (productReview == null) - throw new ArgumentNullException("productReview"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Product.ProductReview", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddProductReviewTokens(tokens, productReview); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use customer email as reply address - var replyTo = GetReplyToEmail(productReview.Customer); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); - } - - /// - /// Sends a "quantity below" notification to a store owner - /// - /// Product - /// Message language identifier - /// Queued email identifier - public virtual int SendQuantityBelowStoreOwnerNotification(Product product, int languageId) - { - if (product == null) - throw new ArgumentNullException("product"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("QuantityBelow.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddProductTokens(tokens, product, language); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - /// - /// Sends a "new VAT sumitted" notification to a store owner - /// - /// Customer - /// Received VAT name - /// Received VAT address - /// Message language identifier - /// Queued email identifier - public virtual int SendNewVatSubmittedStoreOwnerNotification(Customer customer, string vatName, string vatAddress, int languageId) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("NewVATSubmitted.StoreOwnerNotification", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, customer); - tokens.Add(new Token("VatValidationResult.Name", vatName)); - tokens.Add(new Token("VatValidationResult.Address", vatAddress)); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use customer email as reply address - var replyTo = GetReplyToEmail(customer); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); - } - - /// - /// Sends a blog comment notification message to a store owner - /// - /// Blog comment - /// Message language identifier - /// Queued email identifier - public virtual int SendBlogCommentNotificationMessage(BlogComment blogComment, int languageId) - { - if (blogComment == null) - throw new ArgumentNullException("blogComment"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Blog.BlogComment", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddBlogCommentTokens(tokens, blogComment); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use customer email as reply address - var replyTo = GetReplyToEmail(blogComment.Customer); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); - } - - /// - /// Sends a news comment notification message to a store owner - /// - /// News comment - /// Message language identifier - /// Queued email identifier - public virtual int SendNewsCommentNotificationMessage(NewsComment newsComment, int languageId) - { - if (newsComment == null) - throw new ArgumentNullException("newsComment"); - - var store = _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("News.NewsComment", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddNewsCommentTokens(tokens, newsComment); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var toEmail = emailAccount.Email; - var toName = emailAccount.DisplayName; - - // use customer email as sender/reply address - var replyTo = GetReplyToEmail(newsComment.Customer); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName, replyTo.Item1, replyTo.Item2); - } - - /// - /// Sends a 'Back in stock' notification message to a customer - /// - /// Subscription - /// Message language identifier - /// Queued email identifier - public virtual int SendBackInStockNotification(BackInStockSubscription subscription, int languageId) - { - if (subscription == null) - throw new ArgumentNullException("subscription"); - - var store = _storeService.GetStoreById(subscription.StoreId) ?? _storeContext.CurrentStore; - var language = EnsureLanguageIsActive(languageId, store.Id); - - var messageTemplate = GetActiveMessageTemplate("Customer.BackInStock", store.Id); - if (messageTemplate == null) - return 0; - - //tokens - var tokens = new List(); - _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddCustomerTokens(tokens, subscription.Customer); - _messageTokenProvider.AddBackInStockTokens(tokens, subscription); - - //event notification - _eventPublisher.MessageTokensAdded(messageTemplate, tokens); - - var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, language.Id); - var customer = subscription.Customer; - var toEmail = customer.Email; - var toName = customer.GetFullName(); - - return SendNotification(messageTemplate, emailAccount, language.Id, tokens, toEmail, toName); - } - - #endregion - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/News/NewsMessageFactoryExtensions.cs b/src/Libraries/SmartStore.Services/News/NewsMessageFactoryExtensions.cs new file mode 100644 index 0000000000..be7b2f8284 --- /dev/null +++ b/src/Libraries/SmartStore.Services/News/NewsMessageFactoryExtensions.cs @@ -0,0 +1,19 @@ +using System; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.News; +using SmartStore.Services.Messages; + +namespace SmartStore.Services.News +{ + public static class NewsMessageFactoryExtensions + { + /// + /// Sends a news comment notification message to a store owner + /// + public static CreateMessageResult SendNewsCommentNotificationMessage(this IMessageFactory factory, NewsComment newsComment, int languageId = 0) + { + Guard.NotNull(newsComment, nameof(newsComment)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewsCommentStoreOwner, languageId, customer: newsComment.Customer), true, newsComment); + } + } +} diff --git a/src/Libraries/SmartStore.Services/News/NewsService.cs b/src/Libraries/SmartStore.Services/News/NewsService.cs index 743ab7f9f4..c7c041db1a 100644 --- a/src/Libraries/SmartStore.Services/News/NewsService.cs +++ b/src/Libraries/SmartStore.Services/News/NewsService.cs @@ -54,9 +54,6 @@ public virtual void DeleteNews(NewsItem newsItem) throw new ArgumentNullException("newsItem"); _newsItemRepository.Delete(newsItem); - - //event notification - _services.EventPublisher.EntityDeleted(newsItem); } /// @@ -157,9 +154,6 @@ public virtual void InsertNews(NewsItem news) throw new ArgumentNullException("news"); _newsItemRepository.Insert(news); - - //event notification - _services.EventPublisher.EntityInserted(news); } /// @@ -172,9 +166,6 @@ public virtual void UpdateNews(NewsItem news) throw new ArgumentNullException("news"); _newsItemRepository.Update(news); - - //event notification - _services.EventPublisher.EntityUpdated(news); } /// diff --git a/src/Libraries/SmartStore.Services/Orders/AddToCartContext.cs b/src/Libraries/SmartStore.Services/Orders/AddToCartContext.cs index 27c3f37023..e747b73fa3 100644 --- a/src/Libraries/SmartStore.Services/Orders/AddToCartContext.cs +++ b/src/Libraries/SmartStore.Services/Orders/AddToCartContext.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using System.Collections.Specialized; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; +using SmartStore.Services.Catalog.Modelling; namespace SmartStore.Services.Orders { @@ -24,8 +24,8 @@ public AddToCartContext() public Customer Customer { get; set; } public Product Product { get; set; } public ShoppingCartType CartType { get; set; } - public NameValueCollection AttributeForm { get; set; } - public string Attributes { get; set; } + public ProductVariantQuery VariantQuery { get; set; } + public string AttributesXml { get; set; } public decimal CustomerEnteredPrice { get; set; } public int Quantity { get; set; } public bool AddRequiredProducts { get; set; } 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/CheckoutAttributeParser.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeParser.cs index 920cfbf49c..ed51878fd0 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeParser.cs @@ -6,9 +6,6 @@ namespace SmartStore.Services.Orders { - /// - /// Checkout attribute parser - /// public partial class CheckoutAttributeParser : ICheckoutAttributeParser { private readonly ICheckoutAttributeService _checkoutAttributeService; @@ -18,11 +15,6 @@ public CheckoutAttributeParser(ICheckoutAttributeService checkoutAttributeServic this._checkoutAttributeService = checkoutAttributeService; } - /// - /// Gets selected checkout attribute identifiers - /// - /// Attributes - /// Selected checkout attribute identifiers public IList ParseCheckoutAttributeIds(string attributes) { var ids = new List(); @@ -40,8 +32,7 @@ public IList ParseCheckoutAttributeIds(string attributes) if (node1.Attributes != null && node1.Attributes["ID"] != null) { string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id = 0; - if (int.TryParse(str1, out id)) + if (int.TryParse(str1, out var id)) { ids.Add(id); } @@ -55,11 +46,6 @@ public IList ParseCheckoutAttributeIds(string attributes) return ids; } - /// - /// Gets selected checkout attributes - /// - /// Attributes - /// Selected checkout attributes public IList ParseCheckoutAttributes(string attributes) { var caCollection = new List(); @@ -75,11 +61,6 @@ public IList ParseCheckoutAttributes(string attributes) return caCollection; } - /// - /// Get checkout attribute values - /// - /// Attributes - /// Checkout attribute values public IList ParseCheckoutAttributeValues(string attributes) { var caValues = new List(); @@ -94,8 +75,7 @@ public IList ParseCheckoutAttributeValues(string attribu { if (!String.IsNullOrEmpty(caValueStr)) { - int caValueId = 0; - if (int.TryParse(caValueStr, out caValueId)) + if (int.TryParse(caValueStr, out var caValueId)) { var caValue = _checkoutAttributeService.GetCheckoutAttributeValueById(caValueId); if (caValue != null) @@ -107,56 +87,47 @@ public IList ParseCheckoutAttributeValues(string attribu return caValues; } - /// - /// Gets selected checkout attribute value - /// - /// Attributes - /// Checkout attribute identifier - /// Checkout attribute value public IList ParseValues(string attributes, int checkoutAttributeId) { var selectedCheckoutAttributeValues = new List(); - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributes); - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/CheckoutAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id = 0; - if (int.TryParse(str1, out id)) - { - if (id == checkoutAttributeId) - { - var nodeList2 = node1.SelectNodes(@"CheckoutAttributeValue/Value"); - foreach (XmlNode node2 in nodeList2) - { - string value = node2.InnerText.Trim(); - selectedCheckoutAttributeValues.Add(value); - } - } - } - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } + if (attributes.HasValue()) + { + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributes); + + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/CheckoutAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + var str1 = node1.Attributes["ID"].InnerText.Trim(); + if (int.TryParse(str1, out var id)) + { + if (id == checkoutAttributeId) + { + var nodeList2 = node1.SelectNodes(@"CheckoutAttributeValue/Value"); + foreach (XmlNode node2 in nodeList2) + { + string value = node2.InnerText.Trim(); + selectedCheckoutAttributeValues.Add(value); + } + } + } + } + } + } + catch (Exception ex) + { + Debug.Write(ex.ToString()); + } + } + return selectedCheckoutAttributeValues; } - /// - /// Adds an attribute - /// - /// Attributes - /// Checkout attribute - /// Value - /// Attributes public string AddCheckoutAttribute(string attributes, CheckoutAttribute ca, string value) { string result = string.Empty; @@ -182,8 +153,7 @@ public string AddCheckoutAttribute(string attributes, CheckoutAttribute ca, stri if (node1.Attributes != null && node1.Attributes["ID"] != null) { string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id = 0; - if (int.TryParse(str1, out id)) + if (int.TryParse(str1, out var id)) { if (id == ca.Id) { @@ -218,12 +188,6 @@ public string AddCheckoutAttribute(string attributes, CheckoutAttribute ca, stri return result; } - /// - /// Removes checkout attributes which cannot be applied to the current cart and returns an update attributes in XML format - /// - /// Attributes in XML format - /// Shopping cart items - /// Updated attributes in XML format public virtual string EnsureOnlyActiveAttributes(string attributes, IList cart) { if (String.IsNullOrEmpty(attributes)) @@ -256,8 +220,7 @@ public virtual string EnsureOnlyActiveAttributes(string attributes, IList - /// Checkout attribute service - /// public partial class CheckoutAttributeService : ICheckoutAttributeService { - #region Constants - - private const string CHECKOUTATTRIBUTES_ALL_KEY = "SmartStore.checkoutattribute.all-{0}-{1}"; - private const string CHECKOUTATTRIBUTEVALUES_ALL_KEY = "SmartStore.checkoutattributevalue.all-{0}"; - private const string CHECKOUTATTRIBUTES_PATTERN_KEY = "SmartStore.checkoutattribute."; - private const string CHECKOUTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.checkoutattributevalue."; - private const string CHECKOUTATTRIBUTES_BY_ID_KEY = "SmartStore.checkoutattribute.id-{0}"; - private const string CHECKOUTATTRIBUTEVALUES_BY_ID_KEY = "SmartStore.checkoutattributevalue.id-{0}"; - - #endregion - - #region Fields - private readonly IRepository _checkoutAttributeRepository; private readonly IRepository _checkoutAttributeValueRepository; private readonly IRepository _storeMappingRepository; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - - #endregion - - #region Ctor - public CheckoutAttributeService(IRequestCache requestCache, + public CheckoutAttributeService( IRepository checkoutAttributeRepository, IRepository checkoutAttributeValueRepository, IRepository storeMappingRepository, IEventPublisher eventPublisher) { - _requestCache = requestCache; _checkoutAttributeRepository = checkoutAttributeRepository; _checkoutAttributeValueRepository = checkoutAttributeValueRepository; _storeMappingRepository = storeMappingRepository; @@ -52,38 +30,18 @@ public CheckoutAttributeService(IRequestCache requestCache, this.QuerySettings = DbQuerySettings.Default; } - #endregion - public DbQuerySettings QuerySettings { get; set; } - #region Methods - #region Checkout attributes - /// - /// Deletes a checkout attribute - /// - /// Checkout attribute public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) { if (checkoutAttribute == null) throw new ArgumentNullException("checkoutAttribute"); _checkoutAttributeRepository.Delete(checkoutAttribute); - - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(checkoutAttribute); } - /// - /// Gets checkout attributes - /// - /// Whether to filter result by store identifier - /// A value indicating whether to show hidden records - /// Checkout attributes query public virtual IQueryable GetCheckoutAttributes(int storeId = 0, bool showHidden = false) { var query = _checkoutAttributeRepository.Table; @@ -112,23 +70,11 @@ orderby grp.Key return query; } - /// - /// Gets all checkout attributes - /// - /// Whether to filter result by store identifier - /// A value indicating whether to show hidden records - /// Checkout attribute collection public virtual IList GetAllCheckoutAttributes(int storeId = 0, bool showHidden = false) { - string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(storeId, showHidden); - - return _requestCache.Get(key, () => - { - var query = GetCheckoutAttributes(storeId, showHidden); - - return query.ToList(); - }); - } + var query = GetCheckoutAttributes(storeId, showHidden); + return query.ToListCached("db.checkoutattrs.{0}.{1}".FormatInvariant(storeId, showHidden)); + } /// /// Gets a checkout attribute @@ -140,145 +86,71 @@ public virtual CheckoutAttribute GetCheckoutAttributeById(int checkoutAttributeI if (checkoutAttributeId == 0) return null; - string key = string.Format(CHECKOUTATTRIBUTES_BY_ID_KEY, checkoutAttributeId); - return _requestCache.Get(key, () => - { - return _checkoutAttributeRepository.GetById(checkoutAttributeId); - }); - } + return _checkoutAttributeRepository.GetByIdCached(checkoutAttributeId, "db.checkoutattr.id-" + checkoutAttributeId); + } - /// - /// Inserts a checkout attribute - /// - /// Checkout attribute public virtual void InsertCheckoutAttribute(CheckoutAttribute checkoutAttribute) { if (checkoutAttribute == null) throw new ArgumentNullException("checkoutAttribute"); _checkoutAttributeRepository.Insert(checkoutAttribute); - - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(checkoutAttribute); } - /// - /// Updates the checkout attribute - /// - /// Checkout attribute public virtual void UpdateCheckoutAttribute(CheckoutAttribute checkoutAttribute) { if (checkoutAttribute == null) throw new ArgumentNullException("checkoutAttribute"); _checkoutAttributeRepository.Update(checkoutAttribute); - - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(checkoutAttribute); } #endregion #region Checkout variant attribute values - /// - /// Deletes a checkout attribute value - /// - /// Checkout attribute value public virtual void DeleteCheckoutAttributeValue(CheckoutAttributeValue checkoutAttributeValue) { if (checkoutAttributeValue == null) throw new ArgumentNullException("checkoutAttributeValue"); _checkoutAttributeValueRepository.Delete(checkoutAttributeValue); - - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(checkoutAttributeValue); } - /// - /// Gets checkout attribute values by checkout attribute identifier - /// - /// The checkout attribute identifier - /// Checkout attribute value collection public virtual IList GetCheckoutAttributeValues(int checkoutAttributeId) { - string key = string.Format(CHECKOUTATTRIBUTEVALUES_ALL_KEY, checkoutAttributeId); - return _requestCache.Get(key, () => - { - var query = from cav in _checkoutAttributeValueRepository.Table - orderby cav.DisplayOrder - where cav.CheckoutAttributeId == checkoutAttributeId - select cav; - var checkoutAttributeValues = query.ToList(); - return checkoutAttributeValues; - }); - } + var query = from cav in _checkoutAttributeValueRepository.Table + orderby cav.DisplayOrder + where cav.CheckoutAttributeId == checkoutAttributeId + select cav; + var checkoutAttributeValues = query.ToListCached(); + return checkoutAttributeValues; + } - /// - /// Gets a checkout attribute value - /// - /// Checkout attribute value identifier - /// Checkout attribute value public virtual CheckoutAttributeValue GetCheckoutAttributeValueById(int checkoutAttributeValueId) { if (checkoutAttributeValueId == 0) return null; - string key = string.Format(CHECKOUTATTRIBUTEVALUES_BY_ID_KEY, checkoutAttributeValueId); - return _requestCache.Get(key, () => - { - return _checkoutAttributeValueRepository.GetById(checkoutAttributeValueId); - }); - } + return _checkoutAttributeValueRepository.GetByIdCached(checkoutAttributeValueId, "db.checkoutattrval.id-" + checkoutAttributeValueId); + } - /// - /// Inserts a checkout attribute value - /// - /// Checkout attribute value public virtual void InsertCheckoutAttributeValue(CheckoutAttributeValue checkoutAttributeValue) { if (checkoutAttributeValue == null) throw new ArgumentNullException("checkoutAttributeValue"); _checkoutAttributeValueRepository.Insert(checkoutAttributeValue); - - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(checkoutAttributeValue); } - /// - /// Updates the checkout attribute value - /// - /// Checkout attribute value public virtual void UpdateCheckoutAttributeValue(CheckoutAttributeValue checkoutAttributeValue) { if (checkoutAttributeValue == null) throw new ArgumentNullException("checkoutAttributeValue"); _checkoutAttributeValueRepository.Update(checkoutAttributeValue); - - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTES_PATTERN_KEY); - _requestCache.RemoveByPattern(CHECKOUTATTRIBUTEVALUES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(checkoutAttributeValue); } #endregion - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutState.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutState.cs index 6c876aa266..a2da7e33e2 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutState.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutState.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Web.Routing; namespace SmartStore.Services.Orders { @@ -9,31 +8,30 @@ public partial class CheckoutState { public CheckoutState() { - CustomProperties = new RouteValueDictionary(); + CustomProperties = new Dictionary(); + PaymentData = new Dictionary(); } public static string CheckoutStateSessionKey { get { return "SmCheckoutState"; } } - public string PaymentSummary - { - get - { - return CustomProperties["_PaymentSummary"] as string; - } - set - { - CustomProperties["_PaymentSummary"] = value; - } - } + /// + /// The payment summary as displayed on the checkout confirmation page + /// + public string PaymentSummary { get; set; } /// - /// Indicated whether the page with the payment method selection was skipped during checkout. + /// Indicates whether the payment method selection page was skipped /// public bool IsPaymentSelectionSkipped { get; set; } /// - /// Use this dictionary for any custom data required along checkout flow. + /// Use this dictionary for any custom data required along checkout flow /// public IDictionary CustomProperties { get; set; } + + /// + /// Payment data entered on payment method selection page + /// + public IDictionary PaymentData { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/Orders/GiftCardService.cs b/src/Libraries/SmartStore.Services/Orders/GiftCardService.cs index 128993202e..d89896397d 100644 --- a/src/Libraries/SmartStore.Services/Orders/GiftCardService.cs +++ b/src/Libraries/SmartStore.Services/Orders/GiftCardService.cs @@ -49,8 +49,6 @@ public virtual void DeleteGiftCard(GiftCard giftCard) _giftCardRepository.Delete(giftCard); - //event notification - _eventPublisher.EntityDeleted(giftCard); } /// @@ -107,9 +105,6 @@ public virtual void InsertGiftCard(GiftCard giftCard) throw new ArgumentNullException("giftCard"); _giftCardRepository.Insert(giftCard); - - //event notification - _eventPublisher.EntityInserted(giftCard); } /// @@ -122,9 +117,6 @@ public virtual void UpdateGiftCard(GiftCard giftCard) throw new ArgumentNullException("giftCard"); _giftCardRepository.Update(giftCard); - - //event notification - _eventPublisher.EntityUpdated(giftCard); } /// @@ -145,12 +137,7 @@ public virtual IList GetGiftCardsByPurchasedWithOrderItemId(int purcha return giftCards; } - /// - /// Get active gift cards that are applied by a customer - /// - /// Customer - /// Active gift cards - public virtual IList GetActiveGiftCardsAppliedByCustomer(Customer customer) + public virtual IList GetActiveGiftCardsAppliedByCustomer(Customer customer, int storeId) { var result = new List(); if (customer == null) @@ -162,7 +149,7 @@ public virtual IList GetActiveGiftCardsAppliedByCustomer(Customer cust var giftCards = GetAllGiftCards(null, null, null, true, couponCode); foreach (var gc in giftCards) { - if (gc.IsGiftCardValid()) + if (gc.IsGiftCardValid(storeId)) result.Add(gc); } } diff --git a/src/Libraries/SmartStore.Services/Orders/IGiftCardService.cs b/src/Libraries/SmartStore.Services/Orders/IGiftCardService.cs index 878fa81a66..6c7255852b 100644 --- a/src/Libraries/SmartStore.Services/Orders/IGiftCardService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IGiftCardService.cs @@ -58,8 +58,9 @@ IList GetAllGiftCards(int? purchasedWithOrderId, DateTime? startTime = /// Get active gift cards that are applied by a customer /// /// Customer + /// Store identifier /// Active gift cards - IList GetActiveGiftCardsAppliedByCustomer(Customer customer); + IList GetActiveGiftCardsAppliedByCustomer(Customer customer, int storeId); /// /// Generate new gift card code 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 c5122d2beb..3f5ee14bb5 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderTotalCalculationService.cs @@ -6,9 +6,6 @@ namespace SmartStore.Services.Orders { - /// - /// Order service interface - /// public partial interface IOrderTotalCalculationService { /// @@ -70,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 @@ -106,16 +103,6 @@ decimal AdjustShippingRate(decimal shippingRate, IListShipping total decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax); - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Applied tax rate - /// Shipping total - decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate); - /// /// Gets shopping cart shipping total /// @@ -127,14 +114,14 @@ decimal AdjustShippingRate(decimal shippingRate, IList cart, bool includingTax, out decimal taxRate, out Discount appliedDiscount); - /// - /// Gets a shipping discount - /// - /// Customer - /// Shipping total - /// Applied discount - /// Shipping discount - decimal GetShippingDiscount(Customer customer, decimal shippingTotal, out Discount appliedDiscount); + /// + /// Gets a shipping discount + /// + /// Customer + /// Shipping total + /// Applied discount + /// Shipping discount + decimal GetShippingDiscount(Customer customer, decimal shippingTotal, out Discount appliedDiscount); @@ -162,33 +149,20 @@ decimal GetTaxTotal(IList cart, out SortedDictionary< - /// - /// Gets shopping cart total + /// Gets the shopping cart total /// - /// Cart - /// A value indicating whether we should ignore reward points (if enabled and a customer is going to use them) + /// 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 use payment method additional fee when calculating order total - decimal? GetShoppingCartTotal(IList cart, bool ignoreRewardPonts = false, - bool usePaymentMethodAdditionalFee = true); + /// 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 ignoreRewardPoints = false, + bool usePaymentMethodAdditionalFee = true, + bool ignoreCreditBalance = false); - /// - /// Gets shopping cart total - /// - /// Cart - /// Applied gift cards - /// Applied discount amount - /// Applied discount - /// Reward points to redeem - /// Reward points amount in primary store currency to redeem - /// 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 - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - decimal? GetShoppingCartTotal(IList cart, - out decimal discountAmount, out Discount appliedDiscount, - out List appliedGiftCards, - out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, - bool ignoreRewardPonts = false, bool usePaymentMethodAdditionalFee = true); /// /// Gets an order discount (applied to order total) diff --git a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs index d8479eb218..64b10fc2cb 100644 --- a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs @@ -11,25 +11,50 @@ namespace SmartStore.Services.Orders /// public partial interface IShoppingCartService { - /// - /// Delete shopping cart item - /// - /// Shopping cart item - /// A value indicating whether to reset checkout data - /// A value indicating whether to ensure that only active checkout attributes are attached to the current customer + /// + /// Gets the shopping cart items count + /// + /// Customer. Cannot be null. + /// Type of cart to get items count for + /// Store id + /// Sum of all item quantities + int CountItems(Customer customer, ShoppingCartType cartType, int? storeId = null); + + /// + /// Gets the shopping cart items + /// + /// Customer. Cannot be null. + /// Type of cart to get items for + /// Store id + /// All cart items + List GetCartItems(Customer customer, ShoppingCartType cartType, int? storeId = null); + + /// + /// Delete shopping cart item + /// + /// Shopping cart item + /// A value indicating whether to reset checkout data + /// A value indicating whether to ensure that only active checkout attributes are attached to the current customer /// A value indicating whether to delete child cart items - void DeleteShoppingCartItem(ShoppingCartItem shoppingCartItem, bool resetCheckoutData = true, - bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true); + void DeleteShoppingCartItem( + ShoppingCartItem shoppingCartItem, + bool resetCheckoutData = true, + bool ensureOnlyActiveCheckoutAttributes = false, + bool deleteChildCartItems = true); - void DeleteShoppingCartItem(int shoppingCartItemId, bool resetCheckoutData = true, - bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true); + void DeleteShoppingCartItem( + int shoppingCartItemId, + bool resetCheckoutData = true, + bool ensureOnlyActiveCheckoutAttributes = false, + bool deleteChildCartItems = true); /// /// 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) @@ -40,9 +65,12 @@ void DeleteShoppingCartItem(int shoppingCartItemId, bool resetCheckoutData = tru /// Store identifier /// Automatically add required products if enabled /// Warnings - IList GetRequiredProductWarnings(Customer customer, - ShoppingCartType shoppingCartType, Product product, - int storeId, bool automaticallyAddRequiredProductsIfEnabled); + IList GetRequiredProductWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + int storeId, + bool automaticallyAddRequiredProductsIfEnabled); /// /// Validates a product for standard properties @@ -54,9 +82,13 @@ IList GetRequiredProductWarnings(Customer customer, /// Customer entered price /// Quantity /// Warnings - IList GetStandardWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, string selectedAttributes, - decimal customerEnteredPrice, int quantity); + IList GetStandardWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + string selectedAttributes, + decimal customerEnteredPrice, + int quantity); /// /// Validates shopping cart item attributes @@ -85,8 +117,7 @@ IList GetShoppingCartItemAttributeWarnings( /// Product /// Selected attributes /// Warnings - IList GetShoppingCartItemGiftCardWarnings(ShoppingCartType shoppingCartType, - Product product, string selectedAttributes); + IList GetShoppingCartItemGiftCardWarnings(ShoppingCartType shoppingCartType, Product product, string selectedAttributes); /// /// Validates bundle items @@ -116,13 +147,22 @@ IList GetShoppingCartItemGiftCardWarnings(ShoppingCartType shoppingCartT /// Product bundle item if bundles should be validated /// Child cart items to validate bundle items /// Warnings - IList GetShoppingCartItemWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, int storeId, - string selectedAttributes, decimal customerEnteredPrice, - int quantity, bool automaticallyAddRequiredProductsIfEnabled, - bool getStandardWarnings = true, bool getAttributesWarnings = true, - bool getGiftCardWarnings = true, bool getRequiredProductWarnings = true, - bool getBundleWarnings = true, ProductBundleItem bundleItem = null, IList childItems = null); + IList GetShoppingCartItemWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + int storeId, + string selectedAttributes, + decimal customerEnteredPrice, + int quantity, + bool automaticallyAddRequiredProductsIfEnabled, + bool getStandardWarnings = true, + bool getAttributesWarnings = true, + bool getGiftCardWarnings = true, + bool getRequiredProductWarnings = true, + bool getBundleWarnings = true, + ProductBundleItem bundleItem = null, + IList childItems = null); /// /// Validates whether this shopping cart is valid @@ -131,8 +171,7 @@ IList GetShoppingCartItemWarnings(Customer customer, ShoppingCartType sh /// Checkout attributes /// A value indicating whether to validate checkout attributes /// Warnings - IList GetShoppingCartWarnings(IList shoppingCart, - string checkoutAttributes, bool validateCheckoutAttributes); + IList GetShoppingCartWarnings(IList shoppingCart, string checkoutAttributes, bool validateCheckoutAttributes); /// /// Finds a shopping cart item in the cart @@ -143,7 +182,8 @@ IList GetShoppingCartWarnings(IList shoppingC /// Selected attributes /// Price entered by a customer /// Found shopping cart item - OrganizedShoppingCartItem FindShoppingCartItemInTheCart(IList shoppingCart, + OrganizedShoppingCartItem FindShoppingCartItemInTheCart( + IList shoppingCart, ShoppingCartType shoppingCartType, Product product, string selectedAttributes = "", @@ -162,8 +202,16 @@ OrganizedShoppingCartItem FindShoppingCartItemInTheCart(IListWhether to add required products /// Add to cart context /// List with warnings - List AddToCart(Customer customer, Product product, ShoppingCartType cartType, int storeId, string selectedAttributes, - decimal customerEnteredPrice, int quantity, bool automaticallyAddRequiredProductsIfEnabled, AddToCartContext ctx = null); + List AddToCart( + Customer customer, + Product product, + ShoppingCartType cartType, + int storeId, + string selectedAttributes, + decimal customerEnteredPrice, + int quantity, + bool automaticallyAddRequiredProductsIfEnabled, + AddToCartContext ctx = null); /// /// Add product to cart @@ -193,8 +241,7 @@ List AddToCart(Customer customer, Product product, ShoppingCartType cart /// New shopping cart item quantity /// A value indicating whether to reset checkout data /// Warnings - IList UpdateShoppingCartItem(Customer customer, int shoppingCartItemId, - int newQuantity, bool resetCheckoutData); + IList UpdateShoppingCartItem(Customer customer, int shoppingCartItemId, int newQuantity, bool resetCheckoutData); /// /// Migrate shopping cart @@ -213,5 +260,29 @@ IList UpdateShoppingCartItem(Customer customer, int shoppingCartItemId, /// Add required products if enabled /// List with add-to-cart warnings. IList Copy(OrganizedShoppingCartItem sci, Customer customer, ShoppingCartType cartType, int storeId, bool addRequiredProductsIfEnabled); + + /// + /// Gets the subtotal of cart items for the current user + /// + /// unformatted subtotal of cart items for the current user + decimal GetCurrentCartSubTotal(); + + /// + /// Gets the subtotal of cart items for the current user + /// + /// unformatted subtotal of cart items for the current user + decimal GetCurrentCartSubTotal(IList cart); + + /// + /// Gets the formatted subtotal of cart items for the current user + /// + /// Formatted subtotal of cart items for the current user + string GetFormattedCurrentCartSubTotal(); + + /// + /// Gets the formatted subtotal of cart items for the current user + /// + /// Formatted subtotal of cart items for the current user + string GetFormattedCurrentCartSubTotal(IList cart); } } diff --git a/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs b/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs index 23b6b24504..5a5803d728 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs @@ -1,14 +1,33 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Html; +using SmartStore.Services.Directory; +using SmartStore.Services.Payments; namespace SmartStore.Services.Orders { - public static class OrderExtensions + public static class OrderExtensions { + private static int AggregateQuantity(IEnumerable shipments, OrderItem orderItem) + { + var result = 0; + + foreach (var shipment in shipments) + { + var item = shipment.ShipmentItems.FirstOrDefault(x => x.OrderItemId == orderItem.Id); + if (item != null) + { + result += item.Quantity; + } + } + + return result; + } + /// /// Formats the order note text /// @@ -16,253 +35,215 @@ public static class OrderExtensions /// Formatted text public static string FormatOrderNoteText(this OrderNote orderNote) { - if (orderNote == null) - throw new ArgumentNullException("orderNote"); - - string text = orderNote.Note; + Guard.NotNull(orderNote, nameof(orderNote)); - if (String.IsNullOrEmpty(text)) + if (orderNote.Note.IsEmpty()) return string.Empty; - text = SmartStore.Core.Html.HtmlUtils.FormatText(text, false, true, true, false, false, false); - - return text; + return HtmlUtils.ConvertPlainTextToHtml(orderNote.Note); } - /// - /// Gets a total number of items in all shipments - /// - /// Order item - /// Total number of items in all shipmentss - public static int GetTotalNumberOfItemsInAllShipment(this OrderItem orderItem) - { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); - - var totalInShipments = 0; - var shipments = orderItem.Order.Shipments.ToList(); - for (int i = 0; i < shipments.Count; i++) - { - var shipment = shipments[i]; - var si = shipment.ShipmentItems - .Where(x => x.OrderItemId == orderItem.Id) - .FirstOrDefault(); - if (si != null) - { - totalInShipments += si.Quantity; - } - } - return totalInShipments; - } - - /// - /// Gets a total number of already items which can be added to new shipments - /// - /// Order item - /// Total number of already delivered items which can be added to new shipments - public static int GetTotalNumberOfItemsCanBeAddedToShipment(this OrderItem orderItem) - { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); + public static List GetBundleData(this OrderItem orderItem) + { + if (orderItem != null && orderItem.BundleData.HasValue()) + { + var data = orderItem.BundleData.Convert>(); + return data; + } + return new List(); + } - var totalInShipments = orderItem.GetTotalNumberOfItemsInAllShipment(); + public static void SetBundleData(this OrderItem orderItem, List bundleData) + { + string rawData = null; - var qtyOrdered = orderItem.Quantity; - var qtyCanBeAddedToShipmentTotal = qtyOrdered - totalInShipments; - if (qtyCanBeAddedToShipmentTotal < 0) - qtyCanBeAddedToShipmentTotal = 0; + if (bundleData != null && bundleData.Count > 0) + rawData = bundleData.Convert(); - return qtyCanBeAddedToShipmentTotal; - } + orderItem.BundleData = rawData; + } /// - /// Gets a total number of not yet shipped items (but added to shipments) + /// Get the order total in the currency of the customer /// - /// Order item - /// Total number of not yet shipped items (but added to shipments) - public static int GetTotalNumberOfNotYetShippedItems(this OrderItem orderItem) + /// Order + /// Currency service + /// Payment service + /// Rounding amount + /// Order total + public static decimal GetOrderTotalInCustomerCurrency( + this Order order, + ICurrencyService currencyService, + IPaymentService paymentService, + out decimal roundingAmount) { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); + Guard.NotNull(order, nameof(order)); - var result = 0; - var shipments = orderItem.Order.Shipments.ToList(); - for (int i = 0; i < shipments.Count; i++) + roundingAmount = order.OrderTotalRounding; + var orderTotal = currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); + + // Avoid rounding a rounded value. It would zero roundingAmount. + if (orderTotal != order.OrderTotal) { - var shipment = shipments[i]; - if (shipment.ShippedDateUtc.HasValue) - //already shipped - continue; - - var si = shipment.ShipmentItems - .Where(x => x.OrderItemId == orderItem.Id) - .FirstOrDefault(); - if (si != null) + var currency = currencyService.GetCurrencyByCode(order.CustomerCurrencyCode); + + if (currency != null && currency.RoundOrderTotalEnabled && order.PaymentMethodSystemName.HasValue()) { - result += si.Quantity; + var pm = paymentService.GetPaymentMethodBySystemName(order.PaymentMethodSystemName); + if (pm != null && pm.RoundOrderTotalEnabled) + { + orderTotal = orderTotal.RoundToNearest(currency, out roundingAmount); + } } } - return result; + return orderTotal; } - /// - /// Gets a total number of already shipped items - /// - /// Order item - /// Total number of already shipped items - public static int GetTotalNumberOfShippedItems(this OrderItem orderItem) - { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); - - var result = 0; - var shipments = orderItem.Order.Shipments.ToList(); - for (int i = 0; i < shipments.Count; i++) - { - var shipment = shipments[i]; - if (!shipment.ShippedDateUtc.HasValue) - //not shipped yet - continue; - - var si = shipment.ShipmentItems - .Where(x => x.OrderItemId == orderItem.Id) - .FirstOrDefault(); - if (si != null) - { - result += si.Quantity; - } - } - - return result; - } /// - /// Gets a total number of already delivered items + /// Gets a value indicating whether an order has items to dispatch /// - /// Order item - /// Total number of already delivered items - public static int GetTotalNumberOfDeliveredItems(this OrderItem orderItem) - { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); + /// Order + /// A value indicating whether an order has items to dispatch + public static bool HasItemsToDispatch(this Order order) + { + Guard.NotNull(order, nameof(order)); - var result = 0; - var shipments = orderItem.Order.Shipments.ToList(); - for (int i = 0; i < shipments.Count; i++) - { - var shipment = shipments[i]; - if (!shipment.DeliveryDateUtc.HasValue) - //not delivered yet - continue; - - var si = shipment.ShipmentItems - .Where(x => x.OrderItemId == orderItem.Id) - .FirstOrDefault(); - if (si != null) - { - result += si.Quantity; - } - } + foreach (var orderItem in order.OrderItems.Where(x => x.Product.IsShipEnabled)) + { + var notDispatchedItems = orderItem.GetNotDispatchedItemsCount(); + if (notDispatchedItems <= 0) + continue; - return result; - } + // yes, we have at least one item to ship + return true; + } + return false; + } - public static List GetBundleData(this OrderItem orderItem) + /// + /// Gets a value indicating whether an order has items to deliver + /// + /// Order + /// A value indicating whether an order has items to deliver + public static bool HasItemsToDeliver(this Order order) { - if (orderItem != null && orderItem.BundleData.HasValue()) + Guard.NotNull(order, nameof(order)); + + foreach (var orderItem in order.OrderItems.Where(x => x.Product.IsShipEnabled)) { - var data = orderItem.BundleData.Convert>(); - return data; + var dispatchedItems = orderItem.GetDispatchedItemsCount(); + var deliveredItems = orderItem.GetDeliveredItemsCount(); + + if (dispatchedItems <= deliveredItems) + continue; + + // yes, we have at least one item to deliver + return true; } - return new List(); + return false; } - public static void SetBundleData(this OrderItem orderItem, List bundleData) + /// + /// Gets a value indicating whether an order has items to be added to a shipment + /// + /// Order + /// A value indicating whether an order has items to be added to a shipment + public static bool CanAddItemsToShipment(this Order order) { - string rawData = null; + Guard.NotNull(order, nameof(order)); - if (bundleData != null && bundleData.Count > 0) - rawData = bundleData.Convert(); + foreach (var orderItem in order.OrderItems.Where(x => x.Product.IsShipEnabled)) + { + var canBeAddedToShipment = orderItem.GetItemsCanBeAddedToShipmentCount(); + if (canBeAddedToShipment <= 0) + continue; - orderItem.BundleData = rawData; + // yes, we have at least one item to create a new shipment + return true; + } + return false; } - /// - /// Gets a value indicating whether an order has items to be added to a shipment - /// - /// Order - /// A value indicating whether an order has items to be added to a shipment - public static bool HasItemsToAddToShipment(this Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - foreach (var orderItem in order.OrderItems) - { - //we can ship only shippable products - if (!orderItem.Product.IsShipEnabled) - continue; + /// + /// Gets the total number of items which can be added to new shipments + /// + /// Order item + /// Total number of items which can be added to new shipments + public static int GetItemsCanBeAddedToShipmentCount(this OrderItem orderItem) + { + Guard.NotNull(orderItem, nameof(orderItem)); - var totalNumberOfItemsCanBeAddedToShipment = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); - if (totalNumberOfItemsCanBeAddedToShipment <= 0) - continue; + var itemsCount = orderItem.GetShipmentItemsCount(); - //yes, we have at least one item to create a new shipment - return true; - } - return false; - } - /// - /// Gets a value indicating whether an order has items to ship - /// - /// Order - /// A value indicating whether an order has items to ship - public static bool HasItemsToShip(this Order order) + return Math.Max(orderItem.Quantity - itemsCount, 0); + } + + /// + /// Gets the total number of items in all shipments + /// + /// Order item + /// Total number of items in all shipmentss + public static int GetShipmentItemsCount(this OrderItem orderItem) { - if (order == null) - throw new ArgumentNullException("order"); + Guard.NotNull(orderItem, nameof(orderItem)); - foreach (var orderItem in order.OrderItems) - { - //we can ship only shippable products - if (!orderItem.Product.IsShipEnabled) - continue; + var shipments = orderItem.Order.Shipments.ToList(); + return AggregateQuantity(shipments, orderItem); + } - var totalNumberOfNotYetShippedItems = orderItem.GetTotalNumberOfNotYetShippedItems(); - if (totalNumberOfNotYetShippedItems <= 0) - continue; + /// + /// Gets the total number of dispatched items + /// + /// Order item + /// Total number of dispatched items + public static int GetDispatchedItemsCount(this OrderItem orderItem) + { + Guard.NotNull(orderItem, nameof(orderItem)); - //yes, we have at least one item to ship - return true; - } - return false; - } - /// - /// Gets a value indicating whether an order has items to deliver - /// - /// Order - /// A value indicating whether an order has items to deliver - public static bool HasItemsToDeliver(this Order order) + var shipments = orderItem.Order.Shipments.ToList(); + return AggregateQuantity(shipments.Where(x => x.ShippedDateUtc.HasValue), orderItem); + } + + /// + /// Gets the total number of not dispatched items + /// + /// Order item + /// Total number of not dispatched items + public static int GetNotDispatchedItemsCount(this OrderItem orderItem) { - if (order == null) - throw new ArgumentNullException("order"); + Guard.NotNull(orderItem, nameof(orderItem)); - foreach (var orderItem in order.OrderItems) - { - //we can ship only shippable products - if (!orderItem.Product.IsShipEnabled) - continue; + var shipments = orderItem.Order.Shipments.ToList(); + return AggregateQuantity(shipments.Where(x => !x.ShippedDateUtc.HasValue), orderItem); + } + + /// + /// Gets the total number of already delivered items + /// + /// Order item + /// Total number of already delivered items + public static int GetDeliveredItemsCount(this OrderItem orderItem) + { + Guard.NotNull(orderItem, nameof(orderItem)); - var totalNumberOfShippedItems = orderItem.GetTotalNumberOfShippedItems(); - var totalNumberOfDeliveredItems = orderItem.GetTotalNumberOfDeliveredItems(); - if (totalNumberOfShippedItems <= totalNumberOfDeliveredItems) - continue; + var shipments = orderItem.Order.Shipments.ToList(); + return AggregateQuantity(shipments.Where(x => x.DeliveryDateUtc.HasValue), orderItem); + } - //yes, we have at least one item to deliver - return true; - } - return false; - } - } + /// + /// Gets the total number of not delivered items + /// + /// Order item + /// Total number of already delivered items + public static int GetNotDeliveredItemsCount(this OrderItem orderItem) + { + Guard.NotNull(orderItem, nameof(orderItem)); + + var shipments = orderItem.Order.Shipments.ToList(); + return AggregateQuantity(shipments.Where(x => !x.DeliveryDateUtc.HasValue), orderItem); + } + } } diff --git a/src/Libraries/SmartStore.Services/Orders/OrderMessageFactoryExtensions.cs b/src/Libraries/SmartStore.Services/Orders/OrderMessageFactoryExtensions.cs new file mode 100644 index 0000000000..50bb45426e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Orders/OrderMessageFactoryExtensions.cs @@ -0,0 +1,126 @@ +using System; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Services.Messages; + +namespace SmartStore.Services.Orders +{ + public static class OrderMessageFactoryExtensions + { + /// + /// Sends an order placed notification to a store owner + /// + public static CreateMessageResult SendOrderPlacedStoreOwnerNotification(this IMessageFactory factory, Order order, int languageId = 0) + { + Guard.NotNull(order, nameof(order)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.OrderPlacedStoreOwner, languageId, order.StoreId), true, order, order.Customer); + } + + /// + /// Sends an order placed notification to a customer + /// + public static CreateMessageResult SendOrderPlacedCustomerNotification(this IMessageFactory factory, Order order, int languageId = 0) + { + Guard.NotNull(order, nameof(order)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.OrderPlacedCustomer, languageId, order.StoreId, order.Customer), true, order); + } + + /// + /// Sends a shipment sent notification to a customer + /// + public static CreateMessageResult SendShipmentSentCustomerNotification(this IMessageFactory factory, Shipment shipment, int languageId = 0) + { + Guard.NotNull(shipment, nameof(shipment)); + Guard.NotNull(shipment.Order, nameof(shipment.Order)); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ShipmentSentCustomer, languageId, shipment.Order.StoreId, shipment.Order.Customer), true, shipment, shipment.Order); + } + + /// + /// Sends a shipment delivered notification to a customer + /// + public static CreateMessageResult SendShipmentDeliveredCustomerNotification(this IMessageFactory factory, Shipment shipment, int languageId = 0) + { + Guard.NotNull(shipment, nameof(shipment)); + Guard.NotNull(shipment.Order, nameof(shipment.Order)); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ShipmentDeliveredCustomer, languageId, shipment.Order.StoreId, shipment.Order.Customer), true, shipment, shipment.Order); + } + + /// + /// Sends an order completed notification to a customer + /// + public static CreateMessageResult SendOrderCompletedCustomerNotification(this IMessageFactory factory, Order order, int languageId = 0) + { + Guard.NotNull(order, nameof(order)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.OrderCompletedCustomer, languageId, order.StoreId, order.Customer), true, order); + } + + /// + /// Sends an order cancelled notification to a customer + /// + public static CreateMessageResult SendOrderCancelledCustomerNotification(this IMessageFactory factory, Order order, int languageId = 0) + { + Guard.NotNull(order, nameof(order)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.OrderCancelledCustomer, languageId, order.StoreId, order.Customer), true, order); + } + + /// + /// Sends a new order note added notification to a customer + /// + public static CreateMessageResult SendNewOrderNoteAddedCustomerNotification(this IMessageFactory factory, OrderNote orderNote, int languageId = 0) + { + Guard.NotNull(orderNote, nameof(orderNote)); + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.OrderNoteAddedCustomer, languageId, orderNote.Order?.StoreId, orderNote.Order?.Customer), true, orderNote, orderNote.Order); + } + + /// + /// Sends a "Recurring payment cancelled" notification to a store owner + /// + public static CreateMessageResult SendRecurringPaymentCancelledStoreOwnerNotification(this IMessageFactory factory, RecurringPayment recurringPayment, int languageId = 0) + { + Guard.NotNull(recurringPayment, nameof(recurringPayment)); + + var order = recurringPayment.InitialOrder; + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.RecurringPaymentCancelledStoreOwner, languageId, order?.StoreId, order?.Customer), true, + recurringPayment, order); + } + + /// + /// Sends 'New Return Request' message to a store owner + /// + public static CreateMessageResult SendNewReturnRequestStoreOwnerNotification(this IMessageFactory factory, ReturnRequest returnRequest, OrderItem orderItem, int languageId = 0) + { + Guard.NotNull(returnRequest, nameof(returnRequest)); + Guard.NotNull(orderItem, nameof(orderItem)); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.NewReturnRequestStoreOwner, languageId, orderItem.Order?.StoreId, returnRequest.Customer), true, + returnRequest, orderItem, orderItem.Order, orderItem.Product); + } + + /// + /// Sends 'Return Request status changed' message to a customer + /// + public static CreateMessageResult SendReturnRequestStatusChangedCustomerNotification(this IMessageFactory factory, ReturnRequest returnRequest, OrderItem orderItem, int languageId = 0) + { + Guard.NotNull(returnRequest, nameof(returnRequest)); + Guard.NotNull(orderItem, nameof(orderItem)); + + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.ReturnRequestStatusChangedCustomer, languageId, orderItem.Order?.StoreId, returnRequest.Customer), true, returnRequest); + } + + /// + /// Sends a gift card notification + /// + public static CreateMessageResult SendGiftCardNotification(this IMessageFactory factory, GiftCard giftCard, int languageId = 0) + { + Guard.NotNull(giftCard, nameof(giftCard)); + + var orderItem = giftCard.PurchasedWithOrderItem; + var customer = orderItem?.Order?.Customer; + var storeId = orderItem?.Order?.StoreId; + return factory.CreateMessage(MessageContext.Create(MessageTemplateNames.GiftCardCustomer, languageId, storeId, customer), true, giftCard, orderItem, orderItem?.Product); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index d92e6212a0..f09a198d95 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -9,10 +9,10 @@ using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Logging; 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; @@ -33,10 +33,7 @@ namespace SmartStore.Services.Orders { - /// - /// Order processing service - /// - public partial class OrderProcessingService : IOrderProcessingService + public partial class OrderProcessingService : IOrderProcessingService { #region Fields @@ -46,7 +43,6 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly ILanguageService _languageService; private readonly IProductService _productService; private readonly IPaymentService _paymentService; - private readonly ILogger _logger; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly IPriceCalculationService _priceCalculationService; private readonly IPriceFormatter _priceFormatter; @@ -63,7 +59,7 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly IEncryptionService _encryptionService; private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; - private readonly IWorkflowMessageService _workflowMessageService; + private readonly IMessageFactory _messageFactory; private readonly ICustomerActivityService _customerActivityService; private readonly ICurrencyService _currencyService; private readonly IAffiliateService _affiliateService; @@ -78,55 +74,19 @@ 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 - /// - /// Ctor - /// - /// Order service - /// Web helper - /// Localization service - /// Language service - /// Product service - /// Payment service - /// Logger - /// Order total calculationservice - /// Price calculation service - /// Price formatter - /// Product attribute parser - /// Product attribute formatter - /// Gift card service - /// Shopping cart service - /// Checkout attribute service - /// Shipping service - /// Shipment service - /// Tax service - /// Customer service - /// Discount service - /// Encryption service - /// Work context - /// Store context - /// Workflow message service - /// Customer activity service - /// Currency service - /// Affiliate service - /// Event published - /// Payment settings - /// Reward points settings - /// Order settings - /// Tax settings - /// Localization settings - /// Currency settings - public OrderProcessingService(IOrderService orderService, + public OrderProcessingService( + IOrderService orderService, IWebHelper webHelper, ILocalizationService localizationService, ILanguageService languageService, IProductService productService, IPaymentService paymentService, - ILogger logger, IOrderTotalCalculationService orderTotalCalculationService, IPriceCalculationService priceCalculationService, IPriceFormatter priceFormatter, @@ -143,7 +103,7 @@ public OrderProcessingService(IOrderService orderService, IEncryptionService encryptionService, IWorkContext workContext, IStoreContext storeContext, - IWorkflowMessageService workflowMessageService, + IMessageFactory messageFactory, ICustomerActivityService customerActivityService, ICurrencyService currencyService, IAffiliateService affiliateService, @@ -156,58 +116,70 @@ public OrderProcessingService(IOrderService orderService, TaxSettings taxSettings, LocalizationSettings localizationSettings, CurrencySettings currencySettings, - ShoppingCartSettings shoppingCartSettings) + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings) { - this._orderService = orderService; - this._webHelper = webHelper; - this._localizationService = localizationService; - this._languageService = languageService; - this._productService = productService; - this._paymentService = paymentService; - this._logger = logger; - this._orderTotalCalculationService = orderTotalCalculationService; - this._priceCalculationService = priceCalculationService; - this._priceFormatter = priceFormatter; - this._productAttributeParser = productAttributeParser; - this._productAttributeFormatter = productAttributeFormatter; - this._giftCardService = giftCardService; - this._shoppingCartService = shoppingCartService; - this._checkoutAttributeFormatter = checkoutAttributeFormatter; - this._workContext = workContext; - this._storeContext = storeContext; - this._workflowMessageService = workflowMessageService; - this._shippingService = shippingService; - this._shipmentService = shipmentService; - this._taxService = taxService; - this._customerService = customerService; - this._discountService = discountService; - this._encryptionService = encryptionService; - this._customerActivityService = customerActivityService; - this._currencyService = currencyService; - this._affiliateService = affiliateService; - this._eventPublisher = eventPublisher; - this._genericAttributeService = genericAttributeService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._paymentSettings = paymentSettings; - this._rewardPointsSettings = rewardPointsSettings; - this._orderSettings = orderSettings; - this._taxSettings = taxSettings; - this._localizationSettings = localizationSettings; - this._currencySettings = currencySettings; - this._shoppingCartSettings = shoppingCartSettings; + _orderService = orderService; + _webHelper = webHelper; + _localizationService = localizationService; + _languageService = languageService; + _productService = productService; + _paymentService = paymentService; + _orderTotalCalculationService = orderTotalCalculationService; + _priceCalculationService = priceCalculationService; + _priceFormatter = priceFormatter; + _productAttributeParser = productAttributeParser; + _productAttributeFormatter = productAttributeFormatter; + _giftCardService = giftCardService; + _shoppingCartService = shoppingCartService; + _checkoutAttributeFormatter = checkoutAttributeFormatter; + _workContext = workContext; + _storeContext = storeContext; + _messageFactory = messageFactory; + _shippingService = shippingService; + _shipmentService = shipmentService; + _taxService = taxService; + _customerService = customerService; + _discountService = discountService; + _encryptionService = encryptionService; + _customerActivityService = customerActivityService; + _currencyService = currencyService; + _affiliateService = affiliateService; + _eventPublisher = eventPublisher; + _genericAttributeService = genericAttributeService; + _newsLetterSubscriptionService = newsLetterSubscriptionService; + _paymentSettings = paymentSettings; + _rewardPointsSettings = rewardPointsSettings; + _orderSettings = orderSettings; + _taxSettings = taxSettings; + _localizationSettings = localizationSettings; + _currencySettings = currencySettings; + _shoppingCartSettings = shoppingCartSettings; + _catalogSettings = catalogSettings; T = NullLocalizer.Instance; + Logger = NullLogger.Instance; } public Localizer T { get; set; } + public ILogger Logger { get; set; } #endregion #region Utilities - private decimal Round(decimal value) + protected string FormatTaxRates(SortedDictionary taxRates) { - return (_shoppingCartSettings.RoundPricesDuringCalculation ? Math.Round(value, 2) : value); + var result = string.Empty; + + foreach (var rate in taxRates) + { + result += "{0}:{1}; ".FormatInvariant( + rate.Key.ToString(CultureInfo.InvariantCulture), + rate.Value.ToString(CultureInfo.InvariantCulture)); + } + + return result; } private void ProcessErrors(Order order, IList errors, string messageKey) @@ -217,7 +189,7 @@ private void ProcessErrors(Order order, IList errors, string messageKey) var msg = string.Concat(T(messageKey, order.GetOrderNumber()), " ", string.Join(" ", errors)); _orderService.AddOrderNote(order, msg); - _logger.InsertLog(LogLevel.Error, msg, msg); + Logger.Error(msg); } } @@ -242,12 +214,8 @@ protected void AwardRewardPoints(Order order, decimal? amount = null) if (order.RewardPointsWereAdded) return; - // Truncate increases the risk of inaccuracy of rounding - //int points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); - - // why are points awarded for OrderTotal? wouldn't be OrderSubtotalInclTax better? - - int points = (int)Math.Round((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); + // Truncate same as Floor for positive amounts + var points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); if (points == 0) return; @@ -319,19 +287,15 @@ protected void SetActivatedValueForPurchasedGiftCards(Order order, bool activate if (gc.GiftCardType == GiftCardType.Virtual) { - //send email for virtual gift card + // Send email for virtual gift card if (!String.IsNullOrEmpty(gc.RecipientEmail) && !String.IsNullOrEmpty(gc.SenderEmail)) { var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); if (customerLang == null) customerLang = _languageService.GetAllLanguages().FirstOrDefault(); - var queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); - - if (queuedEmailId > 0) - { - isRecipientNotified = true; - } + var qe = _messageFactory.SendGiftCardNotification(gc, customerLang.Id); + isRecipientNotified = qe?.Email.Id != null; } } @@ -374,20 +338,20 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) if (prevOrderStatus != OrderStatus.Complete && os == OrderStatus.Complete && notifyCustomer) { //notification - int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId); - if (orderCompletedCustomerNotificationQueuedEmailId > 0) + var msg = _messageFactory.SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId); + if (msg?.Email?.Id != null) { - _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCompletedEmailQueued", orderCompletedCustomerNotificationQueuedEmailId)); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCompletedEmailQueued", msg.Email.Id)); } } if (prevOrderStatus != OrderStatus.Cancelled && os == OrderStatus.Cancelled && notifyCustomer) { //notification - int orderCancelledCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); - if (orderCancelledCustomerNotificationQueuedEmailId > 0) + var msg = _messageFactory.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); + if (msg?.Email?.Id != null) { - _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCancelledEmailQueued", orderCancelledCustomerNotificationQueuedEmailId)); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCancelledEmailQueued", msg.Email.Id)); } } @@ -463,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) @@ -509,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)); @@ -519,7 +759,6 @@ public virtual PlaceOrderResult PlaceOrder( customerCurrencyCode = customerCurrency.CurrencyCode; var primaryStoreCurrency = _storeContext.CurrentStore.PrimaryStoreCurrency; - customerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; } else @@ -528,7 +767,7 @@ public virtual PlaceOrderResult PlaceOrder( customerCurrencyRate = initialOrder.CurrencyRate; } - // customer language + // Customer language. Language customerLanguage = null; if (!processPaymentRequest.IsRecurringPayment) { @@ -543,62 +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.ShoppingCartItems.Count > 0) - cart = processPaymentRequest.ShoppingCartItems; - 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) { @@ -609,7 +794,7 @@ public virtual PlaceOrderResult PlaceOrder( customerTaxDisplayType = initialOrder.CustomerTaxDisplayType; } - // checkout attributes + // Checkout attributes. string checkoutAttributeDescription, checkoutAttributesXml; if (!processPaymentRequest.IsRecurringPayment) { @@ -622,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; @@ -665,8 +840,8 @@ public virtual PlaceOrderResult PlaceOrder( } - // shipping info - bool shoppingCartRequiresShipping = false; + // Shipping info. + var shoppingCartRequiresShipping = false; if (!processPaymentRequest.IsRecurringPayment) { shoppingCartRequiresShipping = cart.RequiresShipping(); @@ -695,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 { @@ -716,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); } @@ -731,71 +905,57 @@ 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) { vatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); } - //tax rates - foreach (var kvp in taxRatesDictionary) - { - var taxRate = kvp.Key; - var taxValue = kvp.Value; - taxRates += string.Format("{0}:{1}; ", taxRate.ToString(CultureInfo.InvariantCulture), taxValue.ToString(CultureInfo.InvariantCulture)); - } + taxRates = FormatTaxRates(taxRatesDictionary); } else { orderTaxTotal = initialOrder.OrderTax; - //VAT number vatNumber = initialOrder.VatNumber; } + processPaymentRequest.OrderTax = orderTaxTotal; - //order total (and applied discounts, gift cards, reward points) - decimal? orderTotal = null; - decimal orderDiscountAmount = decimal.Zero; - List appliedGiftCards = null; - int redeemedRewardPoints = 0; - decimal redeemedRewardPointsAmount = decimal.Zero; + // Order total (and applied discounts, gift cards, reward points). + ShoppingCartTotal cartTotal = null; + if (!processPaymentRequest.IsRecurringPayment) { - Discount orderAppliedDiscount = null; - orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart, - out orderDiscountAmount, out orderAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); - - if (!orderTotal.HasValue) - throw new SmartException(T("Order.CannotCalculateOrderTotal")); + cartTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart); - //discount history - if (orderAppliedDiscount != null && !appliedDiscounts.Any(x => x.Id == orderAppliedDiscount.Id)) + // Discount history. + if (cartTotal.AppliedDiscount != null && !appliedDiscounts.Any(x => x.Id == cartTotal.AppliedDiscount.Id)) { - appliedDiscounts.Add(orderAppliedDiscount); + appliedDiscounts.Add(cartTotal.AppliedDiscount); } } else { - orderDiscountAmount = initialOrder.OrderDiscount; - orderTotal = initialOrder.OrderTotal; + cartTotal = new ShoppingCartTotal(initialOrder.OrderTotal); + cartTotal.DiscountAmount = initialOrder.OrderDiscount; } - processPaymentRequest.OrderTotal = orderTotal.Value; + + processPaymentRequest.OrderTotal = cartTotal.TotalAmount.Value; #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) @@ -805,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 (orderTotal.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; @@ -905,7 +1014,7 @@ public virtual PlaceOrderResult PlaceOrder( isRecurringShoppingCart = true; } - // process payment + // Process payment. ProcessPaymentResult processPaymentResult = null; if (!skipPaymentWorkflow && !processPaymentRequest.IsMultiOrder) { @@ -913,7 +1022,7 @@ public virtual PlaceOrderResult PlaceOrder( { if (isRecurringShoppingCart) { - // recurring cart + // Recurring cart. var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { @@ -929,7 +1038,7 @@ public virtual PlaceOrderResult PlaceOrder( } else { - // standard cart + // Standard cart. processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); } } @@ -937,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 @@ -960,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: @@ -975,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 @@ -1019,9 +1127,11 @@ public virtual PlaceOrderResult PlaceOrder( PaymentMethodAdditionalFeeTaxRate = paymentAdditionalFeeTaxRate, TaxRates = taxRates, OrderTax = orderTaxTotal, - OrderTotal = orderTotal.Value, + OrderTotalRounding = cartTotal.RoundingAmount, + OrderTotal = cartTotal.TotalAmount.Value, RefundedAmount = decimal.Zero, - OrderDiscount = orderDiscountAmount, + OrderDiscount = cartTotal.DiscountAmount, + CreditBalance = cartTotal.CreditBalance, CheckoutAttributeDescription = checkoutAttributeDescription, CheckoutAttributesXml = checkoutAttributesXml, CustomerCurrencyCode = customerCurrencyCode, @@ -1060,8 +1170,6 @@ public virtual PlaceOrderResult PlaceOrder( ShippingMethod = shippingMethodName, ShippingRateComputationMethodSystemName = shippingRateComputationMethodSystemName, VatNumber = vatNumber, - CreatedOnUtc = utcNow, - UpdatedOnUtc = utcNow, CustomerOrderComment = extraData.ContainsKey("CustomerComment") ? extraData["CustomerComment"] : "" }; @@ -1076,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); @@ -1091,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); @@ -1102,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); - //save order item + // Save order item. var orderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), @@ -1127,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(); @@ -1150,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++) { @@ -1182,17 +1298,19 @@ public virtual PlaceOrderResult PlaceOrder( _productService.AdjustInventory(sc, true); } - //clear shopping cart - if (!processPaymentRequest.IsMultiOrder) - cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci.Item, false)); + // Clear shopping cart. + if (!processPaymentRequest.IsMultiOrder) + { + cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci.Item, false)); + } } 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(), @@ -1213,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++) { @@ -1251,7 +1374,7 @@ public virtual PlaceOrderResult PlaceOrder( } } - //discount usage history + // Discount usage history. if (!processPaymentRequest.IsRecurringPayment) { foreach (var discount in appliedDiscounts) @@ -1266,10 +1389,10 @@ public virtual PlaceOrderResult PlaceOrder( } } - //gift card usage history - if (!processPaymentRequest.IsRecurringPayment && appliedGiftCards != null) + // Gift card usage history. + if (!processPaymentRequest.IsRecurringPayment && cartTotal.AppliedGiftCards != null) { - foreach (var agc in appliedGiftCards) + foreach (var agc in cartTotal.AppliedGiftCards) { var amountUsed = agc.AmountCanBeUsed; var gcuh = new GiftCardUsageHistory @@ -1284,21 +1407,21 @@ public virtual PlaceOrderResult PlaceOrder( } } - //reward points history - if (redeemedRewardPointsAmount > decimal.Zero) + // Reward points history. + if (cartTotal.RedeemedRewardPointsAmount > decimal.Zero) { - customer.AddRewardPointsHistoryEntry(-redeemedRewardPoints, + customer.AddRewardPointsHistoryEntry(-cartTotal.RedeemedRewardPoints, _localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId).FormatInvariant(order.GetOrderNumber()), order, - redeemedRewardPointsAmount); + cartTotal.RedeemedRewardPointsAmount); _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, @@ -1315,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: @@ -1346,41 +1465,41 @@ public virtual PlaceOrderResult PlaceOrder( #region Notifications, notes and attributes - // notes, messages + // Notes, messages. _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPlaced")); - - //send email notifications - int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); - if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) + + // Send email notifications. + var msg = _messageFactory.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); + if (msg?.Email?.Id != null) { - _orderService.AddOrderNote(order, T("Admin.OrderNotice.MerchantEmailQueued", orderPlacedStoreOwnerNotificationQueuedEmailId)); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.MerchantEmailQueued", msg.Email.Id)); } - int orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId); - if (orderPlacedCustomerNotificationQueuedEmailId > 0) + msg = _messageFactory.SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId); + if (msg?.Email?.Id != null) { - _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerEmailQueued", orderPlacedCustomerNotificationQueuedEmailId)); + _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); + _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) @@ -1388,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); @@ -1406,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")); } } @@ -1426,15 +1541,15 @@ public virtual PlaceOrderResult PlaceOrder( } } } - catch (Exception exc) + catch (Exception ex) { - _logger.Error(exc.Message, exc); - result.AddError(exc.Message); + Logger.Error(ex); + result.AddError(ex.Message); } - if (result.Errors.Count > 0) + if (result.Errors.Any()) { - _logger.Error(string.Join(" ", result.Errors)); + Logger.Error(string.Join(" ", result.Errors)); } return result; @@ -1446,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); } @@ -1537,7 +1650,7 @@ public virtual void ProcessNextRecurringPayment(RecurringPayment recurringPaymen } catch (Exception exception) { - _logger.ErrorsAll(exception); + Logger.ErrorsAll(exception); throw; } } @@ -1573,7 +1686,7 @@ public virtual IList CancelRecurringPayment(RecurringPayment recurringPa _orderService.AddOrderNote(initialOrder, T("Admin.OrderNotice.RecurringPaymentCancelled")); //notify a store owner - _workflowMessageService.SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, _localizationSettings.DefaultAdminLanguageId); + _messageFactory.SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, _localizationSettings.DefaultAdminLanguageId); } } catch (Exception exception) @@ -1651,7 +1764,7 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) _shipmentService.UpdateShipment(shipment); //check whether we have more items to ship - if (order.HasItemsToAddToShipment() || order.HasItemsToShip()) + if (order.CanAddItemsToShipment() || order.HasItemsToDispatch()) order.ShippingStatusId = (int)ShippingStatus.PartiallyShipped; else order.ShippingStatusId = (int)ShippingStatus.Shipped; @@ -1663,10 +1776,10 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) if (notifyCustomer) { //notify customer - int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); - if (queuedEmailId > 0) + var msg = _messageFactory.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); + if (msg?.Email?.Id != null) { - _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerShippedEmailQueued", queuedEmailId)); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerShippedEmailQueued", msg.Email.Id)); } } @@ -1694,7 +1807,7 @@ public virtual void Deliver(Shipment shipment, bool notifyCustomer) shipment.DeliveryDateUtc = DateTime.UtcNow; _shipmentService.UpdateShipment(shipment); - if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) + if (!order.CanAddItemsToShipment() && !order.HasItemsToDispatch() && !order.HasItemsToDeliver()) { order.ShippingStatusId = (int)ShippingStatus.Delivered; } @@ -1706,10 +1819,10 @@ public virtual void Deliver(Shipment shipment, bool notifyCustomer) if (notifyCustomer) { //send email notification - int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); - if (queuedEmailId > 0) + var msg = _messageFactory.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); + if (msg?.Email?.Id != null) { - _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerDeliveredEmailQueued", queuedEmailId)); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerDeliveredEmailQueued", msg.Email.Id)); } } @@ -1742,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); @@ -1780,21 +1893,23 @@ public virtual void AutoUpdateOrderDetails(AutoUpdateOrderItemContext context) if (context.UpdateTotals && oi.Order.OrderStatusId <= (int)OrderStatus.Pending) { - decimal priceInclTax = Round(context.QuantityNew * oi.UnitPriceInclTax); - decimal priceExclTax = Round(context.QuantityNew * oi.UnitPriceExclTax); + var currency = _currencyService.GetCurrencyByCode(oi.Order.CustomerCurrencyCode); + + decimal priceInclTax = (context.QuantityNew * oi.UnitPriceInclTax).RoundIfEnabledFor(currency); + decimal priceExclTax = (context.QuantityNew * oi.UnitPriceExclTax).RoundIfEnabledFor(currency); decimal deltaPriceInclTax = priceInclTax - (context.IsNewOrderItem ? decimal.Zero : oi.PriceInclTax); decimal deltaPriceExclTax = priceExclTax - (context.IsNewOrderItem ? decimal.Zero : oi.PriceExclTax); oi.Quantity = context.QuantityNew; - oi.PriceInclTax = Round(priceInclTax); - oi.PriceExclTax = Round(priceExclTax); + oi.PriceInclTax = priceInclTax.RoundIfEnabledFor(currency); + oi.PriceExclTax = priceExclTax.RoundIfEnabledFor(currency); decimal subtotalInclTax = oi.Order.OrderSubtotalInclTax + deltaPriceInclTax; decimal subtotalExclTax = oi.Order.OrderSubtotalExclTax + deltaPriceExclTax; - oi.Order.OrderSubtotalInclTax = Round(subtotalInclTax); - oi.Order.OrderSubtotalExclTax = Round(subtotalExclTax); + oi.Order.OrderSubtotalInclTax = subtotalInclTax.RoundIfEnabledFor(currency); + oi.Order.OrderSubtotalExclTax = subtotalExclTax.RoundIfEnabledFor(currency); decimal discountInclTax = oi.DiscountAmountInclTax * context.QuantityChangeFactor; decimal discountExclTax = oi.DiscountAmountExclTax * context.QuantityChangeFactor; @@ -1802,14 +1917,27 @@ public virtual void AutoUpdateOrderDetails(AutoUpdateOrderItemContext context) decimal deltaDiscountInclTax = discountInclTax - oi.DiscountAmountInclTax; decimal deltaDiscountExclTax = discountExclTax - oi.DiscountAmountExclTax; - oi.DiscountAmountInclTax = Round(discountInclTax); - oi.DiscountAmountExclTax = Round(discountExclTax); + oi.DiscountAmountInclTax = discountInclTax.RoundIfEnabledFor(currency); + oi.DiscountAmountExclTax = discountExclTax.RoundIfEnabledFor(currency); decimal total = Math.Max(oi.Order.OrderTotal + deltaPriceInclTax, 0); decimal tax = Math.Max(oi.Order.OrderTax + (deltaPriceInclTax - deltaPriceExclTax), 0); - oi.Order.OrderTotal = Round(total); - oi.Order.OrderTax = Round(tax); + oi.Order.OrderTotal = total.RoundIfEnabledFor(currency); + oi.Order.OrderTax = tax.RoundIfEnabledFor(currency); + + // Update tax rate value. + var deltaTax = deltaPriceInclTax - deltaPriceExclTax; + if (deltaTax != decimal.Zero) + { + var taxRates = oi.Order.TaxRatesDictionary; + + taxRates[oi.TaxRate] = taxRates.ContainsKey(oi.TaxRate) + ? Math.Max(taxRates[oi.TaxRate] + deltaTax, 0) + : Math.Max(deltaTax, 0); + + oi.Order.TaxRates = FormatTaxRates(taxRates); + } _orderService.UpdateOrder(oi.Order); } @@ -2052,12 +2180,15 @@ public virtual bool CanRefund(Order order) if (order.OrderTotal == decimal.Zero) return false; - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; + // Only partial refunds allowed if already refunded. + if (order.RefundedAmount > decimal.Zero) + return false; - if (order.PaymentStatus == PaymentStatus.Paid && - _paymentService.SupportRefund(order.PaymentMethodSystemName)) + // Uncomment the lines below in order to allow this operation for cancelled orders. + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Paid && _paymentService.SupportRefund(order.PaymentMethodSystemName)) return true; return false; @@ -2088,10 +2219,10 @@ public virtual IList Refund(Order order) if (result.Success) { - //total amount refunded + // Total amount refunded. decimal totalAmountRefunded = order.RefundedAmount + request.AmountToRefund; - //update order info + // Update order info. order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = result.NewPaymentStatus; @@ -2099,7 +2230,6 @@ public virtual IList Refund(Order order) _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderRefunded", _priceFormatter.FormatPrice(request.AmountToRefund, true, false))); - //check order status CheckOrderStatus(order); } @@ -2132,11 +2262,15 @@ public virtual bool CanRefundOffline(Order order) if (order.OrderTotal == decimal.Zero) return false; - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; + // Only partial refunds allowed if already refunded. + if (order.RefundedAmount > decimal.Zero) + return false; - if (order.PaymentStatus == PaymentStatus.Paid) + // Uncomment the lines below in order to allow this operation for cancelled orders. + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Paid) return true; return false; @@ -2154,13 +2288,13 @@ public virtual void RefundOffline(Order order) if (!CanRefundOffline(order)) throw new SmartException(T("Order.CannotRefund")); - //amout to refund + // Amout to refund. decimal amountToRefund = order.OrderTotal; - //total amount refunded + // Total amount refunded. decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - //update order info + // Update order info. order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = PaymentStatus.Refunded; @@ -2168,7 +2302,6 @@ public virtual void RefundOffline(Order order) _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); - //check order status CheckOrderStatus(order); } @@ -2506,6 +2639,7 @@ public virtual bool IsReturnRequestAllowed(Order order) var daysPassed = (DateTime.UtcNow - order.CreatedOnUtc).TotalDays; numberOfDaysReturnRequestAvailableValid = (daysPassed - _orderSettings.NumberOfDaysReturnRequestAvailable) < 0; } + return numberOfDaysReturnRequestAvailableValid; } @@ -2561,7 +2695,7 @@ public virtual bool ValidateMinOrderTotalAmount(IList public virtual Shipment AddShipment(Order order, string trackingNumber, Dictionary quantities) { - Guard.ArgumentNotNull(() => order); + Guard.NotNull(order, nameof(order)); Shipment shipment = null; decimal? totalWeight = null; @@ -2572,7 +2706,7 @@ public virtual Shipment AddShipment(Order order, string trackingNumber, Dictiona continue; //ensure that this product can be shipped (have at least one item to ship) - var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); + var maxQtyToAdd = orderItem.GetItemsCanBeAddedToShipmentCount(); if (maxQtyToAdd <= 0) continue; 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 7ed45bb61a..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) @@ -96,16 +61,10 @@ public virtual IList GetOrdersByIds(int[] orderIds) where orderIds.Contains(o.Id) select o; var orders = query.ToList(); - //sort by passed identifiers - var sortedOrders = new List(); - foreach (int id in orderIds) - { - var order = orders.Find(x => x.Id == id); - if (order != null) - sortedOrders.Add(order); - } - return sortedOrders; - } + + // sort by passed identifier sequence + return orders.OrderBySequence(orderIds).ToList(); + } public virtual Order GetOrderByNumber(string orderNumber) { @@ -128,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) @@ -145,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()) @@ -164,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()) @@ -193,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; @@ -232,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())); @@ -273,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; @@ -296,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 @@ -320,43 +230,24 @@ orderby o.CreatedOnUtc descending return orders; } - /// - /// Inserts an order - /// - /// Order public virtual void InsertOrder(Order order) { if (order == null) throw new ArgumentNullException("order"); _orderRepository.Insert(order); - - //event notification - _eventPublisher.EntityInserted(order); } - /// - /// Updates the order - /// - /// The order public virtual void UpdateOrder(Order order) { if (order == null) throw new ArgumentNullException("order"); - order.UpdatedOnUtc = DateTime.UtcNow; - _orderRepository.Update(order); - //event notifications - _eventPublisher.EntityUpdated(order); _eventPublisher.PublishOrderUpdated(order); } - /// - /// Deletes an order - /// - /// The order public virtual void DeleteOrder(Order order) { if (order == null) @@ -366,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) @@ -379,19 +266,10 @@ public virtual void DeleteOrderNote(OrderNote orderNote) _orderNoteRepository.Delete(orderNote); - //event notifications - _eventPublisher.EntityDeleted(orderNote); - var order = GetOrderById(orderId); _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; @@ -421,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) @@ -438,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) @@ -454,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, @@ -506,7 +360,7 @@ join p in _productRepository.Table on orderItem.ProductId equals p.Id public virtual Multimap GetOrderItemsByOrderIds(int[] orderIds) { - Guard.ArgumentNotNull(() => orderIds); + Guard.NotNull(orderIds, nameof(orderIds)); var query = from x in _orderItemRepository.TableUntracked.Expand(x => x.Product) @@ -521,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) @@ -534,9 +384,6 @@ public virtual void DeleteOrderItem(OrderItem orderItem) _orderItemRepository.Delete(orderItem); - //event notifications - _eventPublisher.EntityDeleted(orderItem); - var order = GetOrderById(orderId); _eventPublisher.PublishOrderUpdated(order); } @@ -545,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) @@ -558,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) @@ -571,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) @@ -582,15 +416,9 @@ public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) _recurringPaymentRepository.Insert(recurringPayment); - //event notification - _eventPublisher.EntityInserted(recurringPayment); _eventPublisher.PublishOrderUpdated(recurringPayment.InitialOrder); } - /// - /// Updates the recurring payment - /// - /// Recurring payment public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -598,20 +426,9 @@ public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) _recurringPaymentRepository.Update(recurringPayment); - //event notification - _eventPublisher.EntityUpdated(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) @@ -646,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) @@ -659,18 +472,10 @@ public virtual void DeleteReturnRequest(ReturnRequest returnRequest) _returnRequestRepository.Delete(returnRequest); - //event notifications - _eventPublisher.EntityDeleted(returnRequest); - var orderItem = GetOrderItemById(orderItemId); _eventPublisher.PublishOrderUpdated(orderItem.Order); } - /// - /// Gets a return request - /// - /// Return request identifier - /// Return request public virtual ReturnRequest GetReturnRequestById(int returnRequestId) { if (returnRequestId == 0) @@ -679,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; @@ -719,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 e154dd519d..7bcf2910a0 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -5,6 +5,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; @@ -13,6 +14,7 @@ using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Common; +using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.Payments; using SmartStore.Services.Shipping; @@ -20,14 +22,16 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderTotalCalculationService : IOrderTotalCalculationService + /// + /// Order service + /// + public partial class OrderTotalCalculationService : IOrderTotalCalculationService { - #region Fields + private const string CART_TAXING_INFO_KEY = "CartTaxingInfos"; - private readonly IWorkContext _workContext; + #region Fields + + private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; @@ -37,7 +41,9 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic private readonly IDiscountService _discountService; private readonly IGiftCardService _giftCardService; private readonly IGenericAttributeService _genericAttributeService; - private readonly IProductAttributeParser _productAttributeParser; + private readonly IPaymentService _paymentService; + private readonly ICurrencyService _currencyService; + private readonly IProductAttributeParser _productAttributeParser; private readonly TaxSettings _taxSettings; private readonly RewardPointsSettings _rewardPointsSettings; private readonly ShippingSettings _shippingSettings; @@ -66,7 +72,8 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic /// Shipping settings /// Shopping cart settings /// Catalog settings - public OrderTotalCalculationService(IWorkContext workContext, + public OrderTotalCalculationService( + IWorkContext workContext, IStoreContext storeContext, IPriceCalculationService priceCalculationService, ITaxService taxService, @@ -76,29 +83,33 @@ public OrderTotalCalculationService(IWorkContext workContext, IDiscountService discountService, IGiftCardService giftCardService, IGenericAttributeService genericAttributeService, - IProductAttributeParser productAttributeParser, + IPaymentService paymentService, + ICurrencyService currencyService, + IProductAttributeParser productAttributeParser, TaxSettings taxSettings, RewardPointsSettings rewardPointsSettings, ShippingSettings shippingSettings, ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings) { - this._workContext = workContext; - this._storeContext = storeContext; - this._priceCalculationService = priceCalculationService; - this._taxService = taxService; - this._shippingService = shippingService; - this._providerManager = providerManager; - this._checkoutAttributeParser = checkoutAttributeParser; - this._discountService = discountService; - this._giftCardService = giftCardService; - this._genericAttributeService = genericAttributeService; - this._productAttributeParser = productAttributeParser; - this._taxSettings = taxSettings; - this._rewardPointsSettings = rewardPointsSettings; - this._shippingSettings = shippingSettings; - this._shoppingCartSettings = shoppingCartSettings; - this._catalogSettings = catalogSettings; + _workContext = workContext; + _storeContext = storeContext; + _priceCalculationService = priceCalculationService; + _taxService = taxService; + _shippingService = shippingService; + _providerManager = providerManager; + _checkoutAttributeParser = checkoutAttributeParser; + _discountService = discountService; + _giftCardService = giftCardService; + _genericAttributeService = genericAttributeService; + _paymentService = paymentService; + _currencyService = currencyService; + _productAttributeParser = productAttributeParser; + _taxSettings = taxSettings; + _rewardPointsSettings = rewardPointsSettings; + _shippingSettings = shippingSettings; + _shoppingCartSettings = shoppingCartSettings; + _catalogSettings = catalogSettings; T = NullLocalizer.Instance; } @@ -107,6 +118,174 @@ public OrderTotalCalculationService(IWorkContext workContext, #endregion + #region Utilities + + private Func GetTaxingInfo = cartItem => (CartTaxingInfo)cartItem.CustomProperties[CART_TAXING_INFO_KEY]; + + protected virtual void PrepareAuxiliaryServicesTaxingInfos(IList cart) + { + // no additional infos required + if (!cart.Any() || _taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.SpecifiedTaxCategory) + return; + + // additional infos already collected + if (cart.First().CustomProperties.ContainsKey(CART_TAXING_INFO_KEY)) + return; + + // instance taxing info objects + cart.Each(x => x.CustomProperties[CART_TAXING_INFO_KEY] = new CartTaxingInfo()); + + // collect infos + if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestCartAmount) + { + // calculate all subtotals + cart.Each(x => GetTaxingInfo(x).SubTotalWithoutDiscount = _priceCalculationService.GetSubTotal(x, false)); + + // items with the highest subtotal + var highestAmountItems = cart + .GroupBy(x => x.Item.Product.TaxCategoryId) + .OrderByDescending(x => x.Sum(y => GetTaxingInfo(y).SubTotalWithoutDiscount)) + .First(); + + // mark items + highestAmountItems.Each(x => GetTaxingInfo(x).HasHighestCartAmount = true); + } + else if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestTaxRate) + { + var customer = cart.GetCustomer(); + var maxTaxRate = decimal.Zero; + var maxTaxCategoryId = 0; + + // get tax category id with the highest rate + cart.Each(x => + { + var taxRate = _taxService.GetTaxRate(x.Item.Product, x.Item.Product.TaxCategoryId, customer); + if (taxRate > maxTaxRate) + { + maxTaxRate = taxRate; + maxTaxCategoryId = x.Item.Product.TaxCategoryId; + } + }); + + // mark items + cart.Where(x => x.Item.Product.TaxCategoryId == maxTaxCategoryId) + .Each(x => GetTaxingInfo(x).HasHighestTaxRate = true); + } + //else if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.ProRata) + //{ + // // calculate all subtotals + // cart.Each(x => GetTaxingInfo(x).SubTotalWithoutDiscount = _priceCalculationService.GetSubTotal(x, false)); + + // // sum over all subtotals + // var subTotalSum = cart.Sum(x => GetTaxingInfo(x).SubTotalWithoutDiscount); + + // // calculate pro rata weightings + // cart.Each(x => + // { + // var taxingInfo = GetTaxingInfo(x); + // taxingInfo.ProRataWeighting = taxingInfo.SubTotalWithoutDiscount / subTotalSum; + // }); + //} + } + + protected virtual decimal? GetAdjustedShippingTotal(IList cart, out Discount appliedDiscount) + { + appliedDiscount = null; + + decimal? shippingTotal = null; + ShippingOption shippingOption = null; + var customer = cart.GetCustomer(); + var storeId = _storeContext.CurrentStore.Id; + + if (customer != null) + { + shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, _genericAttributeService, storeId); + } + + if (shippingOption != null) + { + // use last shipping option (get from cache) + var shippingMethods = _shippingService.GetAllShippingMethods(null, storeId); + shippingTotal = AdjustShippingRate(shippingOption.Rate, cart, shippingOption, shippingMethods, out appliedDiscount); + } + else + { + // use fixed rate (if possible) + Address shippingAddress = null; + if (customer != null) + shippingAddress = customer.ShippingAddress; + + var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(storeId); + if (!shippingRateComputationMethods.Any()) + throw new SmartException(T("Shipping.CouldNotLoadMethod")); + + if (shippingRateComputationMethods.Count() == 1) + { + var shippingRateComputationMethod = shippingRateComputationMethods.First(); + var getShippingOptionRequest = _shippingService.CreateShippingOptionRequest(cart, shippingAddress, storeId); + var fixedRate = shippingRateComputationMethod.Value.GetFixedRate(getShippingOptionRequest); + + if (fixedRate.HasValue) + { + shippingTotal = AdjustShippingRate(fixedRate.Value, cart, null, null, out appliedDiscount); + } + } + } + + return shippingTotal; + } + + protected virtual decimal GetShippingTaxAmount( + decimal shipping, + Customer customer, + int taxCategoryId, + SortedDictionary taxRates) + { + var taxRate = decimal.Zero; + var shippingExclTax = _taxService.GetShippingPrice(shipping, false, customer, taxCategoryId, out taxRate); + var shippingInclTax = _taxService.GetShippingPrice(shipping, true, customer, taxCategoryId, out taxRate); + + var shippingTax = shippingInclTax - shippingExclTax; + + if (shippingTax < decimal.Zero) + shippingTax = decimal.Zero; + + if (taxRate > decimal.Zero && shippingTax > decimal.Zero) + { + if (taxRates.ContainsKey(taxRate)) + taxRates[taxRate] = taxRates[taxRate] + shippingTax; + else + taxRates.Add(taxRate, shippingTax); + } + + return shippingTax; + } + + protected virtual decimal GetPaymentFeeTaxAmount( + decimal paymentFee, + Customer customer, + int taxCategoryId, + SortedDictionary taxRates) + { + var taxRate = decimal.Zero; + var paymentFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentFee, false, customer, taxCategoryId, out taxRate); + var paymentFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentFee, true, customer, taxCategoryId, out taxRate); + + var paymentFeeTax = paymentFeeInclTax - paymentFeeExclTax; + + if (taxRate > decimal.Zero && paymentFeeTax != decimal.Zero) + { + if (taxRates.ContainsKey(taxRate)) + taxRates[taxRate] = taxRates[taxRate] + paymentFeeTax; + else + taxRates.Add(taxRate, paymentFeeTax); + } + + return paymentFeeTax; + } + + #endregion + #region Methods /// @@ -122,6 +301,7 @@ public virtual void GetShoppingCartSubTotal(IList car out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) { bool includingTax = false; + switch (_workContext.TaxDisplayType) { case TaxDisplayType.ExcludingTax: @@ -131,9 +311,13 @@ public virtual void GetShoppingCartSubTotal(IList car includingTax = true; break; } - GetShoppingCartSubTotal(cart, includingTax, - out discountAmount, out appliedDiscount, - out subTotalWithoutDiscount, out subTotalWithDiscount); + + GetShoppingCartSubTotal(cart, + includingTax, + out discountAmount, + out appliedDiscount, + out subTotalWithoutDiscount, + out subTotalWithDiscount); } /// @@ -151,9 +335,14 @@ public virtual void GetShoppingCartSubTotal(IList car out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) { SortedDictionary taxRates = null; - GetShoppingCartSubTotal(cart, includingTax, - out discountAmount, out appliedDiscount, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxRates); + + GetShoppingCartSubTotal(cart, + includingTax, + out discountAmount, + out appliedDiscount, + out subTotalWithoutDiscount, + out subTotalWithDiscount, + out taxRates); } /// @@ -168,8 +357,10 @@ public virtual void GetShoppingCartSubTotal(IList car /// Tax rates (of order sub total) public virtual void GetShoppingCartSubTotal(IList cart, bool includingTax, - out decimal discountAmount, out Discount appliedDiscount, - out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, + out decimal discountAmount, + out Discount appliedDiscount, + out decimal subTotalWithoutDiscount, + out decimal subTotalWithDiscount, out SortedDictionary taxRates) { discountAmount = decimal.Zero; @@ -181,11 +372,12 @@ public virtual void GetShoppingCartSubTotal(IList car if (cart.Count == 0) return; - //get the customer - Customer customer = cart.GetCustomer(); + // get the customer + var customer = cart.GetCustomer(); + var currency = _workContext.WorkingCurrency; - //sub totals - decimal subTotalExclTaxWithoutDiscount = decimal.Zero; + // sub totals + decimal subTotalExclTaxWithoutDiscount = decimal.Zero; decimal subTotalInclTaxWithoutDiscount = decimal.Zero; foreach (var shoppingCartItem in cart) @@ -197,7 +389,7 @@ public virtual void GetShoppingCartSubTotal(IList car shoppingCartItem.Item.Product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, _productAttributeParser); - if (_shoppingCartSettings.RoundPricesDuringCalculation) + if (currency.RoundOrderItemsEnabled) { // Gross > Net RoundFix int qty = shoppingCartItem.Item.Quantity; @@ -206,9 +398,9 @@ public virtual void GetShoppingCartSubTotal(IList car // Adaption to eliminate rounding issues sciExclTax = _taxService.GetProductPrice(shoppingCartItem.Item.Product, sciSubTotal, false, customer, out taxRate); - sciExclTax = Math.Round(sciExclTax, 2) * qty; + sciExclTax = sciExclTax.RoundIfEnabledFor(currency) * qty; sciInclTax = _taxService.GetProductPrice(shoppingCartItem.Item.Product, sciSubTotal, true, customer, out taxRate); - sciInclTax = Math.Round(sciInclTax, 2) * qty; + sciInclTax = sciInclTax.RoundIfEnabledFor(currency) * qty; } else { @@ -220,7 +412,7 @@ public virtual void GetShoppingCartSubTotal(IList car subTotalExclTaxWithoutDiscount += sciExclTax; subTotalInclTaxWithoutDiscount += sciInclTax; - //tax rates + // tax rates decimal sciTax = sciInclTax - sciExclTax; if (taxRate > decimal.Zero && sciTax > decimal.Zero) { @@ -235,7 +427,7 @@ public virtual void GetShoppingCartSubTotal(IList car } } - //checkout attributes + // checkout attributes if (customer != null) { var checkoutAttributesXml = customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); @@ -268,17 +460,22 @@ public virtual void GetShoppingCartSubTotal(IList car } } - //subtotal without discount - if (includingTax) - subTotalWithoutDiscount = subTotalInclTaxWithoutDiscount; - else - subTotalWithoutDiscount = subTotalExclTaxWithoutDiscount; + //subtotal without discount + if (includingTax) + { + subTotalWithoutDiscount = subTotalInclTaxWithoutDiscount; + } + else + { + subTotalWithoutDiscount = subTotalExclTaxWithoutDiscount; + } - if (subTotalWithoutDiscount < decimal.Zero) - subTotalWithoutDiscount = decimal.Zero; + if (subTotalWithoutDiscount < decimal.Zero) + { + subTotalWithoutDiscount = decimal.Zero; + } - if (_shoppingCartSettings.RoundPricesDuringCalculation) - subTotalWithoutDiscount = Math.Round(subTotalWithoutDiscount, 2); + subTotalWithoutDiscount = subTotalWithoutDiscount.RoundIfEnabledFor(currency); /*We calculate discount amount on order subtotal excl tax (discount first)*/ //calculate discount amount ('Applied to order subtotal' discount) @@ -307,8 +504,7 @@ public virtual void GetShoppingCartSubTotal(IList car decimal discountTax = taxRates[taxRate] * (discountAmountExclTax / subTotalExclTaxWithoutDiscount); discountAmountInclTax += discountTax; taxValue = taxRates[taxRate] - discountTax; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - taxValue = Math.Round(taxValue, 2); + taxValue = taxValue.RoundIfEnabledFor(currency); taxRates[taxRate] = taxValue; } @@ -317,8 +513,7 @@ public virtual void GetShoppingCartSubTotal(IList car } } - if (_shoppingCartSettings.RoundPricesDuringCalculation) - discountAmountInclTax = Math.Round(discountAmountInclTax, 2); + discountAmountInclTax = discountAmountInclTax.RoundIfEnabledFor(currency); if (includingTax) { @@ -331,12 +526,13 @@ public virtual void GetShoppingCartSubTotal(IList car discountAmount = discountAmountExclTax; } - //round - if (subTotalWithDiscount < decimal.Zero) - subTotalWithDiscount = decimal.Zero; + //round + if (subTotalWithDiscount < decimal.Zero) + { + subTotalWithDiscount = decimal.Zero; + } - if (_shoppingCartSettings.RoundPricesDuringCalculation) - subTotalWithDiscount = Math.Round(subTotalWithDiscount, 2); + subTotalWithDiscount = subTotalWithDiscount.RoundIfEnabledFor(currency); } /// @@ -377,10 +573,7 @@ public virtual decimal GetOrderSubtotalDiscount(Customer customer, return discountAmount; } - - - - + /// /// Gets shopping cart additional shipping charge /// @@ -395,17 +588,28 @@ public virtual decimal GetShoppingCartAdditionalShippingCharge(IList additionalShippingCharge += (x.Item.Product.AdditionalShippingCharge * x.Item.Quantity)); - } - else - { - additionalShippingCharge += sci.Item.Product.AdditionalShippingCharge * sci.Item.Quantity; - } - } + + if (_shippingSettings.ChargeOnlyHighestProductShippingSurcharge) + { + if (additionalShippingCharge < sci.Item.Product.AdditionalShippingCharge) + { + additionalShippingCharge = sci.Item.Product.AdditionalShippingCharge; + } + } + else + { + if (sci.Item.IsShipEnabled && !sci.Item.IsFreeShipping && sci.Item.Product != null) + { + if (sci.Item.Product.ProductType == ProductType.BundledProduct && sci.Item.Product.BundlePerItemShipping) + { + sci.ChildItems.Each(x => additionalShippingCharge += (x.Item.Product.AdditionalShippingCharge * x.Item.Quantity)); + } + else + { + additionalShippingCharge += sci.Item.Product.AdditionalShippingCharge * sci.Item.Quantity; + } + } + } } return additionalShippingCharge; } @@ -471,18 +675,18 @@ public virtual bool IsFreeShipping(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) { @@ -491,7 +695,9 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IList x.Item.IsShipEnabled && !x.Item.IsFreeShipping)) + { bundlePerItemShipping += shippingRate; + } } } else if (adjustedRate == decimal.Zero) @@ -502,30 +708,33 @@ 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) - adjustedRate = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - adjustedRate = Math.Round(adjustedRate, 2); + if (adjustedRate < decimal.Zero) + { + adjustedRate = decimal.Zero; + } + adjustedRate = adjustedRate.RoundIfEnabledFor(_workContext.WorkingCurrency); return adjustedRate; } @@ -557,22 +766,10 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IListShipping total public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax) { - decimal taxRate = decimal.Zero; - return GetShoppingCartShippingTotal(cart, includingTax, out taxRate); - } + var taxRate = decimal.Zero; + Discount appliedDiscount = null; - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Applied tax rate - /// Shipping total - public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate) - { - Discount appliedDiscount = null; - return GetShoppingCartShippingTotal(cart, includingTax, out taxRate, out appliedDiscount); + return GetShoppingCartShippingTotal(cart, includingTax, out taxRate, out appliedDiscount); } /// @@ -583,87 +780,94 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IListApplied tax rate /// Applied discount /// Shipping total - public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate, out Discount appliedDiscount) + public virtual decimal? GetShoppingCartShippingTotal( + IList cart, + bool includingTax, + out decimal taxRate, + out Discount appliedDiscount) { - decimal? shippingTotal = null; - decimal? shippingTotalTaxed = null; - appliedDiscount = null; - taxRate = decimal.Zero; + appliedDiscount = null; + taxRate = decimal.Zero; - var customer = cart.GetCustomer(); + if (IsFreeShipping(cart)) + return decimal.Zero; - bool isFreeShipping = IsFreeShipping(cart); - if (isFreeShipping) - return decimal.Zero; + decimal? shippingTotalTaxed = null; + var taxCategoryId = 0; + var customer = cart.GetCustomer(); + var currency = _workContext.WorkingCurrency; - ShippingOption shippingOption = null; - if (customer != null) - shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, _genericAttributeService, _storeContext.CurrentStore.Id); + var shippingTotal = GetAdjustedShippingTotal(cart, out appliedDiscount); - if (shippingOption != null) - { - //use last shipping option (get from cache) + if (!shippingTotal.HasValue) + return null; - //adjust shipping rate - var shippingMethods = _shippingService.GetAllShippingMethods(); - shippingTotal = AdjustShippingRate(shippingOption.Rate, cart, shippingOption.Name, shippingMethods, out appliedDiscount); - } - else - { - //use fixed rate (if possible) - Address shippingAddress = null; - if (customer != null) - shippingAddress = customer.ShippingAddress; + if (shippingTotal.Value < decimal.Zero) + { + shippingTotal = decimal.Zero; + } - var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_storeContext.CurrentStore.Id); - if (!shippingRateComputationMethods.Any()) - throw new SmartException(T("Shipping.CouldNotLoadMethod")); + shippingTotal = shippingTotal.Value.RoundIfEnabledFor(currency); - if (shippingRateComputationMethods.Count() == 1) - { - var getShippingOptionRequest = _shippingService.CreateShippingOptionRequest(cart, shippingAddress, _storeContext.CurrentStore.Id); + PrepareAuxiliaryServicesTaxingInfos(cart); - var shippingRateComputationMethod = shippingRateComputationMethods.First(); - decimal? fixedRate = shippingRateComputationMethod.Value.GetFixedRate(getShippingOptionRequest); - if (fixedRate.HasValue) - { - //adjust shipping rate - shippingTotal = AdjustShippingRate(fixedRate.Value, cart, null, null, out appliedDiscount); - } - } - } + // commented out cause requires several plugins to be updated and migration of Order.OrderShippingTaxRate and Order.PaymentMethodAdditionalFeeTaxRate + //if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.ProRata) + //{ + // // calculate proRataShipping: get product weightings for cart and multiply them with the shipping amount + // shippingTotalTaxed = decimal.Zero; - if (shippingTotal.HasValue) - { - if (shippingTotal.Value < decimal.Zero) - shippingTotal = decimal.Zero; + // var tmpTaxRate = decimal.Zero; + // var taxRates = new List(); - //round - if (_shoppingCartSettings.RoundPricesDuringCalculation) - shippingTotal = Math.Round(shippingTotal.Value, 2); + // foreach (var item in cart) + // { + // var proRataShipping = shippingTotal.Value * GetTaxingInfo(item).ProRataWeighting; + // shippingTotalTaxed += _taxService.GetShippingPrice(proRataShipping, includingTax, customer, item.Item.Product.TaxCategoryId, out tmpTaxRate); - shippingTotalTaxed = _taxService.GetShippingPrice(shippingTotal.Value, - includingTax, - customer, - out taxRate); + // taxRates.Add(tmpTaxRate); + // } - //round - if (_shoppingCartSettings.RoundPricesDuringCalculation) - shippingTotalTaxed = Math.Round(shippingTotalTaxed.Value, 2); - } + // // a tax rate is only defined if all rates are equal. return zero tax rate in all other cases. + // if (taxRates.Any() && taxRates.Distinct().Count() == 1) + // { + // taxRate = taxRates.First(); + // } + //} + //else + //{ + + if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestCartAmount) + { + var cartItem = cart.FirstOrDefault(x => GetTaxingInfo(x).HasHighestCartAmount); + taxCategoryId = (cartItem != null ? cartItem.Item.Product.TaxCategoryId : 0); + } + else if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestTaxRate) + { + var cartItem = cart.FirstOrDefault(x => GetTaxingInfo(x).HasHighestTaxRate); + taxCategoryId = (cartItem != null ? cartItem.Item.Product.TaxCategoryId : 0); + } - return shippingTotalTaxed; + // fallback to setting + if (taxCategoryId == 0) + { + taxCategoryId = _taxSettings.ShippingTaxClassId; + } + + shippingTotalTaxed = _taxService.GetShippingPrice(shippingTotal.Value, includingTax, customer, taxCategoryId, out taxRate); + shippingTotalTaxed = shippingTotalTaxed.Value.RoundIfEnabledFor(currency); + + return shippingTotalTaxed; } - /// - /// Gets a shipping discount - /// - /// Customer - /// Shipping total - /// Applied discount - /// Shipping discount - public virtual decimal GetShippingDiscount(Customer customer, decimal shippingTotal, out Discount appliedDiscount) + /// + /// Gets a shipping discount + /// + /// Customer + /// Shipping total + /// Applied discount + /// Shipping discount + public virtual decimal GetShippingDiscount(Customer customer, decimal shippingTotal, out Discount appliedDiscount) { appliedDiscount = null; decimal shippingDiscountAmount = decimal.Zero; @@ -690,12 +894,12 @@ public virtual decimal GetShippingDiscount(Customer customer, decimal shippingTo shippingDiscountAmount = appliedDiscount.GetDiscountAmount(shippingTotal); } - if (shippingDiscountAmount < decimal.Zero) - shippingDiscountAmount = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - shippingDiscountAmount = Math.Round(shippingDiscountAmount, 2); + if (shippingDiscountAmount < decimal.Zero) + { + shippingDiscountAmount = decimal.Zero; + } + shippingDiscountAmount = shippingDiscountAmount.RoundIfEnabledFor(_workContext.WorkingCurrency); return shippingDiscountAmount; } @@ -703,7 +907,6 @@ public virtual decimal GetShippingDiscount(Customer customer, decimal shippingTo - /// /// Gets tax /// @@ -733,7 +936,11 @@ public virtual decimal GetTaxTotal(IList cart, out So taxRates = new SortedDictionary(); - var customer = cart.GetCustomer(); + var subTotalTax = decimal.Zero; + var shippingTax = decimal.Zero; + var paymentFeeTax = decimal.Zero; + var customer = cart.GetCustomer(); + var currency = _workContext.WorkingCurrency; //// (VATFIX) if (_taxService.IsVatExempt(null, customer)) @@ -743,30 +950,24 @@ public virtual decimal GetTaxTotal(IList cart, out So } //// (VATFIX) - string paymentMethodSystemName = ""; - if (customer != null) - { - paymentMethodSystemName = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, _storeContext.CurrentStore.Id); - } + #region order sub total (items + checkout attributes) - //order sub total (items + checkout attributes) - decimal subTotalTaxTotal = decimal.Zero; - decimal orderSubTotalDiscountAmount = decimal.Zero; - Discount orderSubTotalAppliedDiscount = null; - decimal subTotalWithoutDiscountBase = decimal.Zero; - decimal subTotalWithDiscountBase = decimal.Zero; - SortedDictionary orderSubTotalTaxRates = null; + var orderSubTotalDiscountAmount = decimal.Zero; + var subTotalWithoutDiscountBase = decimal.Zero; + var subTotalWithDiscountBase = decimal.Zero; + Discount appliedDiscount = null; + SortedDictionary orderSubTotalTaxRates = null; GetShoppingCartSubTotal(cart, false, - out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount, + out orderSubTotalDiscountAmount, out appliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase, out orderSubTotalTaxRates); foreach (KeyValuePair kvp in orderSubTotalTaxRates) { - decimal taxRate = kvp.Key; - decimal taxValue = kvp.Value; - subTotalTaxTotal += taxValue; + var taxRate = kvp.Key; + var taxValue = kvp.Value; + subTotalTax += taxValue; if (taxRate > decimal.Zero && taxValue > decimal.Zero) { @@ -777,75 +978,125 @@ public virtual decimal GetTaxTotal(IList cart, out So } } - //shipping - decimal shippingTax = decimal.Zero; - if (_taxSettings.ShippingIsTaxable) - { - decimal taxRate = decimal.Zero; - decimal? shippingExclTax = GetShoppingCartShippingTotal(cart, false, out taxRate); - decimal? shippingInclTax = GetShoppingCartShippingTotal(cart, true, out taxRate); + #endregion - if (shippingExclTax.HasValue && shippingInclTax.HasValue) - { - shippingTax = shippingInclTax.Value - shippingExclTax.Value; - //ensure that tax is equal or greater than zero - if (shippingTax < decimal.Zero) - shippingTax = decimal.Zero; + #region shipping tax amount - //tax rates - if (taxRate > decimal.Zero && shippingTax > decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - taxRates.Add(taxRate, shippingTax); - else - taxRates[taxRate] = taxRates[taxRate] + shippingTax; - } - } - } + if (_taxSettings.ShippingIsTaxable && !IsFreeShipping(cart)) + { + var taxCategoryId = 0; + var shippingTotal = GetAdjustedShippingTotal(cart, out appliedDiscount); - //payment method additional fee - decimal paymentMethodAdditionalFeeTax = decimal.Zero; - if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) - { - decimal taxRate = decimal.Zero; - + if (shippingTotal.HasValue) + { + if (shippingTotal.Value < decimal.Zero) + { + shippingTotal = decimal.Zero; + } + + shippingTotal = shippingTotal.Value.RoundIfEnabledFor(currency); + + PrepareAuxiliaryServicesTaxingInfos(cart); + + // commented out cause requires several plugins to be updated and migration of Order.OrderShippingTaxRate and Order.PaymentMethodAdditionalFeeTaxRate + //if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.ProRata) + //{ + // // calculate proRataShipping: get product weightings for cart and multiply them with the shipping amount + // foreach (var item in cart) + // { + // var proRataShipping = shippingTotal.Value * GetTaxingInfo(item).ProRataWeighting; + // shippingTax += GetShippingTaxAmount(proRataShipping, customer, item.Item.Product.TaxCategoryId, taxRates); + // } + //} + //else + //{ + + if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestCartAmount) + { + var cartItem = cart.FirstOrDefault(x => GetTaxingInfo(x).HasHighestCartAmount); + taxCategoryId = (cartItem != null ? cartItem.Item.Product.TaxCategoryId : 0); + } + else if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestTaxRate) + { + var cartItem = cart.FirstOrDefault(x => GetTaxingInfo(x).HasHighestTaxRate); + taxCategoryId = (cartItem != null ? cartItem.Item.Product.TaxCategoryId : 0); + } + + // fallback to setting + if (taxCategoryId == 0) + { + taxCategoryId = _taxSettings.ShippingTaxClassId; + } + + shippingTax = GetShippingTaxAmount(shippingTotal.Value, customer, taxCategoryId, taxRates); + shippingTax = shippingTax.RoundIfEnabledFor(currency); + } + } + + #endregion + + #region payment fee tax amount + + if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable && customer != null) + { + var paymentMethodSystemName = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, _storeContext.CurrentStore.Id); var provider = _providerManager.GetProvider(paymentMethodSystemName); - var paymentMethodAdditionalFee = (provider != null ? provider.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); - if (_shoppingCartSettings.RoundPricesDuringCalculation) + if (provider != null) { - paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); - } + var taxCategoryId = 0; + var paymentFee = provider.Value.GetAdditionalHandlingFee(cart); + paymentFee = paymentFee.RoundIfEnabledFor(currency); + + PrepareAuxiliaryServicesTaxingInfos(cart); + + // commented out cause requires several plugins to be updated and migration of Order.OrderShippingTaxRate and Order.PaymentMethodAdditionalFeeTaxRate + //if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.ProRata) + //{ + // // calculate proRataShipping: get product weightings for cart and multiply them with the shipping amount + // foreach (var item in cart) + // { + // var proRataPaymentFees = paymentFee * GetTaxingInfo(item).ProRataWeighting; + // paymentFeeTax += GetPaymentFeeTaxAmount(proRataPaymentFees, customer, item.Item.Product.TaxCategoryId, taxRates); + // } + //} + //else + //{ + + if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestCartAmount) + { + var cartItem = cart.FirstOrDefault(x => GetTaxingInfo(x).HasHighestCartAmount); + taxCategoryId = (cartItem != null ? cartItem.Item.Product.TaxCategoryId : 0); + } + else if (_taxSettings.AuxiliaryServicesTaxingType == AuxiliaryServicesTaxType.HighestTaxRate) + { + var cartItem = cart.FirstOrDefault(x => GetTaxingInfo(x).HasHighestTaxRate); + taxCategoryId = (cartItem != null ? cartItem.Item.Product.TaxCategoryId : 0); + } - decimal paymentMethodAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer, out taxRate); - decimal paymentMethodAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, customer, out taxRate); + // fallback to setting + if (taxCategoryId == 0) + taxCategoryId = _taxSettings.PaymentMethodAdditionalFeeTaxClassId; - paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax; + paymentFeeTax = GetPaymentFeeTaxAmount(paymentFee, customer, taxCategoryId, taxRates); + } + } - //tax rates - if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax != decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - taxRates.Add(taxRate, paymentMethodAdditionalFeeTax); - else - taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax; - } - } + #endregion - //add at least one tax rate (0%) - if (taxRates.Count == 0) + // add at least one tax rate (0%) + if (taxRates.Count == 0) taxRates.Add(decimal.Zero, decimal.Zero); - //summarize taxes - decimal taxTotal = subTotalTaxTotal + shippingTax + paymentMethodAdditionalFeeTax; + // summarize taxes + var taxTotal = subTotalTax + shippingTax + paymentFeeTax; - //ensure that tax is equal or greater than zero + // ensure that tax is equal or greater than zero if (taxTotal < decimal.Zero) taxTotal = decimal.Zero; - //round tax - if (_shoppingCartSettings.RoundPricesDuringCalculation) - taxTotal = Math.Round(taxTotal, 2); + // round tax + taxTotal = taxTotal.RoundIfEnabledFor(currency); return taxTotal; } @@ -853,215 +1104,212 @@ public virtual decimal GetTaxTotal(IList cart, out So - /// - /// Gets shopping cart total - /// - /// 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 use payment method additional fee when calculating order total - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - public virtual decimal? GetShoppingCartTotal(IList cart, - bool ignoreRewardPonts = false, bool usePaymentMethodAdditionalFee = true) - { - decimal discountAmount = decimal.Zero; - Discount appliedDiscount = null; - - int redeemedRewardPoints = 0; - decimal redeemedRewardPointsAmount = decimal.Zero; - List appliedGiftCards = null; - - return GetShoppingCartTotal(cart, out discountAmount, out appliedDiscount, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, ignoreRewardPonts, usePaymentMethodAdditionalFee); - } - - /// - /// Gets shopping cart total - /// - /// Cart - /// Applied gift cards - /// Applied discount amount - /// Applied discount - /// Reward points to redeem - /// Reward points amount in primary store currency to redeem - /// 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 - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - public virtual decimal? GetShoppingCartTotal(IList cart, - out decimal discountAmount, out Discount appliedDiscount, - out List appliedGiftCards, - out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, - bool ignoreRewardPonts = false, bool usePaymentMethodAdditionalFee = true) + public virtual ShoppingCartTotal GetShoppingCartTotal( + IList cart, + bool ignoreRewardPoints = false, + bool usePaymentMethodAdditionalFee = true, + bool ignoreCreditBalance = false) { - redeemedRewardPoints = 0; - redeemedRewardPointsAmount = decimal.Zero; - var customer = cart.GetCustomer(); - string paymentMethodSystemName = ""; + var store = _storeContext.CurrentStore; + var currency = _workContext.WorkingCurrency; + var paymentMethodSystemName = ""; + if (customer != null) - { - paymentMethodSystemName = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, _storeContext.CurrentStore.Id); - } + { + paymentMethodSystemName = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _genericAttributeService, store.Id); + } - //subtotal without tax - decimal subtotalBase = decimal.Zero; - decimal orderSubTotalDiscountAmount = decimal.Zero; + // Subtotal without tax + var subtotalBase = decimal.Zero; + var orderSubTotalDiscountAmount = decimal.Zero; Discount orderSubTotalAppliedDiscount = null; - decimal subTotalWithoutDiscountBase = decimal.Zero; - decimal subTotalWithDiscountBase = decimal.Zero; + var subTotalWithoutDiscountBase = decimal.Zero; + var subTotalWithDiscountBase = decimal.Zero; GetShoppingCartSubTotal(cart, false, out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - //subtotal with discount + // Subtotal with discount subtotalBase = subTotalWithDiscountBase; - //shipping without tax + // Shipping without tax decimal? shoppingCartShipping = GetShoppingCartShippingTotal(cart, false); - //payment method additional fee without tax - decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero; - if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) + // Payment method additional fee without tax + var paymentMethodAdditionalFeeWithoutTax = decimal.Zero; + if (usePaymentMethodAdditionalFee && !string.IsNullOrEmpty(paymentMethodSystemName)) { - var provider = _providerManager.GetProvider(paymentMethodSystemName); - var paymentMethodAdditionalFee = (provider != null ? provider.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); + var provider = _providerManager.GetProvider(paymentMethodSystemName); + var paymentMethodAdditionalFee = (provider != null ? provider.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); - if (_shoppingCartSettings.RoundPricesDuringCalculation) - { - paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); - } - - paymentMethodAdditionalFeeWithoutTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer); + paymentMethodAdditionalFee = paymentMethodAdditionalFee.RoundIfEnabledFor(currency); + paymentMethodAdditionalFeeWithoutTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer); } - //tax - decimal shoppingCartTax = GetTaxTotal(cart, usePaymentMethodAdditionalFee); + // Tax + var shoppingCartTax = GetTaxTotal(cart, usePaymentMethodAdditionalFee); - //order total - decimal resultTemp = decimal.Zero; + // Order total + var resultTemp = decimal.Zero; resultTemp += subtotalBase; if (shoppingCartShipping.HasValue) { resultTemp += shoppingCartShipping.Value; } - resultTemp += paymentMethodAdditionalFeeWithoutTax; - ////// (VATFIX) - ////resultTemp += shoppingCartTax; - //if (_taxService.IsVatExempt(null, customer)) - //{ - // // add nothing to total - //} - //else - //{ - resultTemp += shoppingCartTax; - //} - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = Math.Round(resultTemp, 2); + resultTemp += paymentMethodAdditionalFeeWithoutTax; + resultTemp += shoppingCartTax; + resultTemp = resultTemp.RoundIfEnabledFor(currency); #region Order total discount - discountAmount = GetOrderTotalDiscount(customer, resultTemp, out appliedDiscount); + Discount appliedDiscount = null; + var discountAmount = GetOrderTotalDiscount(customer, resultTemp, out appliedDiscount); - //sub totals with discount + // Sub totals with discount if (resultTemp < discountAmount) discountAmount = resultTemp; - //reduce subtotal + // Reduce subtotal resultTemp -= discountAmount; if (resultTemp < decimal.Zero) + { resultTemp = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = Math.Round(resultTemp, 2); + } + + resultTemp = resultTemp.RoundIfEnabledFor(currency); #endregion #region Applied gift cards - //let's apply gift cards now (gift cards that can be used) - appliedGiftCards = new List(); + // Let's apply gift cards now (gift cards that can be used) + var appliedGiftCards = new List(); if (!cart.IsRecurring()) { - //we don't apply gift cards for recurring products - var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer); - if (giftCards != null) - { - foreach (var gc in giftCards) - { - if (resultTemp > decimal.Zero) - { - decimal remainingAmount = gc.GetGiftCardRemainingAmount(); - decimal amountCanBeUsed = decimal.Zero; - if (resultTemp > remainingAmount) - amountCanBeUsed = remainingAmount; - else - amountCanBeUsed = resultTemp; - - //reduce subtotal - resultTemp -= amountCanBeUsed; - - var appliedGiftCard = new AppliedGiftCard(); - appliedGiftCard.GiftCard = gc; - appliedGiftCard.AmountCanBeUsed = amountCanBeUsed; - appliedGiftCards.Add(appliedGiftCard); - } - } - } - } + // We don't apply gift cards for recurring products + var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer, store.Id); + if (giftCards != null) + { + foreach (var gc in giftCards) + { + if (resultTemp > decimal.Zero) + { + var remainingAmount = gc.GetGiftCardRemainingAmount(); + var amountCanBeUsed = resultTemp > remainingAmount ? remainingAmount : resultTemp; - #endregion + // Reduce subtotal + resultTemp -= amountCanBeUsed; - if (resultTemp < decimal.Zero) - resultTemp = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = Math.Round(resultTemp, 2); - - decimal? orderTotal = null; - if (!shoppingCartShipping.HasValue) - { - //return null if we have errors - orderTotal = null; - return orderTotal; - } - else - { - //return result if we have no errors - orderTotal = resultTemp; + appliedGiftCards.Add(new AppliedGiftCard + { + GiftCard = gc, + AmountCanBeUsed = amountCanBeUsed + }); + } + } + } } + #endregion + #region Reward points + var redeemedRewardPoints = 0; + var redeemedRewardPointsAmount = decimal.Zero; + if (_rewardPointsSettings.Enabled && - !ignoreRewardPonts && customer != null && - customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id)) + !ignoreRewardPoints && customer != null && + customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, store.Id)) { - int rewardPointsBalance = customer.GetRewardPointsBalance(); - decimal rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance); + var rewardPointsBalance = customer.GetRewardPointsBalance(); + var rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance); - if (orderTotal.HasValue && orderTotal.Value > decimal.Zero) + if (resultTemp > decimal.Zero) { - if (orderTotal.Value > rewardPointsBalanceAmount) + if (resultTemp > rewardPointsBalanceAmount) { redeemedRewardPoints = rewardPointsBalance; redeemedRewardPointsAmount = rewardPointsBalanceAmount; } else { - redeemedRewardPointsAmount = orderTotal.Value; + redeemedRewardPointsAmount = resultTemp; redeemedRewardPoints = ConvertAmountToRewardPoints(redeemedRewardPointsAmount); } } } - #endregion - if (orderTotal.HasValue) + #endregion + + if (resultTemp < decimal.Zero) + { + resultTemp = decimal.Zero; + } + + resultTemp = resultTemp.RoundIfEnabledFor(currency); + + 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) { orderTotal = orderTotal.Value - redeemedRewardPointsAmount; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - orderTotal = Math.Round(orderTotal.Value, 2); - return orderTotal; + + // 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); + + // Order total rounding + if (currency.RoundOrderTotalEnabled && paymentMethodSystemName.HasValue()) + { + var paymentMethod = _paymentService.GetPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod != null && paymentMethod.RoundOrderTotalEnabled) + { + orderTotal = orderTotal.Value.RoundToNearest(currency, out roundingAmount); + orderTotalConverted = orderTotalConverted.Value.RoundToNearest(currency, out roundingAmountConverted); + } + } } - return null; + + var result = new ShoppingCartTotal(orderTotal); + result.RoundingAmount = roundingAmount; + result.DiscountAmount = discountAmount; + result.AppliedDiscount = appliedDiscount; + result.AppliedGiftCards = appliedGiftCards; + result.RedeemedRewardPoints = redeemedRewardPoints; + result.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; + result.CreditBalance = appliedCreditBalance; + + result.ConvertedFromPrimaryStoreCurrency.TotalAmount = orderTotalConverted; + result.ConvertedFromPrimaryStoreCurrency.RoundingAmount = roundingAmountConverted; + + return result; } /// @@ -1099,9 +1347,7 @@ public virtual decimal GetOrderTotalDiscount(Customer customer, decimal orderTot if (discountAmount < decimal.Zero) discountAmount = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - discountAmount = Math.Round(discountAmount, 2); - + discountAmount = discountAmount.RoundIfEnabledFor(_workContext.WorkingCurrency); return discountAmount; } @@ -1121,8 +1367,8 @@ public virtual decimal ConvertRewardPointsToAmount(int rewardPoints) return decimal.Zero; result = rewardPoints * _rewardPointsSettings.ExchangeRate; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - result = Math.Round(result, 2); + result = result.RoundIfEnabledFor(_workContext.WorkingCurrency); + return result; } @@ -1150,4 +1396,18 @@ public virtual int ConvertAmountToRewardPoints(decimal amount) #endregion } + + + internal class CartTaxingInfo + { + internal CartTaxingInfo() + { + ProRataWeighting = decimal.Zero; + } + + public decimal SubTotalWithoutDiscount { get; internal set; } + public bool HasHighestCartAmount { get; internal set; } + public bool HasHighestTaxRate { get; internal set; } + public decimal ProRataWeighting { get; internal set; } + } } diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs index 84dad37b83..1f6c7970dd 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs @@ -4,8 +4,6 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Catalog; using SmartStore.Services.Localization; namespace SmartStore.Services.Orders @@ -63,8 +61,11 @@ public static bool IsRecurring(this IList shoppingCar /// Cycle period /// Total cycles /// Error (if exists); otherwise, empty string - public static string GetRecurringCycleInfo(this IList shoppingCart, ILocalizationService localizationService, - out int cycleLength, out RecurringProductCyclePeriod cyclePeriod, out int totalCycles) + public static string GetRecurringCycleInfo(this IList shoppingCart, + ILocalizationService localizationService, + out int cycleLength, + out RecurringProductCyclePeriod cyclePeriod, + out int totalCycles) { string error = ""; @@ -87,7 +88,7 @@ public static string GetRecurringCycleInfo(this IList string conflictError = localizationService.GetResource("ShoppingCart.ConflictingShipmentSchedules"); if (product.IsRecurring) { - //cycle length + // cycle length if (_cycleLength.HasValue && _cycleLength.Value != product.RecurringCycleLength) { error = conflictError; @@ -98,7 +99,7 @@ public static string GetRecurringCycleInfo(this IList _cycleLength = product.RecurringCycleLength; } - //cycle period + // cycle period if (_cyclePeriod.HasValue && _cyclePeriod.Value != product.RecurringCyclePeriod) { error = conflictError; @@ -109,7 +110,7 @@ public static string GetRecurringCycleInfo(this IList _cyclePeriod = product.RecurringCyclePeriod; } - //total cycles + // total cycles if (_totalCycles.HasValue && _totalCycles.Value != product.RecurringTotalCycles) { error = conflictError; @@ -154,44 +155,5 @@ public static IEnumerable Filter(this IEnumerable Organize(this IList cart) - { - var result = new List(); - var productAttributeParser = EngineContext.Current.Resolve(); - - if (cart == null || cart.Count <= 0) - return result; - - foreach (var parent in cart.Where(x => x.ParentItemId == null)) - { - var parentItem = new OrganizedShoppingCartItem(parent); - - var childs = cart.Where(x => x.ParentItemId != null && x.ParentItemId == parent.Id && x.Id != parent.Id && - x.ShoppingCartTypeId == parent.ShoppingCartTypeId && x.Product.CanBeBundleItem()); - - foreach (var child in childs) - { - var childItem = new OrganizedShoppingCartItem(child); - - if (parent.Product != null && parent.Product.BundlePerItemPricing && child.AttributesXml != null && child.BundleItem != null) - { - child.Product.MergeWithCombination(child.AttributesXml); - - var attributeValues = productAttributeParser.ParseProductVariantAttributeValues(child.AttributesXml).ToList(); - if (attributeValues != null) - { - childItem.BundleItemData.AdditionalCharge = decimal.Zero; - attributeValues.Each(x => childItem.BundleItemData.AdditionalCharge += x.PriceAdjustment); - } - } - - parentItem.ChildItems.Add(childItem); - } - - result.Add(parentItem); - } - - return result; - } } } diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs index 27f0331be4..d0880c760d 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs @@ -17,17 +17,19 @@ using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Services.Stores; +using SmartStore.Core.Domain.Discounts; +using SmartStore.Services.Catalog.Extensions; +using SmartStore.Core.Caching; namespace SmartStore.Services.Orders { - /// - /// Shopping cart service - /// public partial class ShoppingCartService : IShoppingCartService { - #region Fields + // 0 = CustomerId, 1 = CartType, 2 = StoreId + const string CARTITEMS_KEY = "sm.cartitems-{0}-{1}-{2}"; + const string CARTITEMS_PATTERN_KEY = "sm.cartitems-*"; - private readonly IRepository _sciRepository; + private readonly IRepository _sciRepository; private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; private readonly ICurrencyService _currencyService; @@ -47,32 +49,10 @@ public partial class ShoppingCartService : IShoppingCartService private readonly IGenericAttributeService _genericAttributeService; private readonly IDownloadService _downloadService; private readonly CatalogSettings _catalogSettings; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IRequestCache _requestCache; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Shopping cart repository - /// Work context - /// Store context - /// Currency service - /// Product settings - /// Localization service - /// Product attribute parser - /// Checkout attribute service - /// Checkout attribute parser - /// Price formatter - /// Customer service - /// Shopping cart settings - /// Event publisher - /// Permission service - /// ACL service - /// Store mapping service - /// Generic attribute service - public ShoppingCartService( + public ShoppingCartService( IRepository sciRepository, IWorkContext workContext, IStoreContext storeContext, @@ -91,123 +71,210 @@ public ShoppingCartService( IStoreMappingService storeMappingService, IGenericAttributeService genericAttributeService, IDownloadService downloadService, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IOrderTotalCalculationService orderTotalCalculationService, + IRequestCache requestCache) { - this._sciRepository = sciRepository; - this._workContext = workContext; - this._storeContext = storeContext; - this._currencyService = currencyService; - this._productService = productService; - this._localizationService = localizationService; - this._productAttributeParser = productAttributeParser; - this._productAttributeService = productAttributeService; - this._checkoutAttributeService = checkoutAttributeService; - this._checkoutAttributeParser = checkoutAttributeParser; - this._priceFormatter = priceFormatter; - this._customerService = customerService; - this._shoppingCartSettings = shoppingCartSettings; - this._eventPublisher = eventPublisher; - this._permissionService = permissionService; - this._aclService = aclService; - this._storeMappingService = storeMappingService; - this._genericAttributeService = genericAttributeService; - this._downloadService = downloadService; - this._catalogSettings = catalogSettings; - - T = NullLocalizer.Instance; + _sciRepository = sciRepository; + _workContext = workContext; + _storeContext = storeContext; + _currencyService = currencyService; + _productService = productService; + _localizationService = localizationService; + _productAttributeParser = productAttributeParser; + _productAttributeService = productAttributeService; + _checkoutAttributeService = checkoutAttributeService; + _checkoutAttributeParser = checkoutAttributeParser; + _priceFormatter = priceFormatter; + _customerService = customerService; + _shoppingCartSettings = shoppingCartSettings; + _eventPublisher = eventPublisher; + _permissionService = permissionService; + _aclService = aclService; + _storeMappingService = storeMappingService; + _genericAttributeService = genericAttributeService; + _downloadService = downloadService; + _catalogSettings = catalogSettings; + _orderTotalCalculationService = orderTotalCalculationService; + _requestCache = requestCache; + + T = NullLocalizer.Instance; } public Localizer T { get; set; } - #endregion + public virtual int CountItems(Customer customer, ShoppingCartType cartType, int? storeId = null) + { + Guard.NotNull(customer, nameof(customer)); + + var query = _sciRepository.Table.Where(x => x.ParentItemId == null && x.CustomerId == customer.Id && x.ShoppingCartTypeId == (int)cartType); + if (storeId.GetValueOrDefault() > 0) + { + query = query.Where(x => x.StoreId == storeId.Value); + } + + return query.Sum(x => (int?)x.Quantity) ?? 0; + } + + public virtual List GetCartItems(Customer customer, ShoppingCartType cartType, int? storeId = null) + { + Guard.NotNull(customer, nameof(customer)); + + var cacheKey = CARTITEMS_KEY.FormatInvariant(customer.Id, (int)cartType, storeId.GetValueOrDefault()); + var result = _requestCache.Get(cacheKey, () => + { + var query = _sciRepository.Table + .Expand(x => x.Product.ProductVariantAttributes) + .Where(x => x.CustomerId == customer.Id && x.ShoppingCartTypeId == (int)cartType); + + if (storeId.HasValue) + { + query = query.Where(x => x.StoreId == storeId.Value); + } + + var items = query.OrderByDescending(x => x.Id).ToList(); + + // Perf: Prefetch (load) all attribute values in any of the attribute definitions across all cart items (including any bundle part) + _productAttributeParser.PrefetchProductVariantAttributes(items.Select(x => x.AttributesXml)); + + return OrganizeCartItems(items); + }); + + return result; + } + + protected virtual List OrganizeCartItems(IEnumerable cart) + { + var result = new List(); + + if (cart == null || !cart.Any()) + return result; + + var parentItems = cart.Where(x => x.ParentItemId == null).ToArray(); + + foreach (var parent in parentItems) + { + var parentItem = new OrganizedShoppingCartItem(parent); + + var children = cart.Where(x => x.ParentItemId != null + && x.ParentItemId == parent.Id + && x.Id != parent.Id + && x.ShoppingCartTypeId == parent.ShoppingCartTypeId + && x.Product.CanBeBundleItem()) + .ToArray(); + + foreach (var child in children) + { + var childItem = new OrganizedShoppingCartItem(child); + + if (child.AttributesXml.HasValue() && parent.Product != null && parent.Product.BundlePerItemPricing && child.AttributesXml != null && child.BundleItem != null) + { + child.Product.MergeWithCombination(child.AttributesXml, _productAttributeParser); + + var attrValues = _productAttributeParser.ParseProductVariantAttributeValues(child.AttributesXml); + + if (attrValues != null && attrValues.Any()) + { + childItem.BundleItemData.AdditionalCharge = decimal.Zero; + foreach (var v in attrValues) + { + childItem.BundleItemData.AdditionalCharge += v.PriceAdjustment; + } + } + } + + parentItem.ChildItems.Add(childItem); + } - #region Methods + result.Add(parentItem); + } - /// - /// Delete shopping cart item - /// - /// Shopping cart item - /// A value indicating whether to reset checkout data - /// A value indicating whether to ensure that only active checkout attributes are attached to the current customer - /// A value indicating whether to delete child cart items - public virtual void DeleteShoppingCartItem(ShoppingCartItem shoppingCartItem, bool resetCheckoutData = true, - bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true) + return result; + } + + public virtual void DeleteShoppingCartItem( + ShoppingCartItem shoppingCartItem, + bool resetCheckoutData = true, + bool ensureOnlyActiveCheckoutAttributes = false, + bool deleteChildCartItems = true) { - if (shoppingCartItem == null) - throw new ArgumentNullException("shoppingCartItem"); + Guard.NotNull(shoppingCartItem, nameof(shoppingCartItem)); var customer = shoppingCartItem.Customer; var storeId = shoppingCartItem.StoreId; int cartItemId = shoppingCartItem.Id; - //reset checkout data - if (resetCheckoutData) + // reset checkout data + if (resetCheckoutData && customer != null) { _customerService.ResetCheckoutData(shoppingCartItem.Customer, shoppingCartItem.StoreId); } - //delete item + // delete item _sciRepository.Delete(shoppingCartItem); - //validate checkout attributes - if (ensureOnlyActiveCheckoutAttributes && - //only for shopping cart items (ignore wishlist) - shoppingCartItem.ShoppingCartType == ShoppingCartType.ShoppingCart) + // cache busting + _requestCache.RemoveByPattern(CARTITEMS_PATTERN_KEY); + + // validate checkout attributes + if (ensureOnlyActiveCheckoutAttributes && shoppingCartItem.ShoppingCartType == ShoppingCartType.ShoppingCart && customer != null) { - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId); + var cart = GetCartItems(customer, ShoppingCartType.ShoppingCart, storeId); var checkoutAttributesXml = customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); checkoutAttributesXml = _checkoutAttributeParser.EnsureOnlyActiveAttributes(checkoutAttributesXml, cart); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CheckoutAttributes, checkoutAttributesXml); } - //event notification - _eventPublisher.EntityDeleted(shoppingCartItem); - // 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) .ToList(); foreach (var cartItem in childCartItems) + { DeleteShoppingCartItem(cartItem, resetCheckoutData, ensureOnlyActiveCheckoutAttributes, false); + } } } - public virtual void DeleteShoppingCartItem(int shoppingCartItemId, bool resetCheckoutData = true, - bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true) + public virtual void DeleteShoppingCartItem( + int shoppingCartItemId, + bool resetCheckoutData = true, + bool ensureOnlyActiveCheckoutAttributes = false, + bool deleteChildCartItems = true) { if (shoppingCartItemId != 0) { var shoppingCartItem = _sciRepository.GetById(shoppingCartItemId); - DeleteShoppingCartItem(shoppingCartItem, resetCheckoutData, ensureOnlyActiveCheckoutAttributes, deleteChildCartItems); } } - /// - /// Deletes expired shopping cart items - /// - /// Older than date and time - /// Number of deleted items - 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); } @@ -215,26 +282,17 @@ from sci in _sciRepository.Table return cartItems.Count; } - /// - /// Validates required products (products which require other variant to be added to the cart) - /// - /// Customer - /// Shopping cart type - /// Product - /// Store identifier - /// Automatically add required products if enabled - /// Warnings - public virtual IList GetRequiredProductWarnings(Customer customer, - ShoppingCartType shoppingCartType, Product product, - int storeId, bool automaticallyAddRequiredProductsIfEnabled) + public virtual IList GetRequiredProductWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + int storeId, + bool automaticallyAddRequiredProductsIfEnabled) { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(product, nameof(product)); - var cart = customer.GetCartItems(shoppingCartType, storeId); + var cart = GetCartItems(customer, shoppingCartType, storeId); var warnings = new List(); if (product.RequireOtherProducts) @@ -249,8 +307,9 @@ public virtual IList GetRequiredProductWarnings(Customer customer, foreach (var rp in requiredProducts) { - //ensure that product is in the cart + // ensure that product is in the cart bool alreadyInTheCart = false; + foreach (var sci in cart) { if (sci.Item.ProductId == rp.Id) @@ -259,23 +318,24 @@ public virtual IList GetRequiredProductWarnings(Customer customer, break; } } - //not in the cart + + // not in the cart if (!alreadyInTheCart) { if (product.AutomaticallyAddRequiredProducts) { - //add to cart (if possible) + // add to cart (if possible) if (automaticallyAddRequiredProductsIfEnabled) { - //pass 'false' for 'automaticallyAddRequiredProducsIfEnabled' to prevent circular references + // pass 'false' for 'automaticallyAddRequiredProducsIfEnabled' to prevent circular references var addToCartWarnings = AddToCart(customer, rp, shoppingCartType, storeId, "", decimal.Zero, 1, false, null); if (addToCartWarnings.Count > 0) { - //a product wasn't atomatically added for some reasons + // a product wasn't atomatically added for some reasons - //don't display specific errors from 'addToCartWarnings' variable - //display only generic error + // don't display specific errors from 'addToCartWarnings' variable + // display only generic error warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } @@ -295,28 +355,20 @@ public virtual IList GetRequiredProductWarnings(Customer customer, return warnings; } - /// - /// Validates a product for standard properties - /// - /// Customer - /// Shopping cart type - /// Product - /// Selected attributes - /// Customer entered price - /// Quantity - /// Warnings - public virtual IList GetStandardWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, string selectedAttributes, decimal customerEnteredPrice, int quantity) + public virtual IList GetStandardWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + string selectedAttributes, + decimal customerEnteredPrice, + int quantity) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(product, nameof(product)); - if (product == null) - throw new ArgumentNullException("product"); - - var warnings = new List(); + var warnings = new List(); - //deleted? + // deleted? if (product.Deleted) { warnings.Add(T("ShoppingCart.ProductDeleted")); @@ -336,43 +388,43 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart warnings.Add(T("ShoppingCart.Bundle.NoCustomerEnteredPrice")); } - //published? + // published? if (!product.Published) { warnings.Add(T("ShoppingCart.ProductUnpublished")); } - //ACL + // ACL if (!_aclService.Authorize(product, customer)) { warnings.Add(T("ShoppingCart.ProductUnpublished")); } - //Store mapping + // Store mapping if (!_storeMappingService.Authorize(product, _storeContext.CurrentStore.Id)) { warnings.Add(T("ShoppingCart.ProductUnpublished")); } - //disabled "add to cart" button + // disabled "add to cart" button if (shoppingCartType == ShoppingCartType.ShoppingCart && product.DisableBuyButton) { warnings.Add(T("ShoppingCart.BuyingDisabled")); } - //disabled "add to wishlist" button + // disabled "add to wishlist" button if (shoppingCartType == ShoppingCartType.Wishlist && product.DisableWishlistButton) { warnings.Add(T("ShoppingCart.WishlistDisabled")); } - //call for price + // call for price if (shoppingCartType == ShoppingCartType.ShoppingCart && product.CallForPrice) { warnings.Add(T("Products.CallForPrice")); } - //customer entered price + // customer entered price if (product.CustomerEntersPrice) { if (customerEnteredPrice < product.MinimumCustomerEnteredPrice || @@ -388,7 +440,7 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart } } - //quantity validation + // quantity validation var hasQtyWarnings = false; if (quantity < product.OrderMinimumQuantity) { @@ -456,7 +508,7 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart } } - //availability dates + // availability dates var availableStartDateError = false; if (product.AvailableStartDateTimeUtc.HasValue) { @@ -468,6 +520,7 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart availableStartDateError = true; } } + if (product.AvailableEndDateTimeUtc.HasValue && !availableStartDateError) { DateTime now = DateTime.UtcNow; @@ -477,6 +530,7 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart warnings.Add(T("ShoppingCart.NotAvailable")); } } + return warnings; } @@ -489,7 +543,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings( ProductBundleItem bundleItem = null, ProductVariantAttributeCombination combination = null) { - Guard.ArgumentNotNull(() => product); + Guard.NotNull(product, nameof(product)); var warnings = new List(); @@ -499,7 +553,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings( if (bundleItem != null && !bundleItem.BundleProduct.BundlePerItemPricing) return warnings; // customer cannot select anything... selectedAttribute is always empty - //selected attributes + // selected attributes var pva1Collection = _productAttributeParser.ParseProductVariantAttributes(selectedAttributes); foreach (var pva1 in pva1Collection) { @@ -512,14 +566,14 @@ public virtual IList GetShoppingCartItemAttributeWarnings( } } - //existing product attributes + // existing product attributes var pva2Collection = product.ProductVariantAttributes; foreach (var pva2 in pva2Collection) { if (pva2.IsRequired) { bool found = false; - //selected product attributes + // selected product attributes foreach (var pva1 in pva1Collection) { if (pva1.Id == pva2.Id) @@ -572,8 +626,19 @@ public virtual IList GetShoppingCartItemAttributeWarnings( var linkedProduct = _productService.GetProductById(pvaValue.LinkedProductId); if (linkedProduct != null) { - var linkageWarnings = GetShoppingCartItemWarnings(customer, shoppingCartType, linkedProduct, _storeContext.CurrentStore.Id, - "", decimal.Zero, quantity * pvaValue.Quantity, false, true, true, true, true); + var linkageWarnings = GetShoppingCartItemWarnings( + customer, + shoppingCartType, + linkedProduct, + _storeContext.CurrentStore.Id, + "", + decimal.Zero, + quantity * pvaValue.Quantity, + false, + true, + true, + true, + true); foreach (var linkageWarning in linkageWarnings) { @@ -595,31 +660,25 @@ public virtual IList GetShoppingCartItemAttributeWarnings( return warnings; } - /// - /// Validates if all required attributes are selected - /// - /// Selected attributes - /// Product - /// bool public virtual bool AreAllAttributesForCombinationSelected(string selectedAttributes, Product product) { - Guard.ArgumentNotNull(() => product); + Guard.NotNull(product, nameof(product)); var hasAttributeCombinations = _sciRepository.Context.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Any(); if (!hasAttributeCombinations) return true; - //selected attributes + // selected attributes var pva1Collection = _productAttributeParser.ParseProductVariantAttributes(selectedAttributes); - //existing product attributes + // existing product attributes var pva2Collection = product.ProductVariantAttributes; foreach (var pva2 in pva2Collection) { if (pva2.IsRequired) { bool found = false; - //selected product attributes + // selected product attributes foreach (var pva1 in pva1Collection) { if (pva1.Id == pva2.Id) @@ -650,22 +709,13 @@ public virtual bool AreAllAttributesForCombinationSelected(string selectedAttrib return true; } - /// - /// Validates shopping cart item (gift card) - /// - /// Shopping cart type - /// Product - /// Selected attributes - /// Warnings - public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartType shoppingCartType, - Product product, string selectedAttributes) + public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartType shoppingCartType, Product product, string selectedAttributes) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); - var warnings = new List(); + var warnings = new List(); - //gift cards + // gift cards if (product.IsGiftCard) { string giftCardRecipientName = string.Empty; @@ -674,8 +724,13 @@ public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartTyp string giftCardSenderEmail = string.Empty; string giftCardMessage = string.Empty; - _productAttributeParser.GetGiftCardAttribute(selectedAttributes, - out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + _productAttributeParser.GetGiftCardAttribute( + selectedAttributes, + out giftCardRecipientName, + out giftCardRecipientEmail, + out giftCardSenderName, + out giftCardSenderEmail, + out giftCardMessage); if (String.IsNullOrEmpty(giftCardRecipientName)) { @@ -684,7 +739,7 @@ public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartTyp if (product.GiftCardType == GiftCardType.Virtual) { - //validate for virtual gift cards only + // validate for virtual gift cards only if (String.IsNullOrEmpty(giftCardRecipientEmail) || !giftCardRecipientEmail.IsEmail()) { warnings.Add(T("ShoppingCart.RecipientEmailError")); @@ -698,7 +753,7 @@ public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartTyp if (product.GiftCardType == GiftCardType.Virtual) { - //validate for virtual gift cards only + // validate for virtual gift cards only if (String.IsNullOrEmpty(giftCardSenderEmail) || !giftCardSenderEmail.IsEmail()) { warnings.Add(T("ShoppingCart.SenderEmailError")); @@ -709,12 +764,6 @@ public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartTyp return warnings; } - /// - /// Validates bundle items - /// - /// Shopping cart type - /// Product bundle item - /// Warnings public virtual IList GetBundleItemWarnings(ProductBundleItem bundleItem) { var warnings = new List(); @@ -746,6 +795,7 @@ public virtual IList GetBundleItemWarnings(ProductBundleItem bundleItem) return warnings; } + public virtual IList GetBundleItemWarnings(IList cartItems) { var warnings = new List(); @@ -760,51 +810,40 @@ public virtual IList GetBundleItemWarnings(IList - /// Validates shopping cart item - /// - /// Customer - /// Shopping cart type - /// Product - /// Store identifier - /// Selected attributes - /// Customer entered price - /// Quantity - /// Automatically add required products if enabled - /// A value indicating whether we should validate a product for standard properties - /// A value indicating whether we should validate product attributes - /// A value indicating whether we should validate gift card properties - /// A value indicating whether we should validate required products (products which require other products to be added to the cart) - /// A value indicating whether we should validate bundle and bundle items - /// Product bundle item if bundles should be validated - /// Child cart items to validate bundle items - /// Warnings - public virtual IList GetShoppingCartItemWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, int storeId, - string selectedAttributes, decimal customerEnteredPrice, - int quantity, bool automaticallyAddRequiredProductsIfEnabled, - bool getStandardWarnings = true, bool getAttributesWarnings = true, - bool getGiftCardWarnings = true, bool getRequiredProductWarnings = true, - bool getBundleWarnings = true, ProductBundleItem bundleItem = null, IList childItems = null) + public virtual IList GetShoppingCartItemWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + int storeId, + string selectedAttributes, + decimal customerEnteredPrice, + int quantity, + bool automaticallyAddRequiredProductsIfEnabled, + bool getStandardWarnings = true, + bool getAttributesWarnings = true, + bool getGiftCardWarnings = true, + bool getRequiredProductWarnings = true, + bool getBundleWarnings = true, + ProductBundleItem bundleItem = null, + IList childItems = null) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); - var warnings = new List(); + var warnings = new List(); - //standard properties + // standard properties if (getStandardWarnings) warnings.AddRange(GetStandardWarnings(customer, shoppingCartType, product, selectedAttributes, customerEnteredPrice, quantity)); - //selected attributes + // selected attributes if (getAttributesWarnings) warnings.AddRange(GetShoppingCartItemAttributeWarnings(customer, shoppingCartType, product, selectedAttributes, quantity, bundleItem)); - //gift cards + // gift cards if (getGiftCardWarnings) warnings.AddRange(GetShoppingCartItemGiftCardWarnings(shoppingCartType, product, selectedAttributes)); - //required products + // required products if (getRequiredProductWarnings) warnings.AddRange(GetRequiredProductWarnings(customer, shoppingCartType, product, storeId, automaticallyAddRequiredProductsIfEnabled)); @@ -821,15 +860,7 @@ public virtual IList GetShoppingCartItemWarnings(Customer customer, Shop return warnings; } - /// - /// Validates whether this shopping cart is valid - /// - /// Shopping cart - /// Checkout attributes - /// A value indicating whether to validate checkout attributes - /// Warnings - public virtual IList GetShoppingCartWarnings(IList shoppingCart, - string checkoutAttributes, bool validateCheckoutAttributes) + public virtual IList GetShoppingCartWarnings(IList shoppingCart, string checkoutAttributes, bool validateCheckoutAttributes) { var warnings = new List(); @@ -851,13 +882,13 @@ public virtual IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(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))); + } } } } @@ -923,38 +960,27 @@ public virtual IList GetShoppingCartWarnings(IList - /// Finds a shopping cart item in the cart - /// - /// Shopping cart - /// Shopping cart type - /// Product - /// Selected attributes - /// Price entered by a customer - /// Found shopping cart item - public virtual OrganizedShoppingCartItem FindShoppingCartItemInTheCart(IList shoppingCart, + public virtual OrganizedShoppingCartItem FindShoppingCartItemInTheCart( + IList shoppingCart, ShoppingCartType shoppingCartType, Product product, string selectedAttributes = "", decimal customerEnteredPrice = decimal.Zero) { - if (shoppingCart == null) - throw new ArgumentNullException("shoppingCart"); - - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(shoppingCart, nameof(shoppingCart)); + Guard.NotNull(product, nameof(product)); if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) - return null; // too complex + return null; // too complex - foreach (var sci in shoppingCart.Where(a => a.Item.ShoppingCartType == shoppingCartType && a.Item.ParentItemId == null)) + foreach (var sci in shoppingCart.Where(a => a.Item.ShoppingCartType == shoppingCartType && a.Item.ParentItemId == null).ToArray()) { if (sci.Item.ProductId == product.Id && sci.Item.Product.ProductTypeId == product.ProductTypeId) { - //attributes + // attributes bool attributesEqual = _productAttributeParser.AreProductAttributesEqual(sci.Item.AttributesXml, selectedAttributes); - //gift cards + // gift cards var giftCardInfoSame = true; if (sci.Item.Product.IsGiftCard) { @@ -964,8 +990,13 @@ public virtual OrganizedShoppingCartItem FindShoppingCartItemInTheCart(IList - /// Add product to cart - /// - /// The customer - /// The product - /// Cart type - /// Store identifier - /// Selected attributes - /// Price entered by customer - /// Quantity - /// Whether to add required products - /// Add to cart context - /// List with warnings - public virtual List AddToCart(Customer customer, Product product, ShoppingCartType cartType, int storeId, string selectedAttributes, - decimal customerEnteredPrice, int quantity, bool automaticallyAddRequiredProductsIfEnabled, AddToCartContext ctx = null) + public virtual List AddToCart( + Customer customer, + Product product, + ShoppingCartType cartType, + int storeId, + string selectedAttributes, + decimal customerEnteredPrice, + int quantity, + bool automaticallyAddRequiredProductsIfEnabled, + AddToCartContext ctx = null) { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(product, nameof(product)); var warnings = new List(); var bundleItem = (ctx == null ? null : ctx.BundleItem); @@ -1053,10 +1082,10 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi // return warnings; //} - //reset checkout info + // reset checkout info _customerService.ResetCheckoutData(customer, storeId); - var cart = customer.GetCartItems(cartType, storeId); + var cart = GetCartItems(customer, cartType, storeId); OrganizedShoppingCartItem existingCartItem = null; if (bundleItem == null) @@ -1066,7 +1095,7 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi if (existingCartItem != null) { - //update existing shopping cart item + // update existing shopping cart item int newQuantity = existingCartItem.Item.Quantity + quantity; warnings.AddRange( @@ -1080,14 +1109,11 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi existingCartItem.Item.Quantity = newQuantity; existingCartItem.Item.UpdatedOnUtc = DateTime.UtcNow; _customerService.UpdateCustomer(customer); - - //event notification - _eventPublisher.EntityUpdated(existingCartItem.Item); } } else { - //new shopping cart item + // new shopping cart item warnings.AddRange( GetShoppingCartItemWarnings(customer, cartType, product, storeId, selectedAttributes, customerEnteredPrice, quantity, automaticallyAddRequiredProductsIfEnabled, bundleItem: bundleItem) @@ -1095,7 +1121,7 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi if (warnings.Count == 0) { - //maximum items validation + // maximum items validation if (cartType == ShoppingCartType.ShoppingCart && cart.Count >= _shoppingCartSettings.MaximumShoppingCartItems) { warnings.Add(T("ShoppingCart.MaximumShoppingCartItems")); @@ -1116,9 +1142,7 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi AttributesXml = selectedAttributes, CustomerEnteredPrice = customerEnteredPrice, Quantity = quantity, - CreatedOnUtc = now, - UpdatedOnUtc = now, - ParentItemId = null //parentItemId + ParentItemId = null }; if (bundleItem != null) @@ -1130,7 +1154,6 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi { customer.ShoppingCartItems.Add(cartItem); _customerService.UpdateCustomer(customer); - _eventPublisher.EntityInserted(cartItem); } else { @@ -1147,37 +1170,45 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi } } + _requestCache.RemoveByPattern(CARTITEMS_PATTERN_KEY); + return warnings; } - /// - /// Add product to cart - /// - /// Add to cart context public virtual void AddToCart(AddToCartContext ctx) { var customer = ctx.Customer ?? _workContext.CurrentCustomer; - int storeId = ctx.StoreId ?? _storeContext.CurrentStore.Id; - var cart = customer.GetCartItems(ctx.CartType, storeId); + var storeId = ctx.StoreId ?? _storeContext.CurrentStore.Id; _customerService.ResetCheckoutData(customer, storeId); - if (ctx.AttributeForm != null) + if (ctx.VariantQuery != null) { var attributes = _productAttributeService.GetProductVariantAttributesByProductId(ctx.Product.Id); - ctx.Attributes = ctx.AttributeForm.CreateSelectedAttributesXml(ctx.Product.Id, attributes, _productAttributeParser, _localizationService, - _downloadService, _catalogSettings, null, ctx.Warnings, true, ctx.BundleItemId); + ctx.AttributesXml = ctx.VariantQuery.CreateSelectedAttributesXml(ctx.Product.Id, ctx.BundleItemId, attributes, _productAttributeParser, + _localizationService, _downloadService, _catalogSettings, null, ctx.Warnings); - if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.Attributes.HasValue()) + if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.AttributesXml.HasValue()) + { ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); + } if (ctx.Product.IsGiftCard) - ctx.Attributes = ctx.AttributeForm.AddGiftCardAttribute(ctx.Attributes, ctx.Product.Id, _productAttributeParser, ctx.BundleItemId); + { + //ctx.Attributes = ctx.AttributeForm.AddGiftCardAttribute(ctx.Attributes, ctx.Product.Id, _productAttributeParser, ctx.BundleItemId); + ctx.AttributesXml = _productAttributeParser.AddGiftCardAttribute( + ctx.AttributesXml, + ctx.VariantQuery.GetGiftCardValue(ctx.Product.Id, ctx.BundleItemId, "RecipientName"), + ctx.VariantQuery.GetGiftCardValue(ctx.Product.Id, ctx.BundleItemId, "RecipientEmail"), + ctx.VariantQuery.GetGiftCardValue(ctx.Product.Id, ctx.BundleItemId, "SenderName"), + ctx.VariantQuery.GetGiftCardValue(ctx.Product.Id, ctx.BundleItemId, "SenderEmail"), + ctx.VariantQuery.GetGiftCardValue(ctx.Product.Id, ctx.BundleItemId, "Message")); + } } ctx.Warnings.AddRange( - AddToCart(_workContext.CurrentCustomer, ctx.Product, ctx.CartType, storeId, ctx.Attributes, 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) @@ -1192,7 +1223,7 @@ public virtual void AddToCart(AddToCartContext ctx) ChildItems = ctx.ChildItems, Product = bundleItem.Product, Customer = customer, - AttributeForm = ctx.AttributeForm, + VariantQuery = ctx.VariantQuery, CartType = ctx.CartType, Quantity = bundleItem.Quantity, AddRequiredProducts = ctx.AddRequiredProducts, @@ -1213,10 +1244,6 @@ public virtual void AddToCart(AddToCartContext ctx) } } - /// - /// Stores the shopping card items in the database - /// - /// Add to cart context public virtual void AddToCartStoring(AddToCartContext ctx) { if (ctx.Warnings.Count == 0 && ctx.Item != null) @@ -1225,7 +1252,6 @@ public virtual void AddToCartStoring(AddToCartContext ctx) customer.ShoppingCartItems.Add(ctx.Item); _customerService.UpdateCustomer(customer); - _eventPublisher.EntityInserted(ctx.Item); foreach (var childItem in ctx.ChildItems) { @@ -1233,79 +1259,60 @@ public virtual void AddToCartStoring(AddToCartContext ctx) customer.ShoppingCartItems.Add(childItem); _customerService.UpdateCustomer(customer); - _eventPublisher.EntityInserted(childItem); } } } - /// - /// Updates the shopping cart item - /// - /// Customer - /// Shopping cart item identifier - /// New shopping cart item quantity - /// A value indicating whether to reset checkout data - /// Warnings public virtual IList UpdateShoppingCartItem(Customer customer, int shoppingCartItemId, int newQuantity, bool resetCheckoutData) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); - var warnings = new List(); + var warnings = new List(); var shoppingCartItem = customer.ShoppingCartItems.FirstOrDefault(sci => sci.Id == shoppingCartItemId && sci.ParentItemId == null); if (shoppingCartItem != null) { if (resetCheckoutData) { - //reset checkout data + // reset checkout data _customerService.ResetCheckoutData(customer, shoppingCartItem.StoreId); } if (newQuantity > 0) { - //check warnings + // check warnings warnings.AddRange(GetShoppingCartItemWarnings(customer, shoppingCartItem.ShoppingCartType, shoppingCartItem.Product, shoppingCartItem.StoreId, shoppingCartItem.AttributesXml, shoppingCartItem.CustomerEnteredPrice, newQuantity, false)); if (warnings.Count == 0) { - //if everything is OK, then update a shopping cart item + // if everything is OK, then update a shopping cart item shoppingCartItem.Quantity = newQuantity; shoppingCartItem.UpdatedOnUtc = DateTime.UtcNow; _customerService.UpdateCustomer(customer); - - //event notification - _eventPublisher.EntityUpdated(shoppingCartItem); } } else { - //delete a shopping cart item + // delete a shopping cart item DeleteShoppingCartItem(shoppingCartItem, resetCheckoutData, true); } } - return warnings; + _requestCache.RemoveByPattern(CARTITEMS_PATTERN_KEY); + + return warnings; } - /// - /// Migrate shopping cart - /// - /// From customer - /// To customer public virtual void MigrateShoppingCart(Customer fromCustomer, Customer toCustomer) { - if (fromCustomer == null) - throw new ArgumentNullException("fromCustomer"); + Guard.NotNull(fromCustomer, nameof(fromCustomer)); + Guard.NotNull(toCustomer, nameof(toCustomer)); - if (toCustomer == null) - throw new ArgumentNullException("toCustomer"); - - if (fromCustomer.Id == toCustomer.Id) + if (fromCustomer.Id == toCustomer.Id) return; int storeId = 0; - var cartItems = fromCustomer.ShoppingCartItems.ToList().Organize().ToList(); + var cartItems = OrganizeCartItems(fromCustomer.ShoppingCartItems); if (cartItems.Count <= 0) return; @@ -1325,23 +1332,11 @@ public virtual void MigrateShoppingCart(Customer fromCustomer, Customer toCustom DeleteShoppingCartItem(cartItem.Item); } } - - /// - /// Copies a shopping cart item. - /// - /// Shopping cart item - /// The customer - /// Shopping cart type - /// Store Id - /// Add required products if enabled - /// List with add-to-cart warnings. + public virtual IList Copy(OrganizedShoppingCartItem sci, Customer customer, ShoppingCartType cartType, int storeId, bool addRequiredProductsIfEnabled) { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (sci == null) - throw new ArgumentNullException("item"); + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(sci, nameof(sci)); var addToCartContext = new AddToCartContext { @@ -1364,9 +1359,50 @@ public virtual IList Copy(OrganizedShoppingCartItem sci, Customer custom AddToCartStoring(addToCartContext); + _requestCache.RemoveByPattern(CARTITEMS_PATTERN_KEY); + return addToCartContext.Warnings; } - #endregion + public virtual decimal GetCurrentCartSubTotal() + { + var cart = GetCartItems(_workContext.CurrentCustomer, ShoppingCartType.ShoppingCart, _storeContext.CurrentStore.Id); + return GetCurrentCartSubTotal(cart); + } + + public virtual decimal GetCurrentCartSubTotal(IList cart) + { + decimal subtotal = 0; + + if (cart.Count > 0) + { + decimal subtotalBase = decimal.Zero; + decimal orderSubTotalDiscountAmountBase = decimal.Zero; + Discount orderSubTotalAppliedDiscount = null; + decimal subTotalWithoutDiscountBase = decimal.Zero; + decimal subTotalWithDiscountBase = decimal.Zero; + + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, + out orderSubTotalDiscountAmountBase, + out orderSubTotalAppliedDiscount, + out subTotalWithoutDiscountBase, + out subTotalWithDiscountBase); + + subtotalBase = subTotalWithoutDiscountBase; + subtotal = _currencyService.ConvertFromPrimaryStoreCurrency(subtotalBase, _workContext.WorkingCurrency); + } + + return subtotal; + } + + public virtual string GetFormattedCurrentCartSubTotal() + { + return _priceFormatter.FormatPrice(GetCurrentCartSubTotal()); + } + + public virtual string GetFormattedCurrentCartSubTotal(IList cart) + { + return _priceFormatter.FormatPrice(GetCurrentCartSubTotal(cart)); + } } } diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs new file mode 100644 index 0000000000..65155d6db7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Discounts; + +namespace SmartStore.Services.Orders +{ + public class ShoppingCartTotal + { + public ShoppingCartTotal(decimal? totalAmount) + { + TotalAmount = totalAmount; + ConvertedFromPrimaryStoreCurrency = new ConvertedAmounts(); + } + + public static implicit operator decimal?(ShoppingCartTotal obj) + { + return obj.TotalAmount; + } + + public static implicit operator ShoppingCartTotal(decimal? obj) + { + return new ShoppingCartTotal(obj); + } + + /// + /// Total amount of the shopping cart. null if the cart total couldn't be calculated now. + /// + public decimal? TotalAmount { get; private set; } + + /// + /// Rounding amount + /// + public decimal RoundingAmount { get; set; } + + /// + /// Applied discount amount + /// + public decimal DiscountAmount { get; set; } + + /// + /// Applied discount + /// + public Discount AppliedDiscount { get; set; } + + /// + /// Applied gift cards + /// + public List AppliedGiftCards { get; set; } + + /// + /// Reward points to redeem + /// + public int RedeemedRewardPoints { get; set; } + + /// + /// Reward points amount to redeem (in primary store currency) + /// + public decimal RedeemedRewardPointsAmount { get; set; } + + /// + /// Credit balance. + /// + public decimal CreditBalance { get; set; } + + public ConvertedAmounts ConvertedFromPrimaryStoreCurrency { get; set; } + + public override string ToString() + { + return (TotalAmount ?? decimal.Zero).FormatInvariant(); + } + + public class ConvertedAmounts + { + /// + /// Converted total amount of the shopping cart. null if the cart total couldn't be calculated now. + /// + public decimal? TotalAmount { get; set; } + + /// + /// Converted rounding amount + /// + public decimal RoundingAmount { get; set; } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Payments/CapturePaymentHook.cs b/src/Libraries/SmartStore.Services/Payments/CapturePaymentHook.cs new file mode 100644 index 0000000000..1b30b02387 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Payments/CapturePaymentHook.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Services.Orders; + +namespace SmartStore.Services.Payments +{ + public class CapturePaymentHook : DbSaveHook + { + private readonly Lazy _services; + private readonly Lazy _orderProcessingService; + private readonly HashSet _toCapture = new HashSet(); + + public CapturePaymentHook( + Lazy services, + Lazy orderProcessingService) + { + _services = services; + _orderProcessingService = orderProcessingService; + } + + private bool IsStatusPropertyModifiedTo(IHookedEntity entry, string propertyName, int statusId) + { + var prop = entry.Entry.Property(propertyName); + + if (prop != null && prop.CurrentValue != null) + { + if (!prop.CurrentValue.Equals(prop.OriginalValue)) + { + return (int)prop.CurrentValue == statusId; + } + } + + return false; + } + + protected override void OnUpdating(Order entity, IHookedEntity entry) + { + if (entry.State == Core.Data.EntityState.Modified) + { + var isShipped = IsStatusPropertyModifiedTo(entry, nameof(entity.ShippingStatusId), (int)ShippingStatus.Shipped); + var isDelivered = IsStatusPropertyModifiedTo(entry, nameof(entity.ShippingStatusId), (int)ShippingStatus.Delivered); + + if (isShipped || isDelivered) + { + var settings = _services.Value.Settings.LoadSetting(entity.StoreId); + if (settings.CapturePaymentReason.HasValue) + { + if (isShipped && settings.CapturePaymentReason.Value == CapturePaymentReason.OrderShipped) + { + _toCapture.Add(entity); + } + else if (isDelivered && settings.CapturePaymentReason.Value == CapturePaymentReason.OrderDelivered) + { + _toCapture.Add(entity); + } + } + } + + //if (IsStatusPropertyModifiedTo(entry, nameof(entity.OrderStatusId), (int)OrderStatus.Complete)) + //{ + // That's too late. The payment is already marked as paid and the capture process would never be executed. + //} + } + } + + public override void OnAfterSave(IHookedEntity entry) + { + // Do not remove. + } + + public override void OnAfterSaveCompleted() + { + if (_toCapture.Any()) + { + foreach (var order in _toCapture) + { + if (_orderProcessingService.Value.CanCapture(order)) + { + _orderProcessingService.Value.Capture(order); + } + } + + _toCapture.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs index ced73b9b10..d4fbe4e740 100644 --- a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs @@ -6,20 +6,30 @@ namespace SmartStore.Services.Payments { - /// - /// Payment service interface - /// - public partial interface IPaymentService + /// + /// Payment service interface + /// + public partial interface IPaymentService { - /// - /// Load active payment methods - /// + /// + /// Determines whether a payment method is active\enabled for a shop + /// + bool IsPaymentMethodActive(string systemName, int storeId = 0); + + /// + /// Determines whether a payment method is excluded by a filter + /// + bool IsPaymentMethodFiltered(PaymentFilterRequest filterRequest); + + /// + /// Load active payment methods + /// /// Filter payment methods by customer and apply payment method restrictions; null to load all records /// Filter payment methods by cart amount; null to load all records /// Filter payment methods by store identifier; pass 0 to load all records /// Filter payment methods by payment method types /// Provide a fallback payment method if none is active - /// Payment methods + /// Payment methods IEnumerable> LoadActivePaymentMethods( Customer customer = null, IList cart = null, @@ -28,21 +38,18 @@ IEnumerable> LoadActivePaymentMethods( bool provideFallbackMethod = true); /// - /// Determines whether a payment method is active\enabled for a shop + /// Load payment provider by system name /// - bool IsPaymentMethodActive(string systemName, int storeId = 0); - - /// - /// Load payment provider by system name - /// - /// System name - /// Found payment provider + /// System name + /// true to load only active provider + /// Load records allowed only in specified store; pass 0 to load all records + /// Found payment provider Provider LoadPaymentMethodBySystemName(string systemName, bool onlyWhenActive = false, int storeId = 0); /// /// Load all payment providers /// - /// Load records allows only in specified store; pass 0 to load all records + /// Load records allowed only in specified store; pass 0 to load all records /// Payment providers IEnumerable> LoadAllPaymentMethods(int storeId = 0); @@ -50,8 +57,9 @@ IEnumerable> LoadActivePaymentMethods( /// /// Gets all payment method extra data /// + /// Load records allowed only in specified store; pass 0 to load all records /// List of payment method objects - IList GetAllPaymentMethods(); + IList GetAllPaymentMethods(int storeId = 0); /// /// Gets payment method extra data by system name diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs b/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs index d9cd258259..85504dad98 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs @@ -58,13 +58,13 @@ public static decimal CalculateAdditionalFee(this IPaymentMethod paymentMethod, var result = decimal.Zero; if (usePercentage) { - //percentage - var orderTotalWithoutPaymentFee = orderTotalCalculationService.GetShoppingCartTotal(cart, usePaymentMethodAdditionalFee: false); + // Percentage + decimal? orderTotalWithoutPaymentFee = orderTotalCalculationService.GetShoppingCartTotal(cart, usePaymentMethodAdditionalFee: false); result = (decimal)((((float)orderTotalWithoutPaymentFee) * ((float)fee)) / 100f); } else { - //fixed value + // Fixed value result = fee; } return result; @@ -72,7 +72,7 @@ public static decimal CalculateAdditionalFee(this IPaymentMethod paymentMethod, public static RouteInfo GetConfigurationRoute(this IPaymentMethod method) { - Guard.ArgumentNotNull(() => method); + Guard.NotNull(method, nameof(method)); string action; string controller; @@ -94,7 +94,7 @@ public static RouteInfo GetConfigurationRoute(this IPaymentMethod method) public static RouteInfo GetPaymentInfoRoute(this IPaymentMethod method) { - Guard.ArgumentNotNull(() => method); + Guard.NotNull(method, nameof(method)); string action; string controller; diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 0bce57bb5e..4f6298a3b5 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -5,49 +5,36 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Events; +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 Constants - - private const string PAYMENTMETHOD_ALL_KEY = "SmartStore.paymentmethod.all"; - - #endregion - - #region Fields - private readonly static object _lock = new object(); private static IList _paymentMethodFilterTypes = null; private readonly IRepository _paymentMethodRepository; - private readonly PaymentSettings _paymentSettings; + private readonly IRepository _storeMappingRepository; + private readonly IStoreMappingService _storeMappingService; + private readonly PaymentSettings _paymentSettings; private readonly ShoppingCartSettings _shoppingCartSettings; private readonly IProviderManager _providerManager; private readonly ICommonServices _services; private readonly ITypeFinder _typeFinder; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Payment settings - /// Plugin finder - /// Shopping cart settings - /// Plugin service - public PaymentService( + public PaymentService( IRepository paymentMethodRepository, + IRepository storeMappingRepository, + IStoreMappingService storeMappingService, PaymentSettings paymentSettings, ShoppingCartSettings shoppingCartSettings, IProviderManager providerManager, @@ -55,20 +42,38 @@ public PaymentService( ITypeFinder typeFinder) { _paymentMethodRepository = paymentMethodRepository; - _paymentSettings = paymentSettings; + _storeMappingRepository = storeMappingRepository; + _storeMappingService = storeMappingService; + _paymentSettings = paymentSettings; _shoppingCartSettings = shoppingCartSettings; _providerManager = providerManager; _services = services; _typeFinder = typeFinder; T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + QuerySettings = DbQuerySettings.Default; } public Localizer T { 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) + { + var method = LoadPaymentMethodBySystemName(systemName, true, storeId); + return method != null; + } + + public virtual bool IsPaymentMethodFiltered(PaymentFilterRequest filterRequest) + { + Guard.NotNull(filterRequest, nameof(filterRequest)); + + var allFilters = GetAllPaymentMethodFilters(); + return allFilters.Any(x => x.IsExcluded(filterRequest)); + } /// /// Load active payment methods @@ -86,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, @@ -96,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; }); @@ -122,66 +133,92 @@ 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")); + } + } - /// - /// Determines whether a payment method is active\enabled for a shop - /// - public virtual bool IsPaymentMethodActive(string systemName, int storeId = 0) - { - var method = LoadPaymentMethodBySystemName(systemName, true, storeId); - return method != null; - } + return activeProviders; + } - /// - /// Load payment provider by system name - /// - /// System name - /// Found payment provider public virtual Provider LoadPaymentMethodBySystemName(string systemName, bool onlyWhenActive = false, int storeId = 0) { var provider = _providerManager.GetProvider(systemName, storeId); - if (provider != null && onlyWhenActive && !provider.IsPaymentMethodActive(_paymentSettings)) + if (provider != null) { - return null; + if (onlyWhenActive && !provider.IsPaymentMethodActive(_paymentSettings)) + { + return null; + } + + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + // Return provider if paymentMethod is null! + var paymentMethod = _paymentMethodRepository.TableUntracked.FirstOrDefault(x => x.PaymentMethodSystemName == systemName); + if (paymentMethod != null && !_storeMappingService.Authorize(paymentMethod, storeId)) + { + return null; + } + } } + return provider; } - /// - /// Load all payment providers - /// - /// Load records allows only in specified store; pass 0 to load all records - /// Payment providers public virtual IEnumerable> LoadAllPaymentMethods(int storeId = 0) { - return _providerManager.GetAllProviders(storeId); - } + var providers = _providerManager.GetAllProviders(storeId); + if (providers.Any() && !QuerySettings.IgnoreMultiStore && storeId > 0) + { + var unauthorizedMethods = _paymentMethodRepository.TableUntracked + .Where(x => x.LimitedToStores) + .ToList(); - /// - /// Gets all payment method extra data - /// - /// List of payment method objects - public virtual IList GetAllPaymentMethods() + var unauthorizedMethodNames = unauthorizedMethods + .Where(x => !_storeMappingService.Authorize(x, storeId)) + .Select(x => x.PaymentMethodSystemName) + .ToList(); + + return providers.Where(x => !unauthorizedMethodNames.Contains(x.Metadata.SystemName)); + } + + return providers; + } + + public virtual IList GetAllPaymentMethods(int storeId = 0) { - var methods = _paymentMethodRepository.TableUntracked.ToList(); + var query = _paymentMethodRepository.TableUntracked; + + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + query = + from x in query + join sm in _storeMappingRepository.TableUntracked + on new { c1 = x.Id, c2 = "PaymentMethod" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into m_sm + from sm in m_sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == sm.StoreId + select x; + + query = + from x in query + group x by x.Id into mGroup + orderby mGroup.Key + select mGroup.FirstOrDefault(); + } + + var methods = query.ToList(); return methods; } @@ -196,6 +233,7 @@ public virtual PaymentMethod GetPaymentMethodBySystemName(string systemName) { return _paymentMethodRepository.Table.FirstOrDefault(x => x.PaymentMethodSystemName == systemName); } + return null; } @@ -209,8 +247,6 @@ public virtual void InsertPaymentMethod(PaymentMethod paymentMethod) throw new ArgumentNullException("paymentMethod"); _paymentMethodRepository.Insert(paymentMethod); - - _services.EventPublisher.EntityInserted(paymentMethod); } /// @@ -223,8 +259,6 @@ public virtual void UpdatePaymentMethod(PaymentMethod paymentMethod) throw new ArgumentNullException("paymentMethod"); _paymentMethodRepository.Update(paymentMethod); - - _services.EventPublisher.EntityUpdated(paymentMethod); } /// @@ -237,8 +271,6 @@ public virtual void DeletePaymentMethod(PaymentMethod paymentMethod) throw new ArgumentNullException("paymentMethod"); _paymentMethodRepository.Delete(paymentMethod); - - _services.EventPublisher.EntityDeleted(paymentMethod); } @@ -359,10 +391,7 @@ public virtual decimal GetAdditionalHandlingFee(IList var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); var paymentMethodAdditionalFee = (paymentMethod != null ? paymentMethod.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); - if (_shoppingCartSettings.RoundPricesDuringCalculation) - { - paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); - } + paymentMethodAdditionalFee = paymentMethodAdditionalFee.RoundIfEnabledFor(_services.WorkContext.WorkingCurrency); return paymentMethodAdditionalFee; } @@ -636,8 +665,7 @@ public virtual IList GetAllPaymentMethodFilters() { if (_paymentMethodFilterTypes == null) { - _paymentMethodFilterTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) - .ToList(); + _paymentMethodFilterTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true).ToList(); } } } diff --git a/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs b/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs index a3ba77cb5b..334a261ff0 100644 --- a/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs +++ b/src/Libraries/SmartStore.Services/Payments/ProcessPaymentRequest.cs @@ -15,8 +15,8 @@ public ProcessPaymentRequest() { CustomProperties = new Dictionary(); IsMultiOrder = false; - ShoppingCartItems = new List(); - } + ShoppingCartItemIds = new List(); + } /// /// Gets or sets a store identifier @@ -58,6 +58,8 @@ public ProcessPaymentRequest() /// public Dictionary CustomProperties { get; set; } + public IList ShoppingCartItemIds { get; set; } + #region Payment method specific properties /// @@ -152,12 +154,11 @@ public ProcessPaymentRequest() /// public int RecurringTotalCycles { get; set; } - public IList ShoppingCartItems { get; set; } - #endregion } + [Serializable] public partial class CustomPaymentRequestValue { /// diff --git a/src/Libraries/SmartStore.Services/Pdf/Content/PdfHtmlContent.cs b/src/Libraries/SmartStore.Services/Pdf/Content/PdfHtmlContent.cs index 4a4cd59928..226e742928 100644 --- a/src/Libraries/SmartStore.Services/Pdf/Content/PdfHtmlContent.cs +++ b/src/Libraries/SmartStore.Services/Pdf/Content/PdfHtmlContent.cs @@ -5,20 +5,36 @@ using System.Text; using System.Web; using SmartStore.Core; +using SmartStore.Utilities; namespace SmartStore.Services.Pdf { public class PdfHtmlContent : IPdfContent { + private static readonly Uri _engineBaseUri; + private string _html; private string _originalHtml; private bool _processed; private string _tempFilePath; private PdfContentKind _kind = PdfContentKind.Html; + static PdfHtmlContent() + { + var baseUrl = CommonHelper.GetAppSetting("sm:PdfEngineBaseUrl").TrimSafe().NullEmpty(); + if (baseUrl != null) + { + Uri uri; + if (Uri.TryCreate(baseUrl, UriKind.Absolute, out uri)) + { + _engineBaseUri = uri; + } + } + } + public PdfHtmlContent(string html) { - Guard.ArgumentNotEmpty(() => html); + Guard.NotEmpty(html, nameof(html)); _originalHtml = html; _html = html; @@ -33,14 +49,20 @@ public string Process(string flag) { if (!_processed) { - if (HttpContext.Current != null && HttpContext.Current.Request != null) + if (_engineBaseUri != null) + { + _html = WebHelper.MakeAllUrlsAbsolute(_html, _engineBaseUri.Scheme, _engineBaseUri.Authority); + } + else if (HttpContext.Current?.Request != null) { _html = WebHelper.MakeAllUrlsAbsolute(_html, new HttpRequestWrapper(HttpContext.Current.Request)); - if (!flag.IsCaseInsensitiveEqual("page")) - { - CreateTempFile(); - } } + + if (!flag.IsCaseInsensitiveEqual("page")) + { + CreateTempFile(); + } + _processed = true; } diff --git a/src/Libraries/SmartStore.Services/Pdf/Content/PdfUrlContent.cs b/src/Libraries/SmartStore.Services/Pdf/Content/PdfUrlContent.cs index c456b5e172..ecb19891bd 100644 --- a/src/Libraries/SmartStore.Services/Pdf/Content/PdfUrlContent.cs +++ b/src/Libraries/SmartStore.Services/Pdf/Content/PdfUrlContent.cs @@ -6,16 +6,31 @@ using System.Web; using System.Web.Security; using SmartStore.Core; +using SmartStore.Utilities; namespace SmartStore.Services.Pdf { public class PdfUrlContent : IPdfContent { + private static readonly Uri _engineBaseUri; private readonly string _url; + static PdfUrlContent() + { + var baseUrl = CommonHelper.GetAppSetting("sm:PdfEngineBaseUrl").TrimSafe().NullEmpty(); + if (baseUrl != null) + { + Uri uri; + if (Uri.TryCreate(baseUrl, UriKind.Absolute, out uri)) + { + _engineBaseUri = uri; + } + } + } + public PdfUrlContent(string url) { - Guard.ArgumentNotEmpty(() => url); + Guard.NotEmpty(url, nameof(url)); _url = url; this.SendAuthCookie = false; @@ -59,7 +74,17 @@ public PdfContentKind Kind protected virtual string GetAbsoluteUrl() { - if (HttpContext.Current != null && HttpContext.Current.Request != null) + if (_engineBaseUri != null) + { + var url = _url; + if (url.StartsWith("~")) + { + url = VirtualPathUtility.ToAbsolute(url); + } + + return _engineBaseUri.ToString() + url.TrimStart('/'); + } + else if (HttpContext.Current?.Request != null) { return WebHelper.GetAbsoluteUrl(_url, new HttpRequestWrapper(HttpContext.Current.Request)); } 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/Pdf/WkHtmlToPdfConverter.cs b/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs index 4694512828..72181616b6 100644 --- a/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs +++ b/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs @@ -19,7 +19,7 @@ public WkHtmlToPdfConverter() public byte[] Convert(PdfConvertSettings settings) { - Guard.ArgumentNotNull(() => settings); + Guard.NotNull(settings, nameof(settings)); if (settings.Page == null) { throw Error.InvalidOperation("The 'Page' property of the 'settings' argument cannot be null."); @@ -30,19 +30,21 @@ public byte[] Convert(PdfConvertSettings settings) var converter = CreateWkConverter(settings); var input = settings.Page.Process("page"); - + if (settings.Page.Kind == PdfContentKind.Url) { + Logger.DebugFormat("Generating PDF from URL '{0}'. CustomWkHtmlPageArgs: {1}", input, converter.CustomWkHtmlPageArgs); return converter.GeneratePdfFromFile(input, null); } else { + Logger.DebugFormat("Generating PDF from HTML. CustomWkHtmlPageArgs: {0}", converter.CustomWkHtmlPageArgs); return converter.GeneratePdf(input, null); } } catch (Exception ex) { - Logger.Error("Html to Pdf conversion error", ex); + Logger.Error(ex, "Html to Pdf conversion error"); throw; } finally 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 ad30c025fe..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; @@ -9,32 +10,20 @@ namespace SmartStore.Services.Polls { - /// - /// Poll service - /// public partial class PollService : IPollService { - - #region Fields - private readonly IRepository _pollRepository; private readonly IRepository _pollAnswerRepository; private readonly IRepository _pollVotingRecords; private readonly IRepository _storeMappingRepository; - private readonly IRequestCache _requestCache; private readonly IEventPublisher _eventPublisher; private readonly IStoreContext _storeContext; - #endregion - - #region Ctor - public PollService( IRepository pollRepository, IRepository pollAnswerRepository, IRepository pollVotingRecords, IRepository storeMappingRepository, - IRequestCache requestCache, IEventPublisher eventPublisher, IStoreContext storeContext) { @@ -42,7 +31,6 @@ public PollService( this._pollAnswerRepository = pollAnswerRepository; this._pollVotingRecords = pollVotingRecords; this._storeMappingRepository = storeMappingRepository; - this._requestCache = requestCache; this._eventPublisher = eventPublisher; this._storeContext = storeContext; @@ -51,10 +39,6 @@ public PollService( public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Utilities - private IQueryable Filter(IQueryable query, bool showHidden) { if (!showHidden) @@ -81,15 +65,6 @@ from sm in p_sm.DefaultIfEmpty() return query; } - #endregion Utilities - - #region Methods - - /// - /// Gets a poll - /// - /// The poll identifier - /// Poll public virtual Poll GetPollById(int pollId) { if (pollId == 0) @@ -98,13 +73,6 @@ public virtual Poll GetPollById(int pollId) return _pollRepository.GetById(pollId); } - /// - /// Gets a poll - /// - /// The poll system keyword - /// Language identifier. 0 if you want to get all polls - /// A value indicating whether to show hidden records - /// Poll public virtual Poll GetPollBySystemKeyword(string systemKeyword, int languageId, bool showHidden = false) { if (String.IsNullOrWhiteSpace(systemKeyword)) @@ -121,15 +89,6 @@ from p in _pollRepository.Table return poll; } - /// - /// Gets poll collection - /// - /// Language identifier. 0 if you want to get all polls - /// Retrieve only shown on home page polls - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Poll collection public virtual IPagedList GetPolls(int languageId, bool loadShownOnHomePageOnly, int pageIndex, int pageSize, bool showHidden = false) { var query = _pollRepository.Table; @@ -151,56 +110,30 @@ public virtual IPagedList GetPolls(int languageId, bool loadShownOnHomePag return polls; } - /// - /// Deletes a poll - /// - /// The poll public virtual void DeletePoll(Poll poll) { if (poll == null) throw new ArgumentNullException("poll"); _pollRepository.Delete(poll); - - //event notification - _eventPublisher.EntityDeleted(poll); } - /// - /// Inserts a poll - /// - /// Poll public virtual void InsertPoll(Poll poll) { if (poll == null) throw new ArgumentNullException("poll"); _pollRepository.Insert(poll); - - //event notification - _eventPublisher.EntityInserted(poll); } - /// - /// Updates the poll - /// - /// Poll public virtual void UpdatePoll(Poll poll) { if (poll == null) throw new ArgumentNullException("poll"); _pollRepository.Update(poll); - - //event notification - _eventPublisher.EntityUpdated(poll); } - /// - /// Gets a poll answer - /// - /// Poll answer identifier - /// Poll answer public virtual PollAnswer GetPollAnswerById(int pollAnswerId) { if (pollAnswerId == 0) @@ -213,27 +146,14 @@ public virtual PollAnswer GetPollAnswerById(int pollAnswerId) return pollAnswer; } - /// - /// Deletes a poll answer - /// - /// Poll answer public virtual void DeletePollAnswer(PollAnswer pollAnswer) { if (pollAnswer == null) throw new ArgumentNullException("pollAnswer"); _pollAnswerRepository.Delete(pollAnswer); - - //event notification - _eventPublisher.EntityDeleted(pollAnswer); } - /// - /// Gets a value indicating whether customer already vited for this poll - /// - /// Poll identifier - /// Customer identifier - /// Result public virtual bool AlreadyVoted(int pollId, int customerId) { if (pollId == 0 || customerId == 0) @@ -246,6 +166,26 @@ join pvr in _pollVotingRecords.Table on pa.Id equals pvr.PollAnswerId return result; } - #endregion + 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/Properties/Settings.Designer.cs b/src/Libraries/SmartStore.Services/Properties/Settings.Designer.cs index 0d4d638b36..ee12dcdfeb 100644 --- a/src/Libraries/SmartStore.Services/Properties/Settings.Designer.cs +++ b/src/Libraries/SmartStore.Services/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.34209 +// Laufzeitversion:4.0.30319.42000 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. @@ -12,7 +12,7 @@ namespace SmartStore.Services.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -33,5 +33,14 @@ public string SmartStore_Services_EuropaCheckVatService_checkVatService { } } + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.WebServiceUrl)] + [global::System.Configuration.DefaultSettingValueAttribute("http://ec.europa.eu/taxation_customs/vies/services/checkVatService")] + public string Nop_Services_EuropaCheckVatService_checkVatService { + get { + return ((string)(this["Nop_Services_EuropaCheckVatService_checkVatService"])); + } + } } } diff --git a/src/Libraries/SmartStore.Services/ScopedServiceBase.cs b/src/Libraries/SmartStore.Services/ScopedServiceBase.cs new file mode 100644 index 0000000000..961fa5cd6f --- /dev/null +++ b/src/Libraries/SmartStore.Services/ScopedServiceBase.cs @@ -0,0 +1,67 @@ +using System; +using SmartStore.Utilities; + +namespace SmartStore.Services +{ + public abstract class ScopedServiceBase : IScopedService + { + /// + /// Creates a long running unit of work in which cache eviction is suppressed + /// + /// Specifies whether the cache should be evicted completely on batch disposal + /// A disposable unit of work + public IDisposable BeginScope(bool clearCache = true) + { + if (IsInScope) + { + // nested batches are not supported + return ActionDisposable.Empty; + } + + OnBeginScope(); + IsInScope = true; + + return new ActionDisposable(() => + { + IsInScope = false; + OnEndScope(); + if (clearCache && HasChanges) + { + ClearCache(); + } + }); + } + + /// + /// Gets a value indicating whether data has changed during a request, making cache eviction necessary. + /// + /// + /// Cache eviction sets this member to false + /// + public bool HasChanges + { + get; + protected set; + } + + protected bool IsInScope + { + get; + private set; + } + + public void ClearCache() + { + if (!IsInScope) + { + OnClearCache(); + HasChanges = false; + } + } + + protected abstract void OnClearCache(); + + protected virtual void OnBeginScope() { } + protected virtual void OnEndScope() { } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs new file mode 100644 index 0000000000..abfc4f73b5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs @@ -0,0 +1,31 @@ +using System; + +namespace SmartStore.Services.Search +{ + public class CatalogSearchingEvent + { + public CatalogSearchingEvent(CatalogSearchQuery query) + { + Guard.NotNull(query, nameof(query)); + + Query = query; + } + + public CatalogSearchQuery Query { get; private set; } + } + + public class CatalogSearchedEvent + { + public CatalogSearchedEvent(CatalogSearchQuery query, CatalogSearchResult result) + { + Guard.NotNull(query, nameof(query)); + Guard.NotNull(result, nameof(result)); + + Query = query; + Result = result; + } + + public CatalogSearchQuery Query { get; private set; } + public CatalogSearchResult Result { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs new file mode 100644 index 0000000000..cfc21d3a9f --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs @@ -0,0 +1,340 @@ +using System; +using System.Linq; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Search; + +namespace SmartStore.Services.Search +{ + public partial class CatalogSearchQuery : SearchQuery, ICloneable + { + /// + /// Initializes a new instance of the class without a search term being set + /// + public CatalogSearchQuery() + : base((string[])null, null) + { + } + + public CatalogSearchQuery(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 CatalogSearchQuery(string[] fields, string term, SearchMode mode = SearchMode.Contains, bool escape = true, bool isFuzzySearch = false) + : base(fields, term, mode, escape, isFuzzySearch) + { + } + + public CatalogSearchQuery Clone() + { + return (CatalogSearchQuery)this.MemberwiseClone(); + } + + 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) + { + switch (sort) + { + case ProductSortingEnum.CreatedOnAsc: + case ProductSortingEnum.CreatedOn: + return SortBy(SearchSort.ByDateTimeField("createdon", sort == ProductSortingEnum.CreatedOn)); + + case ProductSortingEnum.NameAsc: + case ProductSortingEnum.NameDesc: + return SortBy(SearchSort.ByStringField("name", sort == ProductSortingEnum.NameDesc)); + + case ProductSortingEnum.PriceAsc: + case ProductSortingEnum.PriceDesc: + return SortBy(SearchSort.ByDoubleField("price", sort == ProductSortingEnum.PriceDesc)); + + case ProductSortingEnum.Relevance: + return SortBy(SearchSort.ByRelevance()); + + default: + return this; + } + } + + /// + /// Only products that are visible in frontend + /// + /// Customer whose customer roles should be checked, can be null + /// Catalog search query + public CatalogSearchQuery VisibleOnly(Customer customer) + { + if (customer != null) + { + var allowedCustomerRoleIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToArray(); + + return VisibleOnly(allowedCustomerRoleIds); + } + + return VisibleOnly(new int[0]); + } + + /// + /// Only products that are visible in frontend + /// + /// Allowed customer role id, can be null + /// Catalog search query + public CatalogSearchQuery VisibleOnly(params int[] allowedCustomerRoleIds) + { + var utcNow = DateTime.UtcNow; + + PublishedOnly(true); + + WithFilter(SearchFilter.ByRange("availablestart", null, utcNow, false, false).Mandatory().NotAnalyzed()); + WithFilter(SearchFilter.ByRange("availableend", utcNow, null, false, false).Mandatory().NotAnalyzed()); + + if (allowedCustomerRoleIds != null && allowedCustomerRoleIds.Length > 0) + { + var roleIds = allowedCustomerRoleIds.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 CatalogSearchQuery PublishedOnly(bool value) + { + return WithFilter(SearchFilter.ByField("published", value).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery VisibleIndividuallyOnly(bool value) + { + return WithFilter(SearchFilter.ByField("visibleindividually", value).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery HomePageProductsOnly(bool value) + { + return WithFilter(SearchFilter.ByField("showonhomepage", value).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery HasParentGroupedProduct(params int[] parentProductIds) + { + if (parentProductIds.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(parentProductIds.Select(x => SearchFilter.ByField("parentid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public override CatalogSearchQuery 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 CatalogSearchQuery IsProductType(ProductType type) + { + return WithFilter(SearchFilter.ByField("typeid", (int)type).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery WithProductIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("id", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public CatalogSearchQuery WithProductId(int? fromId, int? toId) + { + if (fromId == null && toId == null) + { + return this; + } + + return WithFilter(SearchFilter.ByRange("id", fromId, toId, fromId.HasValue, toId.HasValue).Mandatory().ExactMatch().NotAnalyzed()); + } + + /// + /// Category ids filter + /// + /// + /// A value indicating whether loaded products are marked as featured (relates only to categories and manufacturers). + /// false to load featured products only, true to load unfeatured products only, null to load all products + /// + /// The category ids + /// Query + public CatalogSearchQuery WithCategoryIds(bool? featuredOnly, params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + var fieldName = featuredOnly.HasValue + ? featuredOnly.Value ? "featuredcategoryid" : "notfeaturedcategoryid" + : "categoryid"; + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField(fieldName, x).ExactMatch().NotAnalyzed()).ToArray())); + } + + /// Includes only published categories + public CatalogSearchQuery HasAnyCategory(bool value) + { + if (value) + { + return WithFilter(SearchFilter.ByRange("categoryid", 1, int.MaxValue, true, true).Mandatory().NotAnalyzed()); + } + else + { + return WithFilter(SearchFilter.ByField("categoryid", 0).Mandatory().ExactMatch().NotAnalyzed()); + } + } + + public CatalogSearchQuery WithManufacturerIds(bool? featuredOnly, params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + var fieldName = featuredOnly.HasValue + ? featuredOnly.Value ? "featuredmanufacturerid" : "notfeaturedmanufacturerid" + : "manufacturerid"; + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField(fieldName, x).ExactMatch().NotAnalyzed()).ToArray())); + } + + /// Includes only published manufacturers + public CatalogSearchQuery HasAnyManufacturer(bool value) + { + if (value) + { + return WithFilter(SearchFilter.ByRange("manufacturerid", 1, int.MaxValue, true, true).Mandatory().NotAnalyzed()); + } + else + { + return WithFilter(SearchFilter.ByField("manufacturerid", 0).Mandatory().ExactMatch().NotAnalyzed()); + } + } + + public CatalogSearchQuery WithProductTagIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("tagid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public CatalogSearchQuery WithStockQuantity(int? fromQuantity, int? toQuantity) + { + if (fromQuantity == null && toQuantity == null) + { + return this; + } + + return WithFilter(SearchFilter.ByRange("stockquantity", fromQuantity, toQuantity, fromQuantity.HasValue, toQuantity.HasValue).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery AvailableOnly(bool value) + { + return WithFilter(SearchFilter.ByField("available", value).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery PriceBetween(decimal? fromPrice, decimal? toPrice) + { + if (fromPrice == null && toPrice == null) + { + return this; + } + + Guard.NotEmpty(CurrencyCode, nameof(CurrencyCode)); + + var fieldName = "price_c-" + CurrencyCode.EmptyNull().ToLower(); + + return WithFilter(SearchFilter.ByRange(fieldName, + fromPrice.HasValue ? decimal.ToDouble(fromPrice.Value) : (double?)null, + toPrice.HasValue ? decimal.ToDouble(toPrice.Value) : (double?)null, + fromPrice.HasValue, + toPrice.HasValue).Mandatory().ExactMatch().NotAnalyzed() + ); + } + + public CatalogSearchQuery 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()); + } + + public CatalogSearchQuery WithRating(double? fromRate, double? toRate) + { + if (fromRate == null && toRate == null) + { + return this; + } + if (fromRate.HasValue) + { + Guard.InRange(fromRate.Value, 0.0, 5.0, nameof(fromRate.Value)); + } + if (toRate.HasValue) + { + Guard.InRange(toRate.Value, 0.0, 5.0, nameof(toRate.Value)); + } + + return WithFilter(SearchFilter.ByRange("rating", fromRate, toRate, fromRate.HasValue, toRate.HasValue).Mandatory().ExactMatch().NotAnalyzed()); + } + + public CatalogSearchQuery WithDeliveryTimeIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("deliveryid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + #endregion + } +} 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/Catalog/ICatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/ICatalogSearchService.cs new file mode 100644 index 0000000000..21ffebbbe1 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/ICatalogSearchService.cs @@ -0,0 +1,26 @@ +using System.Linq; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Catalog; + +namespace SmartStore.Services.Search +{ + public partial interface ICatalogSearchService + { + /// + /// Searches for products + /// + /// Search term, filters and other parameters used for searching + /// Which product navigation properties to eager load + /// Bypasses the index provider (if available) and directly searches in the database + /// Catalog search result + CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None, bool direct = false); + + /// + /// Builds a product query using linq search + /// + /// Search term, filters and other parameters used for searching + /// Optional query used to build the product query. + /// Product queryable + IQueryable PrepareQuery(CatalogSearchQuery searchQuery, IQueryable baseQuery = null); + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs new file mode 100644 index 0000000000..fcd96b6675 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -0,0 +1,796 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Catalog; +using SmartStore.Services.Directory; +using SmartStore.Services.Search.Extensions; + +namespace SmartStore.Services.Search +{ + public partial class LinqCatalogSearchService : SearchServiceBase, ICatalogSearchService + { + private static int[] _priceThresholds = new int[] { 10, 25, 50, 100, 250, 500, 1000 }; + + private readonly IProductService _productService; + private readonly IRepository _productRepository; + private readonly IRepository _productManufacturerRepository; + private readonly IRepository _productCategoryRepository; + private readonly IRepository _localizedPropertyRepository; + private readonly IRepository _storeMappingRepository; + private readonly IRepository _aclRepository; + private readonly IEventPublisher _eventPublisher; + private readonly ICommonServices _services; + private readonly IDeliveryTimeService _deliveryTimeService; + private readonly IManufacturerService _manufacturerService; + private readonly ICategoryService _categoryService; + + public LinqCatalogSearchService( + IProductService productService, + IRepository productRepository, + IRepository productManufacturerRepository, + IRepository productCategoryRepository, + IRepository localizedPropertyRepository, + IRepository storeMappingRepository, + IRepository aclRepository, + IEventPublisher eventPublisher, + ICommonServices services, + IDeliveryTimeService deliveryTimeService, + IManufacturerService manufacturerService, + ICategoryService categoryService) + { + _productService = productService; + _productRepository = productRepository; + _productManufacturerRepository = productManufacturerRepository; + _productCategoryRepository = productCategoryRepository; + _localizedPropertyRepository = localizedPropertyRepository; + _storeMappingRepository = storeMappingRepository; + _aclRepository = aclRepository; + _eventPublisher = eventPublisher; + _services = services; + _deliveryTimeService = deliveryTimeService; + _manufacturerService = manufacturerService; + _categoryService = categoryService; + + QuerySettings = DbQuerySettings.Default; + } + + public DbQuerySettings QuerySettings { get; set; } + + #region Utilities + + private IQueryable QueryCategories(IQueryable query, List ids, bool? featuredOnly) + { + 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) + { + 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) + { + var term = searchQuery.Term; + var fields = searchQuery.Fields; + var languageId = searchQuery.LanguageId ?? 0; + + if (term.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) + { + // SearchMode.ExactMatch doesn't make sense here + if (searchQuery.Mode == SearchMode.StartsWith) + { + query = + from p in query + join lp in _localizedPropertyRepository.Table on p.Id equals lp.EntityId into plp + from lp in plp.DefaultIfEmpty() + where + (fields.Contains("name") && p.Name.StartsWith(term)) || + (fields.Contains("sku") && p.Sku.StartsWith(term)) || + (fields.Contains("shortdescription") && p.ShortDescription.StartsWith(term)) || + (languageId != 0 && lp.LanguageId == languageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "Name" && lp.LocaleValue.StartsWith(term)) || + (languageId != 0 && lp.LanguageId == languageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "ShortDescription" && lp.LocaleValue.StartsWith(term)) + select p; + } + else + { + query = + from p in query + join lp in _localizedPropertyRepository.Table on p.Id equals lp.EntityId into plp + from lp in plp.DefaultIfEmpty() + where + (fields.Contains("name") && p.Name.Contains(term)) || + (fields.Contains("sku") && p.Sku.Contains(term)) || + (fields.Contains("shortdescription") && p.ShortDescription.Contains(term)) || + (languageId != 0 && lp.LanguageId == languageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "Name" && lp.LocaleValue.Contains(term)) || + (languageId != 0 && lp.LanguageId == languageId && lp.LocaleKeyGroup == "Product" && lp.LocaleKey == "ShortDescription" && lp.LocaleValue.Contains(term)) + select p; + } + } + + return query; + } + + protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQuery, IQueryable baseQuery) + { + var ordered = false; + var utcNow = DateTime.UtcNow; + var categoryId = 0; + var manufacturerId = 0; + var query = baseQuery ?? _productRepository.Table; + + query = query.Where(x => !x.Deleted && !x.IsSystemProduct); + query = ApplySearchTerm(query, searchQuery); + + #region Filters + + var filters = new List(); + FlattenFilters(searchQuery.Filters, filters); + + var productIds = GetIdList(filters, "id"); + if (productIds.Any()) + { + query = query.Where(x => productIds.Contains(x.Id)); + } + + var categoryIds = GetIdList(filters, "categoryid"); + if (categoryIds.Any()) + { + categoryId = categoryIds.First(); + if (categoryIds.Count == 1 && categoryId == 0) + { + // Has no category. + query = query.Where(x => x.ProductCategories.Count == 0); + } + else + { + query = QueryCategories(query, categoryIds, null); + } + } + + 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()) + { + manufacturerId = manufacturerIds.First(); + if (manufacturerIds.Count == 1 && manufacturerId == 0) + { + // Has no manufacturer. + query = query.Where(x => x.ProductManufacturers.Count == 0); + } + else + { + query = QueryManufacturers(query, manufacturerIds, null); + } + } + + 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 = + from p in query + from pt in p.ProductTags.Where(pt => tagIds.Contains(pt.Id)) + select p; + } + + if (!QuerySettings.IgnoreAcl) + { + var roleIds = GetIdList(filters, "roleid"); + if (roleIds.Any()) + { + 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() + where !p.SubjectToAcl || roleIds.Contains(acl.CustomerRoleId) + select p; + } + } + + var deliverTimeIds = GetIdList(filters, "deliveryid"); + if (deliverTimeIds.Any()) + { + query = query.Where(x => x.DeliveryTimeId != null && deliverTimeIds.Contains(x.DeliveryTimeId.Value)); + } + + var parentProductIds = GetIdList(filters, "parentid"); + if (parentProductIds.Any()) + { + query = query.Where(x => parentProductIds.Contains(x.ParentGroupedProductId)); + } + + 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 == "categoryid") + { + if (rangeFilter != null && 1 == ((filter.Term as int?) ?? 0) && int.MaxValue == ((rangeFilter.UpperTerm as int?) ?? 0)) + { + // Has any category. + query = query.Where(x => x.ProductCategories.Count > 0); + } + } + else if (filter.FieldName == "manufacturerid") + { + if (rangeFilter != null && 1 == ((filter.Term as int?) ?? 0) && int.MaxValue == ((rangeFilter.UpperTerm as int?) ?? 0)) + { + // Has any manufacturer. + query = query.Where(x => x.ProductManufacturers.Count > 0); + } + } + else if (filter.FieldName == "published") + { + query = query.Where(x => x.Published == (bool)filter.Term); + } + else if (filter.FieldName == "availablestart") + { + 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.AvailableStartDateTimeUtc.HasValue || x.AvailableStartDateTimeUtc >= lower.Value); + else + query = query.Where(x => !x.AvailableStartDateTimeUtc.HasValue || x.AvailableStartDateTimeUtc > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => !x.AvailableStartDateTimeUtc.HasValue || x.AvailableStartDateTimeUtc <= upper.Value); + else + query = query.Where(x => !x.AvailableStartDateTimeUtc.HasValue || x.AvailableStartDateTimeUtc < upper.Value); + } + } + } + else if (filter.FieldName == "availableend") + { + 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.AvailableEndDateTimeUtc.HasValue || x.AvailableEndDateTimeUtc >= lower.Value); + else + query = query.Where(x => !x.AvailableEndDateTimeUtc.HasValue || x.AvailableEndDateTimeUtc > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => !x.AvailableEndDateTimeUtc.HasValue || x.AvailableEndDateTimeUtc <= upper.Value); + else + query = query.Where(x => !x.AvailableEndDateTimeUtc.HasValue || x.AvailableEndDateTimeUtc < upper.Value); + } + } + } + else if (filter.FieldName == "visibleindividually") + { + query = query.Where(x => x.VisibleIndividually == (bool)filter.Term); + } + else if (filter.FieldName == "showonhomepage") + { + query = query.Where(p => p.ShowOnHomePage == (bool)filter.Term); + } + else if (filter.FieldName == "typeid") + { + query = query.Where(x => x.ProductTypeId == (int)filter.Term); + } + else if (filter.FieldName == "stockquantity") + { + 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.StockQuantity >= lower.Value); + else + query = query.Where(x => x.StockQuantity > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesUpper) + query = query.Where(x => x.StockQuantity <= upper.Value); + else + query = query.Where(x => x.StockQuantity < upper.Value); + } + } + } + else if (filter.FieldName == "rating") + { + query = query.Where(x => x.ApprovedTotalReviews != 0 && ((double)x.ApprovedRatingSum / (double)x.ApprovedTotalReviews) >= (double)filter.Term); + } + else if (filter.FieldName == "available") + { + // We cannot include ManageInventoryMethod.ManageStockByAttributes because it's only functional with MergeWithCombination. + var manageStock = (int)ManageInventoryMethod.ManageStock; + + query = query.Where(x => + x.ManageInventoryMethodId != manageStock || + (x.ManageInventoryMethodId == manageStock && (x.StockQuantity > 0 || x.BackorderModeId != (int)BackorderMode.NoBackorders)) + ); + } + else if (filter.FieldName.StartsWith("price")) + { + if (rangeFilter != null) + { + var lower = filter.Term as double?; + var upper = rangeFilter.UpperTerm as double?; + + if (lower.HasValue) + { + var minPrice = Convert.ToDecimal(lower.Value); + + query = query.Where(x => + ((x.SpecialPrice.HasValue && + ((!x.SpecialPriceStartDateTimeUtc.HasValue || x.SpecialPriceStartDateTimeUtc.Value < utcNow) && + (!x.SpecialPriceEndDateTimeUtc.HasValue || x.SpecialPriceEndDateTimeUtc.Value > utcNow))) && + (x.SpecialPrice >= minPrice)) + || + ((!x.SpecialPrice.HasValue || + ((x.SpecialPriceStartDateTimeUtc.HasValue && x.SpecialPriceStartDateTimeUtc.Value > utcNow) || + (x.SpecialPriceEndDateTimeUtc.HasValue && x.SpecialPriceEndDateTimeUtc.Value < utcNow))) && + (x.Price >= minPrice)) + ); + } + + if (upper.HasValue) + { + var maxPrice = Convert.ToDecimal(upper); + + query = query.Where(x => + ((x.SpecialPrice.HasValue && + ((!x.SpecialPriceStartDateTimeUtc.HasValue || x.SpecialPriceStartDateTimeUtc.Value < utcNow) && + (!x.SpecialPriceEndDateTimeUtc.HasValue || x.SpecialPriceEndDateTimeUtc.Value > utcNow))) && + (x.SpecialPrice <= maxPrice)) + || + ((!x.SpecialPrice.HasValue || + ((x.SpecialPriceStartDateTimeUtc.HasValue && x.SpecialPriceStartDateTimeUtc.Value > utcNow) || + (x.SpecialPriceEndDateTimeUtc.HasValue && x.SpecialPriceEndDateTimeUtc.Value < utcNow))) && + (x.Price <= maxPrice)) + ); + } + } + } + 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.Table on new { pid = p.Id, pname = "Product" } equals new { pid = sm.EntityId, pname = sm.EntityName } into psm + from sm in psm.DefaultIfEmpty() + where !p.LimitedToStores || sm.StoreId == storeId + select p; + } + } + } + } + + #endregion + + // Grouping is very slow if there are many products. + query = + from p in query + group p by p.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + + #region Sorting + + foreach (var sort in searchQuery.Sorting) + { + if (sort.FieldName.IsEmpty()) + { + // Sort by relevance. + if (categoryId != 0) + { + query = OrderBy(ref ordered, query, x => x.ProductCategories.Where(pc => pc.CategoryId == categoryId).FirstOrDefault().DisplayOrder); + } + else if (manufacturerId != 0) + { + query = OrderBy(ref ordered, query, x => x.ProductManufacturers.Where(pm => pm.ManufacturerId == manufacturerId).FirstOrDefault().DisplayOrder); + } + else if (FindFilter(searchQuery.Filters, "parentid") != null) + { + query = OrderBy(ref ordered, query, x => x.DisplayOrder); + } + else + { + query = OrderBy(ref ordered, query, x => x.Name); + } + } + else if (sort.FieldName == "createdon") + { + query = OrderBy(ref ordered, query, x => x.CreatedOnUtc, sort.Descending); + } + else if (sort.FieldName == "name") + { + query = OrderBy(ref ordered, query, x => x.Name, sort.Descending); + } + else if (sort.FieldName == "price") + { + query = OrderBy(ref ordered, query, x => x.Price, sort.Descending); + } + else + { + query = OrderBy(ref ordered, query, x => x.Name); + } + } + + if (!ordered) + { + if (FindFilter(searchQuery.Filters, "parentid") != null) + { + query = query.OrderBy(x => x.DisplayOrder); + } + else + { + query = query.OrderBy(x => x.Id); + } + } + + #endregion + + return query; + } + + protected virtual IDictionary GetFacets(CatalogSearchQuery 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("Catalog", key); + + switch (kind) + { + case FacetGroupKind.Category: + case FacetGroupKind.Brand: + case FacetGroupKind.DeliveryTime: + case FacetGroupKind.Rating: + case FacetGroupKind.Price: + if (totalHits == 0 && !descriptor.Values.Any(x => x.IsSelected)) + { + continue; + } + break; + } + + if (kind == FacetGroupKind.Category) + { + var categoryTree = _categoryService.GetCategoryTree(0, false, storeId); + var categories = categoryTree.Flatten(false); + + if (descriptor.MaxChoicesCount > 0) + { + categories = categories.Take(descriptor.MaxChoicesCount); + } + + var nameQuery = _localizedPropertyRepository.TableUntracked + .Where(x => x.LocaleKeyGroup == "Category" && x.LocaleKey == "Name" && x.LanguageId == languageId); + var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); + + foreach (var category in categories) + { + string label = null; + names.TryGetValue(category.Id, out label); + + facets.Add(new Facet(new FacetValue(category.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(category.Id)), + Label = label.HasValue() ? label : category.Name, + DisplayOrder = category.DisplayOrder + })); + } + } + else if (kind == FacetGroupKind.Brand) + { + var manufacturers = _manufacturerService.GetAllManufacturers(null, storeId); + if (descriptor.MaxChoicesCount > 0) + { + manufacturers = manufacturers.Take(descriptor.MaxChoicesCount).ToList(); + } + + var nameQuery = _localizedPropertyRepository.TableUntracked + .Where(x => x.LocaleKeyGroup == "Manufacturer" && x.LocaleKey == "Name" && x.LanguageId == languageId); + var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); + + foreach (var manu in manufacturers) + { + string label = null; + names.TryGetValue(manu.Id, out label); + + facets.Add(new Facet(new FacetValue(manu.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(manu.Id)), + Label = label.HasValue() ? label : manu.Name, + DisplayOrder = manu.DisplayOrder + })); + } + } + else if (kind == FacetGroupKind.DeliveryTime) + { + var deliveryTimes = _deliveryTimeService.GetAllDeliveryTimes(); + var nameQuery = _localizedPropertyRepository.TableUntracked + .Where(x => x.LocaleKeyGroup == "DeliveryTime" && x.LocaleKey == "Name" && x.LanguageId == languageId); + var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); + + foreach (var deliveryTime in deliveryTimes) + { + if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) + break; + + string label = null; + names.TryGetValue(deliveryTime.Id, out label); + + facets.Add(new Facet(new FacetValue(deliveryTime.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(deliveryTime.Id)), + Label = label.HasValue() ? label : deliveryTime.Name, + DisplayOrder = deliveryTime.DisplayOrder + })); + } + } + else if (kind == FacetGroupKind.Price) + { + var count = 0; + var hasActivePredefinedFacet = false; + 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); + + for (var i = 0; i < _priceThresholds.Length; ++i) + { + if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) + break; + + var price = _priceThresholds[i]; + if (price < minPrice) + continue; + + if (price >= maxPrice) + i = int.MaxValue - 1; + + var selected = descriptor.Values.Any(x => x.IsSelected && x.Value == null && x.UpperValue != null && (double)x.UpperValue == price); + if (selected) + hasActivePredefinedFacet = true; + + facets.Add(new Facet(new FacetValue(null, price, IndexTypeCode.Double, false, true) + { + DisplayOrder = ++count, + IsSelected = selected + })); + } + + // Add facet for custom price range. + var priceDescriptorValue = descriptor.Values.FirstOrDefault(); + + var customPriceFacetValue = new FacetValue( + priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.Value : null, + priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.UpperValue : null, + IndexTypeCode.Double, + true, + true); + + customPriceFacetValue.IsSelected = customPriceFacetValue.Value != null || customPriceFacetValue.UpperValue != null; + + if (!(totalHits == 0 && !customPriceFacetValue.IsSelected)) + { + facets.Insert(0, new Facet("custom", customPriceFacetValue)); + } + } + else if (kind == FacetGroupKind.Rating) + { + foreach (var rating in FacetUtility.GetRatings()) + { + var newFacet = new Facet(rating); + newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(rating.Value)); + facets.Add(newFacet); + } + } + else if (kind == FacetGroupKind.Availability || kind == FacetGroupKind.NewArrivals) + { + var value = descriptor.Values.FirstOrDefault(); + if (value != null) + { + if (kind == FacetGroupKind.NewArrivals && totalHits == 0 && !value.IsSelected) + { + continue; + } + + var newValue = value.Clone(); + newValue.Value = true; + newValue.TypeCode = IndexTypeCode.Boolean; + newValue.IsRange = false; + newValue.IsSelected = value.IsSelected; + + facets.Add(new Facet(newValue)); + } + } + + if (facets.Any(x => x.Published)) + { + //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); + + result.Add(key, new FacetGroup( + "Catalog", + key, + descriptor.Label, + descriptor.IsMultiSelect, + false, + descriptor.DisplayOrder, + facets.OrderBy(descriptor))); + } + } + + return result; + } + + #endregion + + public CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None, bool direct = false) + { + _eventPublisher.Publish(new CatalogSearchingEvent(searchQuery)); + + var totalHits = 0; + Func> hitsFactory = null; + IDictionary facets = null; + + if (searchQuery.Take > 0) + { + var query = GetProductQuery(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 = () => _productService.GetProductsByIds(ids, loadFlags); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets) && searchQuery.FacetDescriptors.Any()) + { + facets = GetFacets(searchQuery, totalHits); + } + } + + var result = new CatalogSearchResult( + null, + searchQuery, + totalHits, + hitsFactory, + null, + facets); + + _eventPublisher.Publish(new CatalogSearchedEvent(searchQuery, result)); + + return result; + } + + public IQueryable PrepareQuery(CatalogSearchQuery searchQuery, IQueryable baseQuery = null) + { + return GetProductQuery(searchQuery, baseQuery); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs new file mode 100644 index 0000000000..6cfae38fd0 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs @@ -0,0 +1,495 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Caching; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Search.Facets; +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 + { + 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 readonly ICacheManager _cacheManager; + private readonly IRepository _localizedPropertyRepository; + private readonly IRepository _productAttributeRepository; + private readonly IRepository _productVariantAttributeValueRepository; + private readonly ISpecificationAttributeService _specificationAttributeService; + private readonly ISettingService _settingService; + private readonly ILanguageService _languageService; + + public CatalogSearchQueryAliasMapper( + ICacheManager cacheManager, + IRepository localizedPropertyRepository, + IRepository productAttributeRepository, + IRepository productVariantAttributeValueRepository, + ISpecificationAttributeService specificationAttributeService, + ISettingService settingService, + ILanguageService languageService) + { + _cacheManager = cacheManager; + _localizedPropertyRepository = localizedPropertyRepository; + _productAttributeRepository = productAttributeRepository; + _productVariantAttributeValueRepository = productVariantAttributeValueRepository; + _specificationAttributeService = specificationAttributeService; + _settingService = settingService; + _languageService = languageService; + } + + protected string CreateKey(string prefix, int languageId, string alias) + { + return $"{prefix}.{languageId}.{alias}"; + } + protected string CreateKey(string prefix, int languageId, int attributeId) + { + return $"{prefix}.{languageId}.{attributeId}"; + } + + protected string CreateOptionKey(string prefix, int languageId, int attributeId, string optionAlias) + { + return $"{prefix}.{languageId}.{attributeId}.{optionAlias}"; + } + protected string CreateOptionKey(string prefix, int languageId, int optionId) + { + return $"{prefix}.{languageId}.{optionId}"; + } + + protected void CachedLocalizedAlias(string localeKeyGroup, Action 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 + + protected virtual IDictionary GetAttributeIdByAliasMappings() + { + return _cacheManager.Get(ALL_ATTRIBUTE_ID_BY_ALIAS_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + var optionIdMappings = new Dictionary(); + IPagedList attributes = null; + var attributeId = 0; + var pageIndex = 0; + + var query = _specificationAttributeService.GetSpecificationAttributes() + .Expand(x => x.SpecificationAttributeOptions) + .OrderBy(x => x.Id); + + do + { + attributes = new PagedList(query, pageIndex++, 500); + + foreach (var attribute in attributes) + { + if (attribute.Alias.HasValue()) + { + result[CreateKey("attr", 0, attribute.Alias)] = attribute.Id; + } + + foreach (var option in attribute.SpecificationAttributeOptions) + { + optionIdMappings[option.Id] = option.SpecificationAttributeId; + + if (option.Alias.HasValue()) + { + result[CreateOptionKey("attr.option", 0, attribute.Id, option.Alias)] = option.Id; + } + } + } + } + while (attributes.HasNextPage); + + attributes.Clear(); + + CachedLocalizedAlias("SpecificationAttribute", x => result[CreateKey("attr", x.LanguageId, x.LocaleValue)] = x.EntityId); + CachedLocalizedAlias("SpecificationAttributeOption", x => + { + if (optionIdMappings.TryGetValue(x.EntityId, out attributeId)) + result[CreateOptionKey("attr.option", x.LanguageId, attributeId, x.LocaleValue)] = x.EntityId; + }); + + return result; + }); + } + + protected virtual IDictionary GetAttributeAliasByIdMappings() + { + return _cacheManager.Get(ALL_ATTRIBUTE_ALIAS_BY_ID_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + IPagedList attributes = null; + var pageIndex = 0; + + var query = _specificationAttributeService.GetSpecificationAttributes() + .Expand(x => x.SpecificationAttributeOptions) + .OrderBy(x => x.Id); + + do + { + attributes = new PagedList(query, pageIndex++, 500); + + foreach (var attribute in attributes) + { + if (attribute.Alias.HasValue()) + { + result[CreateKey("attr", 0, attribute.Id)] = attribute.Alias; + } + + foreach (var option in attribute.SpecificationAttributeOptions.Where(x => x.Alias.HasValue())) + { + result[CreateOptionKey("attr.option", 0, option.Id)] = option.Alias; + } + } + } + while (attributes.HasNextPage); + + attributes.Clear(); + + CachedLocalizedAlias("SpecificationAttribute", x => result[CreateKey("attr", x.LanguageId, x.EntityId)] = x.LocaleValue); + CachedLocalizedAlias("SpecificationAttributeOption", x => result[CreateOptionKey("attr.option", x.LanguageId, x.EntityId)] = x.LocaleValue); + + return result; + }); + } + + public void ClearAttributeCache() + { + _cacheManager.Remove(ALL_ATTRIBUTE_ID_BY_ALIAS_KEY); + _cacheManager.Remove(ALL_ATTRIBUTE_ALIAS_BY_ID_KEY); + } + + public int GetAttributeIdByAlias(string attributeAlias, int languageId = 0) + { + var result = 0; + + if (attributeAlias.HasValue()) + { + var mappings = GetAttributeIdByAliasMappings(); + + if (!mappings.TryGetValue(CreateKey("attr", languageId, attributeAlias), out result) && languageId != 0) + { + mappings.TryGetValue(CreateKey("attr", 0, attributeAlias), out result); + } + } + + return result; + } + + public int GetAttributeOptionIdByAlias(string optionAlias, int attributeId, int languageId = 0) + { + var result = 0; + + if (optionAlias.HasValue() && attributeId != 0) + { + var mappings = GetAttributeIdByAliasMappings(); + + if (!mappings.TryGetValue(CreateOptionKey("attr.option", languageId, attributeId, optionAlias), out result) && languageId != 0) + { + mappings.TryGetValue(CreateOptionKey("attr.option", 0, attributeId, optionAlias), out result); + } + } + + return result; + } + + public string GetAttributeAliasById(int attributeId, int languageId = 0) + { + string result = null; + + if (attributeId != 0) + { + var mappings = GetAttributeAliasByIdMappings(); + + if (!mappings.TryGetValue(CreateKey("attr", languageId, attributeId), out result) && languageId != 0) + { + mappings.TryGetValue(CreateKey("attr", 0, attributeId), out result); + } + } + + return result; + } + + public string GetAttributeOptionAliasById(int optionId, int languageId = 0) + { + string result = null; + + if (optionId != 0) + { + var mappings = GetAttributeAliasByIdMappings(); + + if (!mappings.TryGetValue(CreateOptionKey("attr.option", languageId, optionId), out result) && languageId != 0) + { + mappings.TryGetValue(CreateOptionKey("attr.option", 0, optionId), out result); + } + } + + return result; + } + + #endregion + + #region Product Variants + + protected virtual IDictionary GetVariantIdByAliasMappings() + { + return _cacheManager.Get(ALL_VARIANT_ID_BY_ALIAS_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + IPagedList variants = null; + IPagedList options = null; + var variantId = 0; + var pageIndex = 0; + + var variantQuery = _productAttributeRepository.TableUntracked + .Where(x => !string.IsNullOrEmpty(x.Alias)) + .OrderBy(x => x.Id); + + var optionQuery = _productVariantAttributeValueRepository.TableUntracked + .Expand(x => x.ProductVariantAttribute) + .Expand("ProductVariantAttribute.ProductAttribute") + .Where(x => !string.IsNullOrEmpty(x.Alias)) + .OrderBy(x => x.Id); + + do + { + variants = new PagedList(variantQuery, pageIndex++, 500); + + foreach (var variant in variants) + { + result[CreateKey("vari", 0, variant.Alias)] = variant.Id; + } + } + while (variants.HasNextPage); + pageIndex = 0; + variants.Clear(); + + do + { + options = new PagedList(optionQuery, pageIndex++, 500); + + foreach (var option in options) + { + var variant = option.ProductVariantAttribute.ProductAttribute; + result[CreateOptionKey("vari.option", 0, variant.Id, option.Alias)] = option.Id; + } + } + while (options.HasNextPage); + options.Clear(); + + var optionIdMappings = _productVariantAttributeValueRepository.TableUntracked + .Expand(x => x.ProductVariantAttribute.ProductAttribute) + .Select(x => new + { + OptionId = x.Id, + VariantId = x.ProductVariantAttribute.ProductAttribute.Id + }) + .ToDictionary(x => x.OptionId, x => x.VariantId); + + CachedLocalizedAlias("ProductAttribute", x => result[CreateKey("vari", x.LanguageId, x.LocaleValue)] = x.EntityId); + CachedLocalizedAlias("ProductVariantAttributeValue", x => + { + if (optionIdMappings.TryGetValue(x.EntityId, out variantId)) + result[CreateOptionKey("vari.option", x.LanguageId, variantId, x.LocaleValue)] = x.EntityId; + }); + + return result; + }); + } + + protected virtual IDictionary GetVariantAliasByIdMappings() + { + return _cacheManager.Get(ALL_VARIANT_ALIAS_BY_ID_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + IPagedList variants = null; + IPagedList options = null; + var pageIndex = 0; + + var variantQuery = _productAttributeRepository.TableUntracked + .Where(x => !string.IsNullOrEmpty(x.Alias)) + .OrderBy(x => x.Id); + + var optionQuery = _productVariantAttributeValueRepository.TableUntracked + .Where(x => !string.IsNullOrEmpty(x.Alias)) + .OrderBy(x => x.Id); + + do + { + variants = new PagedList(variantQuery, pageIndex++, 500); + + foreach (var variant in variants) + { + result[CreateKey("vari", 0, variant.Id)] = variant.Alias; + } + } + while (variants.HasNextPage); + pageIndex = 0; + variants.Clear(); + + do + { + options = new PagedList(optionQuery, pageIndex++, 500); + + foreach (var option in options) + { + result[CreateOptionKey("attr.option", 0, option.Id)] = option.Alias; + } + } + while (options.HasNextPage); + options.Clear(); + + CachedLocalizedAlias("ProductAttribute", x => result[CreateKey("vari", x.LanguageId, x.EntityId)] = x.LocaleValue); + CachedLocalizedAlias("ProductVariantAttributeValue", x => result[CreateOptionKey("vari.option", x.LanguageId, x.EntityId)] = x.LocaleValue); + + return result; + }); + } + + public void ClearVariantCache() + { + _cacheManager.Remove(ALL_VARIANT_ID_BY_ALIAS_KEY); + _cacheManager.Remove(ALL_VARIANT_ALIAS_BY_ID_KEY); + } + + public int GetVariantIdByAlias(string variantAlias, int languageId = 0) + { + var result = 0; + + if (variantAlias.HasValue()) + { + var mappings = GetVariantIdByAliasMappings(); + + if (!mappings.TryGetValue(CreateKey("vari", languageId, variantAlias), out result) && languageId != 0) + { + mappings.TryGetValue(CreateKey("vari", 0, variantAlias), out result); + } + } + + return result; + } + + public int GetVariantOptionIdByAlias(string optionAlias, int variantId, int languageId = 0) + { + var result = 0; + + if (optionAlias.HasValue() && variantId != 0) + { + var mappings = GetVariantIdByAliasMappings(); + + if (!mappings.TryGetValue(CreateOptionKey("vari.option", languageId, variantId, optionAlias), out result) && languageId != 0) + { + mappings.TryGetValue(CreateOptionKey("vari.option", 0, variantId, optionAlias), out result); + } + } + + return result; + } + + public string GetVariantAliasById(int variantId, int languageId = 0) + { + string result = null; + + if (variantId != 0) + { + var mappings = GetVariantAliasByIdMappings(); + + if (!mappings.TryGetValue(CreateKey("vari", languageId, variantId), out result) && languageId != 0) + { + mappings.TryGetValue(CreateKey("vari", 0, variantId), out result); + } + } + + return result; + } + + public string GetVariantOptionAliasById(int optionId, int languageId = 0) + { + string result = null; + + if (optionId != 0) + { + var mappings = GetVariantAliasByIdMappings(); + + if (!mappings.TryGetValue(CreateOptionKey("vari.option", languageId, optionId), out result) && languageId != 0) + { + mappings.TryGetValue(CreateOptionKey("vari.option", 0, optionId), out result); + } + } + + return result; + } + + #endregion + + #region Common Facets + + protected virtual IDictionary GetCommonFacetAliasByGroupKindMappings() + { + return _cacheManager.Get(ALL_COMMONFACET_ALIAS_BY_KIND_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var groupKinds = new FacetGroupKind[] + { + FacetGroupKind.Category, + FacetGroupKind.Brand, + FacetGroupKind.Price, + FacetGroupKind.Rating, + FacetGroupKind.DeliveryTime, + FacetGroupKind.Availability, + FacetGroupKind.NewArrivals + }; + + foreach (var language in _languageService.GetAllLanguages()) + { + foreach (var groupKind in groupKinds) + { + var key = FacetUtility.GetFacetAliasSettingKey(groupKind, language.Id); + var value = _settingService.GetSettingByKey(key); + if (value.HasValue()) + { + result.Add(key, value); + } + } + } + + return result; + }); + } + + public void ClearCommonFacetCache() + { + _cacheManager.Remove(ALL_COMMONFACET_ALIAS_BY_KIND_KEY); + } + + public string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId) + { + var mappings = GetCommonFacetAliasByGroupKindMappings(); + + return mappings.Get(FacetUtility.GetFacetAliasSettingKey(kind, languageId)); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs new file mode 100644 index 0000000000..3c30544a0d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Routing; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Catalog; +using SmartStore.Services.Search.Extensions; +using SmartStore.Services.Security; + +namespace SmartStore.Services.Search.Modelling +{ + /* + TOKENS: + =============================== + q - Search term + i - Page index + s - Page size + o - Order by + p - Price range (from~to || from(~) || ~to) + c - Categories + m - Manufacturers + r - Min Rating + a - Availability + n - New Arrivals + d - Delivery Time + v - View Mode + + * - Variants & attributes + */ + + public class CatalogSearchQueryFactory : SearchQueryFactoryBase, ICatalogSearchQueryFactory + { + protected static readonly string[] _instantSearchFields = new string[] { "manufacturer", "sku", "gtin", "mpn", "attrname", "variantname" }; + + protected readonly CatalogSettings _catalogSettings; + protected readonly SearchSettings _searchSettings; + protected readonly ICommonServices _services; + protected readonly ICatalogSearchQueryAliasMapper _catalogSearchQueryAliasMapper; + + public CatalogSearchQueryFactory( + HttpContextBase httpContext, + CatalogSettings catalogSettings, + SearchSettings searchSettings, + ICommonServices services, + ICatalogSearchQueryAliasMapper catalogSearchQueryAliasMapper) + : base(httpContext) + { + _catalogSettings = catalogSettings; + _searchSettings = searchSettings; + _services = services; + _catalogSearchQueryAliasMapper = catalogSearchQueryAliasMapper; + + QuerySettings = DbQuerySettings.Default; + } + + public DbQuerySettings QuerySettings { get; set; } + + public CatalogSearchQuery Current { get; private set; } + + protected override string[] Tokens => new string[] { "q", "i", "s", "o", "p", "c", "m", "r", "a", "n", "d", "v" }; + + public CatalogSearchQuery 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 { "name" }; + var term = GetValueFor("q"); + var isInstantSearch = origin.IsCaseInsensitiveEqual("Search/InstantSearch"); + + if (isInstantSearch) + { + fields.Add("shortdescription"); + fields.Add("tagname"); + + foreach (var fieldName in _instantSearchFields) + { + if (_searchSettings.SearchFields.Contains(fieldName)) + { + fields.Add(fieldName); + } + } + } + else + { + fields.AddRange(_searchSettings.SearchFields); + } + + var query = new CatalogSearchQuery(fields.ToArray(), term, _searchSettings.SearchMode) + .OriginatesFrom(origin) + .WithLanguage(_services.WorkContext.WorkingLanguage) + .WithCurrency(_services.WorkContext.WorkingCurrency) + .VisibleIndividuallyOnly(true) + .BuildFacetMap(!isInstantSearch); + + // Visibility. + query.VisibleOnly(!QuerySettings.IgnoreAcl ? _services.WorkContext.CurrentCustomer : null); + + // Store. + if (!QuerySettings.IgnoreMultiStore) + { + query.HasStoreId(_services.StoreContext.CurrentStore.Id); + } + + // Availability. + ConvertAvailability(query, routeData, origin); + + // Instant-Search never uses these filter parameters. + if (!isInstantSearch) + { + ConvertPagingSorting(query, routeData, origin); + ConvertPrice(query, routeData, origin); + ConvertCategory(query, routeData, origin); + ConvertManufacturer(query, routeData, origin); + ConvertRating(query, routeData, origin); + ConvertNewArrivals(query, routeData, origin); + ConvertDeliveryTime(query, routeData, origin); + } + + OnConverted(query, routeData, origin); + + Current = query; + return query; + } + + protected virtual void ConvertPagingSorting(CatalogSearchQuery 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); + + var orderBy = GetValueFor("o"); + if (orderBy == null || orderBy == ProductSortingEnum.Initial) + { + orderBy = origin.IsCaseInsensitiveEqual("Search/Search") ? _searchSettings.DefaultSortOrder : _catalogSettings.DefaultSortOrder; + } + + query.CustomData["CurrentSortOrder"] = orderBy.Value; + + query.SortBy(orderBy.Value); + } + + private int GetPageSize(CatalogSearchQuery query, RouteData routeData, string origin) + { + string entityViewMode = null; + + // Determine entity id if possible + IPagingOptions entity = null; + int? entityId; + if (origin.IsCaseInsensitiveEqual("Catalog/Category")) + { + entityId = (int?)routeData.Values["categoryId"]; + if (entityId.HasValue) + { + entity = _services.Resolve().GetCategoryById(entityId.Value) as IPagingOptions; + entityViewMode = ((Category)entity)?.DefaultViewMode; + } + } + else if (origin.IsCaseInsensitiveEqual("Catalog/Manufacturer")) + { + entityId = (int?)routeData.Values["manufacturerId"]; + if (entityId.HasValue) + { + entity = _services.Resolve().GetManufacturerById(entityId.Value) as IPagingOptions; + } + } + + var entitySize = entity?.PageSize; + + var sessionKey = origin; + if (entitySize.HasValue) + { + sessionKey += "/" + entitySize.Value; + } + + DetectViewMode(query, sessionKey, entityViewMode); + + var allowChange = entity?.AllowCustomersToSelectPageSize ?? _catalogSettings.AllowCustomersToSelectPageSize; + if (!allowChange) + { + return entitySize ?? _catalogSettings.DefaultProductListPageSize; + } + + sessionKey = "PageSize:" + sessionKey; + + // Get from form or query + var selectedSize = GetValueFor("s"); + + if (selectedSize.HasValue) + { + // Save the selection in session. We'll fetch this session value + // on subsequent requests for this route. + if (_httpContext.Session != null) + { + _httpContext.Session[sessionKey] = selectedSize.Value; + } + return selectedSize.Value; + } + + // Return user size from session + if (_httpContext.Session != null) + { + var sessionSize = _httpContext.Session[sessionKey].Convert(); + if (sessionSize.HasValue) + { + return sessionSize.Value; + } + } + + // Return default size for entity (IPagingOptions) + if (entitySize.HasValue) + { + return entitySize.Value; + } + + // Return default page size + return _catalogSettings.DefaultProductListPageSize; + } + + private void DetectViewMode(CatalogSearchQuery query, string sessionKey, string entityViewMode = null) + { + if (!_catalogSettings.AllowProductViewModeChanging) + { + query.CustomData["ViewMode"] = entityViewMode.NullEmpty() ?? _catalogSettings.DefaultViewMode; + return; + } + + var selectedViewMode = GetValueFor("v"); + + sessionKey = "ViewMode:" + sessionKey; + + if (selectedViewMode != null) + { + // Save the view mode selection in session. We'll fetch this session value + // on subsequent requests for this route. + if (_httpContext.Session != null) + { + _httpContext.Session[sessionKey] = selectedViewMode; + } + query.CustomData["ViewMode"] = selectedViewMode; + return; + } + + // Set view mode from session + if (_httpContext.Session != null) + { + var sessionViewMode = _httpContext.Session[sessionKey].Convert(); + if (sessionViewMode != null) + { + query.CustomData["ViewMode"] = sessionViewMode; + return; + } + } + + // Set default view mode for entity + if (entityViewMode != null) + { + query.CustomData["ViewMode"] = entityViewMode; + return; + } + + // Set default view mode + query.CustomData["ViewMode"] = _catalogSettings.DefaultViewMode; + } + + private void AddFacet( + CatalogSearchQuery query, + FacetGroupKind kind, + bool isMultiSelect, + FacetSorting sorting, + Action addValues) + { + string fieldName; + var displayOrder = 0; + + switch (kind) + { + case FacetGroupKind.Category: + fieldName = _catalogSettings.IncludeFeaturedProductsInNormalLists ? "categoryid" : "notfeaturedcategoryid"; + break; + case FacetGroupKind.Brand: + if (_searchSettings.BrandDisabled) + return; + fieldName = "manufacturerid"; + displayOrder = _searchSettings.BrandDisplayOrder; + break; + case FacetGroupKind.Price: + if (_searchSettings.PriceDisabled || !_services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices)) + return; + fieldName = "price"; + displayOrder = _searchSettings.PriceDisplayOrder; + break; + case FacetGroupKind.Rating: + if (_searchSettings.RatingDisabled) + return; + fieldName = "rating"; + displayOrder = _searchSettings.RatingDisplayOrder; + break; + case FacetGroupKind.DeliveryTime: + if (_searchSettings.DeliveryTimeDisabled) + return; + fieldName = "deliveryid"; + displayOrder = _searchSettings.DeliveryTimeDisplayOrder; + break; + case FacetGroupKind.Availability: + if (_searchSettings.AvailabilityDisabled) + return; + fieldName = "available"; + displayOrder = _searchSettings.AvailabilityDisplayOrder; + break; + case FacetGroupKind.NewArrivals: + if (_searchSettings.NewArrivalsDisabled) + return; + fieldName = "createdon"; + displayOrder = _searchSettings.NewArrivalsDisplayOrder; + break; + default: + throw new SmartException($"Unknown field name for facet group '{kind.ToString()}'"); + } + + 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); + } + + protected virtual void ConvertCategory(CatalogSearchQuery query, RouteData routeData, string origin) + { + if (origin == "Catalog/Category") + { + // we don't need category facetting in category pages + return; + } + + List ids; + + if (GetValueFor(query, "c", FacetGroupKind.Category, out ids) && ids != null && ids.Any()) + { + // TODO; (mc) Get deep ids (???) Make a low-level version of CatalogHelper.GetChildCategoryIds() + query.WithCategoryIds(_catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false, ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.Category, true, FacetSorting.HitsDesc, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void ConvertManufacturer(CatalogSearchQuery query, RouteData routeData, string origin) + { + //List ids = null; + //int? minHitCount = null; + + //GetValueFor(query, "m", FacetGroupKind.Brand, out ids); + + //// Preselect manufacturer on manufacturer page.... and then? + //if (origin.IsCaseInsensitiveEqual("Catalog/Manufacturer")) + //{ + // minHitCount = 0; + + // var manufacturerId = routeData.Values["manufacturerid"].ToString().ToInt(); + // if (manufacturerId != 0) + // { + // if (ids == null) + // ids = new List { manufacturerId }; + // else if (!ids.Contains(manufacturerId)) + // ids.Add(manufacturerId); + // } + //} + + if (origin.IsCaseInsensitiveEqual("Catalog/Manufacturer")) + { + // we don't need brand facetting in brand pages + return; + } + + List ids; + + if (GetValueFor(query, "m", FacetGroupKind.Brand, out ids) && ids != null && ids.Any()) + { + query.WithManufacturerIds(null, ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.Brand, true, FacetSorting.LabelAsc, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void ConvertPrice(CatalogSearchQuery query, RouteData routeData, string origin) + { + string price; + double? minPrice = null; + double? maxPrice = null; + + if (GetValueFor(query, "p", FacetGroupKind.Price, out price) && TryParseRange(price, out minPrice, out maxPrice)) + { + var currency = _services.WorkContext.WorkingCurrency; + + if (minPrice.HasValue) + { + // TODO: (mc) Why the heck did I convert this??!! + //minPrice = _currencyService.ConvertToPrimaryStoreCurrency(minPrice.Value, currency); + } + + if (maxPrice.HasValue) + { + //maxPrice = _currencyService.ConvertToPrimaryStoreCurrency(maxPrice.Value, currency); + } + + if (minPrice.HasValue && maxPrice.HasValue && minPrice > maxPrice) + { + var tmp = minPrice; + minPrice = maxPrice; + maxPrice = tmp; + } + + if (minPrice.HasValue || maxPrice.HasValue) + { + query.PriceBetween((decimal?)minPrice, (decimal?)maxPrice); + } + } + + AddFacet(query, FacetGroupKind.Price, false, FacetSorting.DisplayOrder, descriptor => + { + if (minPrice.HasValue || maxPrice.HasValue) + { + descriptor.AddValue(new FacetValue( + minPrice, + maxPrice, + IndexTypeCode.Double, + minPrice.HasValue, + maxPrice.HasValue) + { + IsSelected = true + }); + } + }); + } + + protected virtual void ConvertRating(CatalogSearchQuery query, RouteData routeData, string origin) + { + double? fromRate; + + if (GetValueFor(query, "r", FacetGroupKind.Rating, out fromRate) && fromRate.HasValue) + { + query.WithRating(fromRate, null); + } + + AddFacet(query, FacetGroupKind.Rating, false, FacetSorting.DisplayOrder, descriptor => + { + descriptor.MinHitCount = 0; + descriptor.MaxChoicesCount = 5; + + if (fromRate.HasValue) + { + descriptor.AddValue(new FacetValue(fromRate.Value, IndexTypeCode.Double) + { + IsSelected = true + }); + } + }); + } + + protected virtual void ConvertAvailability(CatalogSearchQuery query, RouteData routeData, string origin) + { + bool availability; + GetValueFor(query, "a", FacetGroupKind.Availability, out availability); + + // Setting specifies the logical direction of the filter. That's smarter than just to specify a default value. + if (_searchSettings.IncludeNotAvailable) + { + // False = show, True = hide unavailable products. + if (availability) + { + query.AvailableOnly(true); + } + } + else + { + // False = hide, True = show unavailable products. + if (!availability) + { + query.AvailableOnly(true); + } + } + + AddFacet(query, FacetGroupKind.Availability, true, FacetSorting.LabelAsc, descriptor => + { + descriptor.MinHitCount = 0; + + var newValue = availability + ? new FacetValue(true, IndexTypeCode.Boolean) + : new FacetValue(null, IndexTypeCode.Empty); + + newValue.IsSelected = availability; + newValue.Label = _services.Localization.GetResource(_searchSettings.IncludeNotAvailable ? "Search.Facet.ExcludeOutOfStock" : "Search.Facet.IncludeOutOfStock"); + + descriptor.AddValue(newValue); + }); + } + + protected virtual void ConvertNewArrivals(CatalogSearchQuery query, RouteData routeData, string origin) + { + var newForMaxDays = _catalogSettings.LabelAsNewForMaxDays ?? 0; + if (newForMaxDays <= 0) + { + // we cannot filter without it + return; + } + + bool newArrivalsOnly; + var fromUtc = DateTime.UtcNow.Subtract(TimeSpan.FromDays(newForMaxDays)); + + if (GetValueFor(query, "n", FacetGroupKind.NewArrivals, out newArrivalsOnly) && newArrivalsOnly) + { + query.CreatedBetween(fromUtc, null); + } + + AddFacet(query, FacetGroupKind.NewArrivals, true, FacetSorting.LabelAsc, descriptor => + { + descriptor.AddValue(new FacetValue(fromUtc, null, IndexTypeCode.DateTime, true, false) + { + IsSelected = newArrivalsOnly, + Label = _services.Localization.GetResource("Search.Facet.LastDays").FormatInvariant(newForMaxDays) + }); + }); + } + + protected virtual void ConvertDeliveryTime(CatalogSearchQuery query, RouteData routeData, string origin) + { + List ids; + + if (GetValueFor(query, "d", FacetGroupKind.DeliveryTime, out ids) && ids != null && ids.Any()) + { + query.WithDeliveryTimeIds(ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.DeliveryTime, true, FacetSorting.DisplayOrder, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void OnConverted(CatalogSearchQuery query, RouteData routeData, string origin) + { + } + + protected virtual bool GetValueFor(CatalogSearchQuery query, string key, FacetGroupKind kind, out T value) + { + return TryGetValueFor(_catalogSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs new file mode 100644 index 0000000000..dce6a49f57 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs @@ -0,0 +1,42 @@ +using System.Web.Mvc; +using Autofac.Integration.Mvc; + +namespace SmartStore.Services.Search.Modelling +{ + [ModelBinderType(typeof(CatalogSearchQuery))] + public class CatalogSearchQueryModelBinder : IModelBinder + { + private readonly ICatalogSearchQueryFactory _factory; + + public CatalogSearchQueryModelBinder(ICatalogSearchQueryFactory 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 + // 'CatalogSearchQuery', good for you :-) You'll get an instance, but null otherwise. + return _factory.Current; + } + + 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/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs new file mode 100644 index 0000000000..c036029c6d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs @@ -0,0 +1,96 @@ +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Modelling +{ + public interface ICatalogSearchQueryAliasMapper + { + /// + /// Clears all cached attribute mappings + /// + void ClearAttributeCache(); + + /// + /// Gets the attribute id by attribute alias + /// + /// Attribute alias + /// Language identifier + /// Attribute identifier + int GetAttributeIdByAlias(string attributeAlias, int languageId = 0); + + /// + /// Gets the attribute option id by option alias + /// + /// Attribute option alias + /// Attribute identifier + /// Language identifier + /// Attribute option identifier + int GetAttributeOptionIdByAlias(string optionAlias, int attributeId, int languageId = 0); + + /// + /// Gets the attribute alias by attribute id + /// + /// Attribute identifier + /// Language identifier + /// Attribute alias + string GetAttributeAliasById(int attributeId, int languageId = 0); + + /// + /// Get the attribute option alias by option id + /// + /// Option identifier + /// Language identifier + /// Attribute option alias + string GetAttributeOptionAliasById(int optionId, int languageId = 0); + + /// + /// Clears all cached variant mappings + /// + void ClearVariantCache(); + + /// + /// Gets the variant id by variant alias + /// + /// Variant alias + /// Language identifier + /// Variant identifier + int GetVariantIdByAlias(string variantAlias, int languageId = 0); + + /// + /// Gets the variant option id by option alias + /// + /// Variant option alias + /// Variant identifier + /// Language identifier + /// Variant option identifier + int GetVariantOptionIdByAlias(string optionAlias, int variantId, int languageId = 0); + + /// + /// Gets the variant alias by variant id + /// + /// Variant identifier + /// Language identifier + /// Variant alias + string GetVariantAliasById(int variantId, int languageId = 0); + + /// + /// Get the variant option alias by value id + /// + /// Option identifier + /// Language identifier + /// Variant option alias + string GetVariantOptionAliasById(int optionId, int languageId = 0); + + /// + /// 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/Catalog/Modelling/ICatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryFactory.cs new file mode 100644 index 0000000000..7b0afdba68 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryFactory.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Services.Search.Modelling +{ + public interface ICatalogSearchQueryFactory + { + /// + /// Creates a instance from the current + /// by looking up corresponding keys in posted form and/or query string + /// + /// The query object + CatalogSearchQuery CreateFromQuery(); + + /// + /// The last created query instance. The MVC model binder uses this property to avoid repeated binding. + /// + CatalogSearchQuery Current { get; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs new file mode 100644 index 0000000000..d39648af11 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Web; +using SmartStore.Collections; +using SmartStore.Core; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Search.Modelling; + +namespace SmartStore.Services.Search.Extensions +{ + public class FacetUrlHelper + { + private readonly ICatalogSearchQueryAliasMapper _catalogAliasMapper; + private readonly IForumSearchQueryAliasMapper _forumAliasMapper; + private readonly IWorkContext _workContext; + private readonly HttpRequestBase _httpRequest; + + private readonly int _languageId; + private readonly string _url; + private readonly QueryString _initialQuery; + + 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" }, + + // Forum. + { FacetGroupKind.Forum, "f" }, + { FacetGroupKind.Customer, "c" }, + { FacetGroupKind.Date, "d" } + }; + + public FacetUrlHelper( + ICatalogSearchQueryAliasMapper catalogAliasMapper, + IForumSearchQueryAliasMapper forumAliasMapper, + IWorkContext workContext, + HttpRequestBase httpRequest) + { + _catalogAliasMapper = catalogAliasMapper; + _forumAliasMapper = forumAliasMapper; + _workContext = workContext; + _httpRequest = httpRequest; + + _languageId = _workContext.WorkingLanguage.Id; + _url = _httpRequest.CurrentExecutionFilePath; + //_initialQuery = new QueryString().FillFromString(_httpRequest.QueryString.ToString(), false); + _initialQuery = QueryString.CurrentUnvalidated; + + // Remove page index (i) from query string + _initialQuery.Remove("i"); + } + + public string Toggle(Facet facet) + { + if (facet.Value.IsSelected) + { + return Remove(facet); + } + else + { + return Add(facet); + } + } + + public string Add(params Facet[] facets) + { + var qs = new QueryString(_initialQuery); + + foreach (var facet in facets) + { + var parts = GetQueryParts(facet); + foreach (var name in parts.AllKeys) + { + qs.Add(name, parts[name], !facet.FacetGroup.IsMultiSelect); + } + } + + return _url + qs.ToString(false); + } + + public string Remove(params Facet[] facets) + { + var qs = new QueryString(_initialQuery); + + foreach (var facet in facets) + { + var parts = GetQueryParts(facet); + foreach (var name in parts.AllKeys) + { + var qsName = name; + + if (!qs.AllKeys.Contains(name)) + { + // Query string does not contain that name. Try the unmapped name. + switch (facet.FacetGroup.Kind) + { + case FacetGroupKind.Attribute: + qsName = "attr" + facet.Value.ParentId; + break; + case FacetGroupKind.Variant: + qsName = "vari" + facet.Value.ParentId; + break; + case FacetGroupKind.Category: + case FacetGroupKind.Brand: + case FacetGroupKind.Price: + case FacetGroupKind.Rating: + case FacetGroupKind.DeliveryTime: + case FacetGroupKind.Availability: + case FacetGroupKind.NewArrivals: + case FacetGroupKind.Forum: + case FacetGroupKind.Customer: + case FacetGroupKind.Date: + qsName = _queryNames[facet.FacetGroup.Kind]; + break; + } + } + + string[] currentValues = null; + + // The query string value is not necessarily equal to the facet value. + // We must skip subsequent lines here to not add the removed value again and again. + if (facet.FacetGroup.Kind != FacetGroupKind.Price && + facet.FacetGroup.Kind != FacetGroupKind.Availability && + facet.FacetGroup.Kind != FacetGroupKind.NewArrivals) + { + currentValues = qs.Get(qsName)?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(); + } + + qs.Remove(qsName); + + if (currentValues != null) + { + var removeValues = parts.GetValues(name); + var newValues = currentValues.Except(removeValues).ToArray(); + if (newValues.Length > 0) + { + newValues.Each(x => qs.Add(name, x, false)); + } + } + } + } + + return _url + qs.ToString(false); + } + + public string GetQueryName(Facet facet) + { + var parts = GetQueryParts(facet); + return parts.GetKey(0); + } + + protected virtual NameValueCollection GetQueryParts(Facet facet) + { + string name = null; + string value = null; + int entityId; + + var group = facet.FacetGroup; + var val = facet.Value; + + var result = new NameValueCollection(2); + + switch (group.Kind) + { + case FacetGroupKind.Attribute: + if (facet.Value.TypeCode == IndexTypeCode.Double) + { + value = "{0}~{1}".FormatInvariant( + val.Value != null ? ((double)val.Value).ToString(CultureInfo.InvariantCulture) : "", + val.UpperValue != null ? ((double)val.UpperValue).ToString(CultureInfo.InvariantCulture) : ""); + } + else + { + entityId = val.Value.Convert(); + value = _catalogAliasMapper.GetAttributeOptionAliasById(entityId, _languageId) ?? "opt" + entityId; + } + name = _catalogAliasMapper.GetAttributeAliasById(val.ParentId, _languageId) ?? "attr" + val.ParentId; + result.Add(name, value); + break; + case FacetGroupKind.Variant: + entityId = val.Value.Convert(); + name = _catalogAliasMapper.GetVariantAliasById(val.ParentId, _languageId) ?? "vari" + val.ParentId; + value = _catalogAliasMapper.GetVariantOptionAliasById(entityId, _languageId) ?? "opt" + entityId; + result.Add(name, value); + break; + case FacetGroupKind.Category: + case FacetGroupKind.Brand: + case FacetGroupKind.Price: + case FacetGroupKind.Rating: + case FacetGroupKind.DeliveryTime: + case FacetGroupKind.Availability: + case FacetGroupKind.NewArrivals: + value = val.ToString(); + if (value.HasValue()) + { + 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 new file mode 100644 index 0000000000..26bb723fba --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -0,0 +1,230 @@ +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 +{ + /// + /// Contains methods that are specifically required for facet processing. + /// + public static class FacetUtility + { + private const double MAX_PRICE = 1000000000; + + private static int[,] _priceThresholds = new int[,] + { + { 10, 5 }, + { 25, 15 }, + { 200, 25 }, + { 500, 50 }, + { 1000, 100 }, + { 2000, 250 }, + { 5000, 500 }, + { 10000, 1000 }, + { 20000, 2500 }, + { 50000, 5000 }, + { 100000, 10000 }, + { 200000, 25000 }, + { 500000, 50000 }, + { 1000000, 100000 }, + { 2000000, 250000 }, + { 5000000, 500000 }, + { 10000000, 1000000 }, + { 20000000, 2500000 }, + { 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) + { + if (price < _priceThresholds[i, 0]) + return price + _priceThresholds[i, 1]; + } + + return MAX_PRICE; + } + + public static double MakePriceEven(double price) + { + if (price == 0.0) + return GetNextPrice(0.0); + + // Get previous threshold for price. + var result = 0.0; + for (var i = 1; i <= _priceThresholds.GetUpperBound(0) && result == 0.0; ++i) + { + if (price < _priceThresholds[i, 0]) + result = _priceThresholds[i - 1, 0]; + } + + while (result < price && result < MAX_PRICE) + { + result = GetNextPrice(result); + } + + return result; + } + + public static List GetLessPriceFacets(List facets, int maxNumber) + { + const double expFactor = 2.0; + const double flatten = 2.0; + + if (facets.Count <= 3) + return facets; + + // Remove too granular facets. + if (facets.Any(x => x.Value.UpperValue != null && (double)x.Value.UpperValue == 25.0)) + { + facets.RemoveFacet(5.0, true); + facets.RemoveFacet(10.0, true); + } + + var result = new List(); + var expIndexes = new HashSet(); + var lastIndex = facets.Count - 1; + + // Get exponential distributed indexes. + for (var i = 0.0; i < lastIndex; ++i) + { + var x = (int)Math.Floor(Math.Pow(expFactor, i / flatten)); + expIndexes.Add(x); + } + + for (var index = 0; index <= lastIndex; ++index) + { + var facet = facets[index]; + + // Always return first, last and selected facets. + if (index == 0 || index == lastIndex || facet.Value.IsSelected) + { + result.Add(facet); + } + else if (expIndexes.Contains(index) && result.Count < maxNumber && index < (lastIndex - 1)) + { + result.Add(facet); + } + } + + return result; + } + + public static IEnumerable GetRatings() + { + var count = 0; + for (double rate = 1.0; rate <= 5.0; ++rate) + { + yield return new FacetValue(rate, IndexTypeCode.Double) + { + DisplayOrder = ++count + }; + } + } + + 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/Extensions/UrlHelperExtensions.cs b/src/Libraries/SmartStore.Services/Search/Extensions/UrlHelperExtensions.cs new file mode 100644 index 0000000000..1f07ae6323 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Extensions/UrlHelperExtensions.cs @@ -0,0 +1,33 @@ +using System.Web.Mvc; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Extensions +{ + public static class UrlHelperExtensions + { + public static string FacetToggle(this UrlHelper urlHelper, Facet facet) + { + var facetUrlHelper = EngineContext.Current.Resolve(); + return facetUrlHelper.Toggle(facet); + } + + public static string FacetAdd(this UrlHelper urlHelper, params Facet[] facets) + { + var facetUrlHelper = EngineContext.Current.Resolve(); + return facetUrlHelper.Add(facets); + } + + public static string FacetRemove(this UrlHelper urlHelper, params Facet[] facets) + { + var facetUrlHelper = EngineContext.Current.Resolve(); + return facetUrlHelper.Remove(facets); + } + + public static string GetFacetQueryName(this UrlHelper urlHelper, Facet facet) + { + var facetUrlHelper = EngineContext.Current.Resolve(); + return facetUrlHelper.GetQueryName(facet); + } + } +} 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/Rendering/FacetTemplateProvider.cs b/src/Libraries/SmartStore.Services/Search/Rendering/FacetTemplateProvider.cs new file mode 100644 index 0000000000..c8e4860470 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Rendering/FacetTemplateProvider.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Rendering +{ + public class FacetTemplateProvider : IFacetTemplateProvider + { + private readonly IEnumerable _selectors; + + public FacetTemplateProvider(IEnumerable selectors) + { + _selectors = selectors; + } + + public RouteInfo GetTemplateRoute(FacetGroup facetGroup) + { + var route = _selectors + .OrderByDescending(x => x.Ordinal) + .Select(x => x.GetTemplateRoute(facetGroup)) + .FirstOrDefault(x => x != null); + + return route; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Rendering/IFacetTemplateProvider.cs b/src/Libraries/SmartStore.Services/Search/Rendering/IFacetTemplateProvider.cs new file mode 100644 index 0000000000..45893675d6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Rendering/IFacetTemplateProvider.cs @@ -0,0 +1,10 @@ +using System; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Rendering +{ + public interface IFacetTemplateProvider + { + RouteInfo GetTemplateRoute(FacetGroup facetGroup); + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Rendering/IFacetTemplateSelector.cs b/src/Libraries/SmartStore.Services/Search/Rendering/IFacetTemplateSelector.cs new file mode 100644 index 0000000000..d7efdbc334 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Rendering/IFacetTemplateSelector.cs @@ -0,0 +1,10 @@ +using System; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Rendering +{ + public interface IFacetTemplateSelector : IOrdered + { + RouteInfo GetTemplateRoute(FacetGroup facetGroup); + } +} 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 f1ea323ff2..2b77fadb24 100644 --- a/src/Libraries/SmartStore.Services/Security/AclService.cs +++ b/src/Libraries/SmartStore.Services/Security/AclService.cs @@ -6,54 +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 { - /// - /// ACL service - /// public partial class AclService : IAclService { - #region Constants - - private const string ACLRECORD_BY_ENTITYID_NAME_KEY = "SmartStore.aclrecord.entityid-name-{0}-{1}"; - private const string ACLRECORD_PATTERN_KEY = "SmartStore.aclrecord."; - - #endregion - - #region Fields + /// + /// 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 bool? _hasActiveAcl; - - #endregion + private readonly ICustomerService _customerService; - #region Ctor + private bool? _hasActiveAcl; - /// - /// Ctor - /// - /// Cache manager - /// Work context - /// ACL record repository - public AclService(ICacheManager cacheManager, IWorkContext workContext, - IRepository aclRecordRepository) + public AclService( + ICacheManager cacheManager, + Work workContext, + IRepository aclRecordRepository, + ICustomerService customerService) { - this._cacheManager = cacheManager; - this._workContext = workContext; - this._aclRecordRepository = aclRecordRepository; - - this.QuerySettings = DbQuerySettings.Default; + _cacheManager = cacheManager; + _workContext = workContext; + _aclRecordRepository = aclRecordRepository; + _customerService = customerService; } - public DbQuerySettings QuerySettings { get; set; } - - #endregion - - #region Members - public bool HasActiveAcl { get @@ -66,26 +50,15 @@ public bool HasActiveAcl } } - /// - /// Deletes an ACL record - /// - /// ACL record public virtual void DeleteAclRecord(AclRecord aclRecord) { - if (aclRecord == null) - throw new ArgumentNullException("aclRecord"); + Guard.NotNull(aclRecord,nameof(aclRecord)); _aclRecordRepository.Delete(aclRecord); - //cache - _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); - } + ClearCacheSegment(aclRecord.EntityName, aclRecord.EntityId); + } - /// - /// Gets an ACL record - /// - /// ACL record identifier - /// ACL record public virtual AclRecord GetAclRecordById(int aclRecordId) { if (aclRecordId == 0) @@ -95,27 +68,20 @@ public virtual AclRecord GetAclRecordById(int aclRecordId) return aclRecord; } - /// - /// Gets ACL records - /// - /// Type - /// Entity - /// ACL records public IList GetAclRecords(T entity) where T : BaseEntity, IAclSupported { - if (entity == null) - throw new ArgumentNullException("entity"); + Guard.NotNull(entity, nameof(entity)); - int entityId = entity.Id; - string entityName = typeof(T).Name; + int entityId = entity.Id; + string entityName = entity.GetEntityName(); return GetAclRecordsFor(entityName, entityId); } public virtual IList GetAclRecordsFor(string entityName, int entityId) { - Guard.ArgumentIsPositive(entityId, "entityId"); - Guard.ArgumentNotEmpty(() => entityName); + Guard.IsPositive(entityId, nameof(entityId)); + Guard.NotEmpty(entityName, nameof(entityName)); var query = from ur in _aclRecordRepository.Table where ur.EntityId == entityId && @@ -125,38 +91,47 @@ 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); - /// - /// Inserts an ACL record - /// - /// ACL record - public virtual void InsertAclRecord(AclRecord aclRecord) + 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) { - if (aclRecord == null) - throw new ArgumentNullException("aclRecord"); + Guard.NotNull(aclRecord, nameof(aclRecord)); - _aclRecordRepository.Insert(aclRecord); + _aclRecordRepository.Insert(aclRecord); - //cache - _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); - } + ClearCacheSegment(aclRecord.EntityName, aclRecord.EntityId); + } - /// - /// Inserts an ACL record - /// - /// Type - /// Entity - /// Customer role id public virtual void InsertAclRecord(T entity, int customerRoleId) where T : BaseEntity, IAclSupported { - if (entity == null) - throw new ArgumentNullException("entity"); - - if (customerRoleId == 0) - throw new ArgumentOutOfRangeException("customerRoleId"); + Guard.NotNull(entity, nameof(entity)); + + if (customerRoleId == 0) + throw new ArgumentOutOfRangeException(nameof(customerRoleId)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); var aclRecord = new AclRecord { @@ -168,90 +143,129 @@ public virtual void InsertAclRecord(T entity, int customerRoleId) where T : B InsertAclRecord(aclRecord); } - /// - /// Updates the ACL record - /// - /// ACL record public virtual void UpdateAclRecord(AclRecord aclRecord) { - if (aclRecord == null) - throw new ArgumentNullException("aclRecord"); + Guard.NotNull(aclRecord, nameof(aclRecord)); - _aclRecordRepository.Update(aclRecord); + _aclRecordRepository.Update(aclRecord); - _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); - } + ClearCacheSegment(aclRecord.EntityName, aclRecord.EntityId); + } - /// - /// Find customer role identifiers with granted access - /// - /// Type - /// Entity - /// Customer role identifiers - public virtual int[] GetCustomerRoleIdsWithAccess(T entity) where T : BaseEntity, IAclSupported - { - if (entity == null) - throw new ArgumentNullException("entity"); + public virtual int[] GetCustomerRoleIdsWithAccessTo(string entityName, int entityId) + { + Guard.NotEmpty(entityName, nameof(entityName)); - int entityId = entity.Id; - string entityName = typeof(T).Name; + if (entityId <= 0) + return new int[0]; - string key = string.Format(ACLRECORD_BY_ENTITYID_NAME_KEY, entityId, entityName); - return _cacheManager.Get(key, () => - { - var query = from ur in _aclRecordRepository.Table - where ur.EntityId == entityId && - ur.EntityName == entityName - select ur.CustomerRoleId; - var result = query.ToArray(); - //little hack here. nulls aren't cacheable so set it to "" - if (result == null) - result = new int[0]; - return result; - }); - } + var cacheSegment = GetCacheSegment(entityName, entityId); - /// - /// Authorize ACL permission - /// - /// Type - /// Wntity - /// true - authorized; otherwise, false - public virtual bool Authorize(T entity) where T : BaseEntity, IAclSupported - { - return Authorize(entity, _workContext.CurrentCustomer); - } + if (!cacheSegment.TryGetValue(entityId, out var roleIds)) + { + return Array.Empty(); + } - /// - /// Authorize ACL permission - /// - /// Type - /// Wntity - /// Customer - /// true - authorized; otherwise, false - public virtual bool Authorize(T entity, Customer customer) where T : BaseEntity, IAclSupported - { - if (entity == null) - return false; + return roleIds; + } + + public bool Authorize(string entityName, int entityId) + { + return Authorize(entityName, entityId, _workContext.Value.CurrentCustomer?.CustomerRoles); + } - if (customer == null) - return false; + public virtual bool Authorize(string entityName, int entityId, IEnumerable roles) + { + Guard.NotEmpty(entityName, nameof(entityName)); - if (QuerySettings.IgnoreAcl) + if (entityId <= 0) + return false; + + if (!HasActiveAcl) return true; - if (!entity.SubjectToAcl) - return true; + if (roles == null) + return false; + + foreach (var role in roles) + { + if (!role.Active) + continue; - foreach (var role1 in customer.CustomerRoles.Where(cr => cr.Active)) - foreach (var role2Id in GetCustomerRoleIdsWithAccess(entity)) - if (role1.Id == role2Id) - //yes, we have such permission - return true; + foreach (var role2Id in GetCustomerRoleIdsWithAccessTo(entityName, entityId)) + { + if (role.Id == role2Id) + { + // Yes, we have such permission + return true; + } + } + } - //no permission found - return false; - } - #endregion - } + // 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 6d18ce30f4..5130dcc09f 100644 --- a/src/Libraries/SmartStore.Services/Security/IAclService.cs +++ b/src/Libraries/SmartStore.Services/Security/IAclService.cs @@ -2,11 +2,8 @@ using System.Collections.Generic; using System.Linq; using SmartStore.Core; -using SmartStore.Core.Caching; -using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Seo; namespace SmartStore.Services.Security { @@ -49,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 @@ -69,29 +74,115 @@ public partial interface IAclService /// ACL record void UpdateAclRecord(AclRecord aclRecord); - /// - /// Find customer role identifiers with granted access - /// - /// Type - /// Wntity - /// Customer role identifiers - int[] GetCustomerRoleIdsWithAccess(T entity) where T : BaseEntity, IAclSupported; + /// + /// Find customer role identifiers with granted access + /// + /// Entity name to check permission for + /// Entity id to check permission for + /// Customer role identifiers + int[] GetCustomerRoleIdsWithAccessTo(string entityName, int entityId); - /// - /// Authorize ACL permission - /// - /// Type - /// Wntity - /// true - authorized; otherwise, false - bool Authorize(T entity) where T : BaseEntity, IAclSupported; + /// + /// Authorize ACL permission + /// + /// Type + /// Entity name to check permission for + /// Entity id to check permission for + /// true - authorized; otherwise, false + bool Authorize(string entityName, int entityId); - /// - /// Authorize ACL permission - /// - /// Type - /// Wntity - /// Customer - /// true - authorized; otherwise, false - bool Authorize(T entity, Customer customer) where T : BaseEntity, IAclSupported; - } + ///// + ///// 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 + /// + /// Entity name to check permission for + /// Entity id to check permission for + /// Roles to check access permission for. Inactive roles will be skipped. + /// true - authorized; otherwise, false + bool Authorize(string entityName, int entityId, IEnumerable roles); + } + + public static class IAclServiceExtensions + { + /// + /// Find customer role identifiers with granted access + /// + /// Type + /// Entity + /// Customer role identifiers + public static int[] GetCustomerRoleIdsWithAccessTo(this IAclService aclService, T entity) where T : BaseEntity, IAclSupported + { + if (entity == null) + return new int[0]; + + 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); + } + + /// + /// Authorize ACL permission + /// + /// Type + /// Entity + /// true - authorized; otherwise, false + public static bool Authorize(this IAclService aclService, T entity) where T : BaseEntity, IAclSupported + { + if (entity == null) + return false; + + if (!entity.SubjectToAcl) + return true; + + return aclService.Authorize(entity.GetEntityName(), entity.Id); + } + + /// + /// Authorize ACL permission + /// + /// Type + /// Entity + /// Customer + /// true - authorized; otherwise, false + public static bool Authorize(this IAclService aclService, T entity, Customer customer) where T : BaseEntity, IAclSupported + { + if (entity == null) + return false; + + if (!entity.SubjectToAcl) + return true; + + 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 a3ada9721a..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 /// @@ -23,22 +22,14 @@ public partial class PermissionService : IPermissionService /// {0} : customer role id /// {1} : permission system name /// - private const string PERMISSIONS_ALLOWED_KEY = "SmartStore.permission.allowed-{0}-{1}"; - private const string PERMISSIONS_PATTERN_KEY = "SmartStore.permission."; - #endregion - - #region Fields + private const string PERMISSIONS_ALLOWED_KEY = "permission:allowed-{0}-{1}"; + private const string PERMISSIONS_PATTERN_KEY = "permission:*"; 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 4f1032077a..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" }; @@ -35,8 +36,6 @@ public partial class StandardPermissionProvider : IPermissionProvider public static readonly PermissionRecord ManageShippingSettings = new PermissionRecord { Name = "Admin area. Manage Shipping Settings", SystemName = "ManageShippingSettings", Category = "Configuration" }; public static readonly PermissionRecord ManageCurrencies = new PermissionRecord { Name = "Admin area. Manage Currencies", SystemName = "ManageCurrencies", Category = "Configuration" }; public static readonly PermissionRecord ManageDeliveryTimes = new PermissionRecord { Name = "Admin area. Manage Delivery Times", SystemName = "ManageDeliveryTimes", Category = "Configuration" }; - public static readonly PermissionRecord ManageQuantityUnits = new PermissionRecord { Name = "Admin area. Manage Quantity Units", SystemName = "ManageQuantityUnits", Category = "Configuration" }; - public static readonly PermissionRecord ManageContentSlider = new PermissionRecord { Name = "Admin area. Manage Content Slider", SystemName = "ManageContentSlider", Category = "Configuration" }; public static readonly PermissionRecord ManageThemes = new PermissionRecord { Name = "Admin area. Manage Themes", SystemName = "ManageThemes", Category = "Configuration" }; public static readonly PermissionRecord ManageMeasures = new PermissionRecord { Name = "Admin area. Manage Measures", SystemName = "ManageMeasures", Category = "Configuration" }; public static readonly PermissionRecord ManageActivityLog = new PermissionRecord { Name = "Admin area. Manage Activity Log", SystemName = "ManageActivityLog", Category = "Configuration" }; @@ -81,6 +80,7 @@ public virtual IEnumerable GetPermissions() ManageBlog, ManageWidgets, ManageTopics, + ManageMenus, ManageForums, ManageMessageTemplates, ManageCountries, @@ -92,7 +92,6 @@ public virtual IEnumerable GetPermissions() ManageShippingSettings, ManageCurrencies, ManageDeliveryTimes, - ManageContentSlider, ManageThemes, ManageMeasures, ManageActivityLog, @@ -141,6 +140,7 @@ public virtual IEnumerable GetDefaultPermissions() ManageBlog, ManageWidgets, ManageTopics, + ManageMenus, ManageForums, ManageMessageTemplates, ManageCountries, @@ -152,7 +152,6 @@ public virtual IEnumerable GetDefaultPermissions() ManageShippingSettings, ManageCurrencies, ManageDeliveryTimes, - ManageContentSlider, ManageThemes, ManageMeasures, ManageActivityLog, diff --git a/src/Libraries/SmartStore.Services/Seo/BaseSitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/BaseSitemapGenerator.cs deleted file mode 100644 index e4ae0b24f7..0000000000 --- a/src/Libraries/SmartStore.Services/Seo/BaseSitemapGenerator.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Web.Mvc; -using System.Xml; -using SmartStore.Core; -using SmartStore.Core.Data; - -namespace SmartStore.Services.Seo -{ - /// - /// Represents a base sitemap generator - /// - public abstract partial class BaseSitemapGenerator : ISitemapGenerator - { - #region Fields - - private const string DateFormat = @"yyyy-MM-dd"; - private XmlTextWriter _writer; - - #endregion - - #region Utilities - - protected abstract void GenerateUrlNodes(UrlHelper urlHelper); - - protected void WriteUrlLocation(string url, UpdateFrequency updateFrequency, DateTime lastUpdated) - { - if (url.IsEmpty()) - return; - - string loc = XmlHelper.XmlEncode(url); - if (url.IsEmpty()) - return; - - _writer.WriteStartElement("url"); - _writer.WriteElementString("loc", loc); - //_writer.WriteElementString("changefreq", updateFrequency.ToString().ToLowerInvariant()); - _writer.WriteElementString("lastmod", lastUpdated.ToString(DateFormat)); - _writer.WriteEndElement(); - } - - #endregion - - #region Methods - - public string Generate(UrlHelper urlHelper) - { - using (var stream = new MemoryStream()) - { - Generate(urlHelper, stream); - return Encoding.UTF8.GetString(stream.ToArray()); - } - } - - public void Generate(UrlHelper urlHelper, Stream stream) - { - using (var scope = new DbContextScope(autoDetectChanges: false, forceNoTracking: true)) - { - _writer = new XmlTextWriter(stream, Encoding.UTF8); - _writer.Formatting = Formatting.Indented; - _writer.WriteStartDocument(); - _writer.WriteStartElement("urlset"); - _writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"); - _writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); - _writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"); - - GenerateUrlNodes(urlHelper); - - _writer.WriteEndElement(); - _writer.Close(); - } - } - - #endregion - - } -} diff --git a/src/Libraries/SmartStore.Services/Seo/ISitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/ISitemapGenerator.cs deleted file mode 100644 index 1073d291f4..0000000000 --- a/src/Libraries/SmartStore.Services/Seo/ISitemapGenerator.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.IO; -using System.Web.Mvc; - -namespace SmartStore.Services.Seo -{ - /// - /// Represents a sitemap generator - /// - public partial interface ISitemapGenerator - { - /// - /// This will build an xml sitemap for better index with search engines. - /// See http://en.wikipedia.org/wiki/Sitemaps for more information. - /// - /// URL helper - /// Sitemap.xml as string - string Generate(UrlHelper urlHelper); - - /// - /// This will build an xml sitemap for better index with search engines. - /// See http://en.wikipedia.org/wiki/Sitemaps for more information. - /// - /// URL helper - /// Stream of sitemap. - void Generate(UrlHelper urlHelper, Stream stream); - - } -} diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index d3bfa5a04d..639dc8efc1 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -9,7 +9,7 @@ namespace SmartStore.Services.Seo /// /// Provides information about URL records /// - public partial interface IUrlRecordService + public partial interface IUrlRecordService : IScopedService { /// /// Deletes an URL record @@ -63,6 +63,39 @@ public partial interface IUrlRecordService /// 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 new file mode 100644 index 0000000000..5e833a086e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Seo/IXmlSitemapGenerator.cs @@ -0,0 +1,51 @@ +using SmartStore.Services.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace SmartStore.Services.Seo +{ + /// + /// Represents a sitemap generator + /// + public partial interface IXmlSitemapGenerator + { + /// + /// Gets the sitemap partition + /// + /// + /// 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 partition + Task GetSitemapPartAsync(int index = 0); + + /// + /// 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. + /// + bool IsRebuilding(int storeId, int languageId); + + /// + /// Indicates whether the sitemap has been generated and cached. + /// + 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 29c29d13ef..3a2c115b85 100644 --- a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs +++ b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs @@ -15,8 +15,7 @@ namespace SmartStore.Services.Seo { public static class SeoExtensions { - - #region Product tag + #region Entities /// /// Gets product tag SE (search engine) name @@ -37,27 +36,33 @@ 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; + Guard.NotNull(productTag, nameof(productTag)); + + return GetSeName((string)productTag.GetLocalized(x => x.Name, languageId)); } - #endregion + /// + /// Gets blog post SE (search engine) name + /// + /// Blog post + /// Blog post SE (search engine) name + public static string GetSeName(this BlogPost blogPost) + { + Guard.NotNull(blogPost, nameof(blogPost)); - #region Blog / news + return GetSeName(blogPost.Title); + } /// /// Gets blog post SE (search engine) name /// /// Blog post /// Blog post SE (search engine) name - public static string GetSeName(this BlogPost blogPost) + public static string GetSeName(this BlogPostTag blogPostTag) { - if (blogPost == null) - throw new ArgumentNullException("blogPost"); - string seName = GetSeName(blogPost.Title); - return seName; + Guard.NotNull(blogPostTag, nameof(blogPostTag)); + + return GetSeName(blogPostTag.Name); } /// @@ -67,26 +72,21 @@ public static string GetSeName(this BlogPost blogPost) /// 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; - } + Guard.NotNull(newsItem, nameof(newsItem)); - #endregion - - #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; @@ -98,22 +98,45 @@ public static string GetSeName(this ForumTopic forumTopic) return seName; } - #endregion + /// + /// Get search engine name for a category node + /// + /// Node + /// Search engine name + public static string GetSeName(this ICategoryNode node) + { + Guard.NotNull(node, nameof(node)); - #region General + return GetSeName( + "Category", + node.Id, + EngineContext.Current.Resolve().WorkingLanguage.Id, + EngineContext.Current.Resolve(), + EngineContext.Current.Resolve()); + } - /// - /// Get search engine name - /// - /// Entity type - /// Entity - /// Search engine name - public static string GetSeName(this T entity) + #endregion + + #region Generic + + /// + /// Get search engine name + /// + /// Entity type + /// Entity + /// Search engine name + public static string GetSeName(this T entity) where T : BaseEntity, ISlugSupported { - var workContext = EngineContext.Current.Resolve(); - return GetSeName(entity, workContext.WorkingLanguage.Id); - } + Guard.NotNull(entity, nameof(entity)); + + return GetSeName( + entity.GetEntityName(), + entity.Id, + EngineContext.Current.Resolve().WorkingLanguage.Id, + EngineContext.Current.Resolve(), + EngineContext.Current.Resolve()); + } /// /// Get search engine name @@ -124,11 +147,17 @@ public static string GetSeName(this T entity) /// A value indicating whether to return default value (if language specified one is not found) /// A value indicating whether to ensure that we have at least two published languages; otherwise, load only default value /// Search engine name - public static string GetSeName(this T entity, int languageId, bool returnDefaultValue = true, bool ensureTwoPublishedLanguages = true) + public static string GetSeName(this T entity, + int languageId, + bool returnDefaultValue = true, + bool ensureTwoPublishedLanguages = true) where T : BaseEntity, ISlugSupported { + Guard.NotNull(entity, nameof(entity)); + return GetSeName( - entity, + entity.GetEntityName(), + entity.Id, languageId, EngineContext.Current.Resolve(), EngineContext.Current.Resolve(), @@ -153,11 +182,28 @@ public static string GetSeName(this T entity, bool ensureTwoPublishedLanguages = true) where T : BaseEntity, ISlugSupported { - if (entity == null) - throw new ArgumentNullException("entity"); + Guard.NotNull(entity, nameof(entity)); + return GetSeName( + entity.GetEntityName(), + entity.Id, + languageId, + urlRecordService, + languageService, + returnDefaultValue, + ensureTwoPublishedLanguages); + } + + private static string GetSeName( + string entityName, + int entityId, + int languageId, + IUrlRecordService urlRecordService, + ILanguageService languageService, + bool returnDefaultValue = true, + bool ensureTwoPublishedLanguages = true) + { string result = string.Empty; - string entityName = typeof(T).Name; if (languageId > 0) { @@ -171,27 +217,28 @@ public static string GetSeName(this T entity, // localized value if (loadLocalizedValue) { - result = urlRecordService.GetActiveSlug(entity.Id, entityName, languageId); + result = urlRecordService.GetActiveSlug(entityId, entityName, languageId); } } + // set default value if required if (String.IsNullOrEmpty(result) && returnDefaultValue) { - result = urlRecordService.GetActiveSlug(entity.Id, entityName, 0); + result = urlRecordService.GetActiveSlug(entityId, entityName, 0); } return result; } - /// - /// Validate search engine name - /// - /// Entity - /// Search engine name to validate - /// User-friendly name used to generate sename - /// Ensreu that sename is not empty - /// Valid sename - public static string ValidateSeName(this T entity, string seName, string name, bool ensureNotEmpty, int? languageId = null) + /// + /// Validate search engine name + /// + /// Entity + /// Search engine name to validate + /// User-friendly name used to generate sename + /// Ensreu that sename is not empty + /// Valid sename + public static string ValidateSeName(this T entity, string seName, string name, bool ensureNotEmpty, int? languageId = null) where T : BaseEntity, ISlugSupported { return entity.ValidateSeName( @@ -213,11 +260,9 @@ public static string ValidateSeName(this T entity, Func extraSlugLookup = null) where T : BaseEntity, ISlugSupported { - Guard.ArgumentNotNull(() => urlRecordService); - Guard.ArgumentNotNull(() => seoSettings); - - if (entity == null) - throw new ArgumentNullException("entity"); + Guard.NotNull(urlRecordService, nameof(urlRecordService)); + Guard.NotNull(seoSettings, nameof(seoSettings)); + Guard.NotNull(entity, nameof(entity)); // use name if sename is not specified if (String.IsNullOrWhiteSpace(seName) && !String.IsNullOrWhiteSpace(name)) @@ -253,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) @@ -305,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/SitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs deleted file mode 100644 index 497555026d..0000000000 --- a/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Linq; -using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Services.Catalog; -using SmartStore.Services.Topics; -using SmartStore.Services.Seo; -using SmartStore.Core.Domain.Security; - -namespace SmartStore.Services.Seo -{ - /// - /// Represents a sitemap generator - /// - public partial class SitemapGenerator : BaseSitemapGenerator - { - private readonly IStoreContext _storeContext; - private readonly ICategoryService _categoryService; - private readonly IProductService _productService; - private readonly IManufacturerService _manufacturerService; - private readonly ITopicService _topicService; - private readonly CommonSettings _commonSettings; - private readonly IWebHelper _webHelper; - private readonly SecuritySettings _securitySettings; - - public SitemapGenerator( - IStoreContext storeContext, - ICategoryService categoryService, - IProductService productService, - IManufacturerService manufacturerService, - ITopicService topicService, - CommonSettings commonSettings, - IWebHelper webHelper, - SecuritySettings securitySettings) - { - this._storeContext = storeContext; - this._categoryService = categoryService; - this._productService = productService; - this._manufacturerService = manufacturerService; - this._topicService = topicService; - this._commonSettings = commonSettings; - this._webHelper = webHelper; - this._securitySettings = securitySettings; - } - - protected override void GenerateUrlNodes(UrlHelper urlHelper) - { - if (_commonSettings.SitemapIncludeCategories) - { - WriteCategories(urlHelper, 0); - } - - if (_commonSettings.SitemapIncludeManufacturers) - { - WriteManufacturers(urlHelper); - } - - if (_commonSettings.SitemapIncludeProducts) - { - WriteProducts(urlHelper); - } - - if (_commonSettings.SitemapIncludeTopics) - { - WriteTopics(urlHelper); - } - } - - private void WriteCategories(UrlHelper urlHelper, int parentCategoryId) - { - var categories = _categoryService.GetAllCategories(showHidden: false); - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - foreach (var category in categories) - { - var url = urlHelper.RouteUrl("Category", new { SeName = category.GetSeName() }, protocol); - var updateFrequency = UpdateFrequency.Weekly; - var updateTime = category.UpdatedOnUtc; - WriteUrlLocation(url, updateFrequency, updateTime); - } - } - - private void WriteManufacturers(UrlHelper urlHelper) - { - var manufacturers = _manufacturerService.GetAllManufacturers(false); - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - foreach (var manufacturer in manufacturers) - { - var url = urlHelper.RouteUrl("Manufacturer", new { SeName = manufacturer.GetSeName() }, protocol); - var updateFrequency = UpdateFrequency.Weekly; - var updateTime = manufacturer.UpdatedOnUtc; - WriteUrlLocation(url, updateFrequency, updateTime); - } - } - - private void WriteProducts(UrlHelper urlHelper) - { - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - - var ctx = new ProductSearchContext - { - OrderBy = ProductSortingEnum.CreatedOn, - PageSize = 500, - StoreId = _storeContext.CurrentStoreIdIfMultiStoreMode, - VisibleIndividuallyOnly = true - }; - - for (ctx.PageIndex = 0; ctx.PageIndex < 9999999; ++ctx.PageIndex) - { - var products = _productService.SearchProducts(ctx); - - foreach (var product in products) - { - var url = urlHelper.RouteUrl("Product", new { SeName = product.GetSeName() }, protocol); - var updateFrequency = UpdateFrequency.Weekly; - var updateTime = product.UpdatedOnUtc; - WriteUrlLocation(url, updateFrequency, updateTime); - } - - if (!products.HasNextPage) - break; - } - } - - private void WriteTopics(UrlHelper urlHelper) - { - var topics = _topicService.GetAllTopics(_storeContext.CurrentStore.Id).ToList().FindAll(t => t.IncludeInSitemap && !t.RenderAsWidget); - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - foreach (var topic in topics) - { - var url = urlHelper.RouteUrl("Topic", new { SystemName = topic.SystemName }, protocol); - var updateFrequency = UpdateFrequency.Weekly; - var updateTime = DateTime.UtcNow; - WriteUrlLocation(url, updateFrequency, updateTime); - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs b/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs new file mode 100644 index 0000000000..5e07f2a9fb --- /dev/null +++ b/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 : AsyncTask + { + private readonly IStoreService _storeService; + private readonly ILanguageService _languageService; + private readonly IXmlSitemapGenerator _generator; + private readonly SeoSettings _seoSettings; + + public RebuildXmlSitemapTask( + IStoreService storeService, + ILanguageService languageService, + IXmlSitemapGenerator generator, + SeoSettings seoSettings) + { + _storeService = storeService; + _languageService = languageService; + _generator = generator; + _seoSettings = seoSettings; + } + + public override async Task ExecuteAsync(TaskExecutionContext ctx) + { + var stores = _storeService.GetAllStores(); + + foreach (var store in stores) + { + var languages = _languageService.GetAllLanguages(false, store.Id); + var buildContext = new XmlSitemapBuildContext(store, languages.ToArray()) + { + CancellationToken = ctx.CancellationToken, + ProgressCallback = OnProgress + }; + + await _generator.RebuildAsync(buildContext); + } + + void OnProgress(int value, int max, string msg) + { + ctx.SetProgress(value, max, msg, true); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Seo/UpdateFrequency.cs b/src/Libraries/SmartStore.Services/Seo/UpdateFrequency.cs deleted file mode 100644 index efb1737ed8..0000000000 --- a/src/Libraries/SmartStore.Services/Seo/UpdateFrequency.cs +++ /dev/null @@ -1,38 +0,0 @@ - -namespace SmartStore.Services.Seo -{ - /// - /// Represents a sitemap update frequency - /// - public enum UpdateFrequency - { - /// - /// Always - /// - Always, - /// - /// Hourly - /// - Hourly, - /// - /// Daily - /// - Daily, - /// - /// Weekly - /// - Weekly, - /// - /// Monthly - /// - Monthly, - /// - /// Yearly - /// - Yearly, - /// - /// Never - /// - Never - } -} diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index 38fe4a7bda..df8bad3de9 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -2,57 +2,92 @@ 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 { - /// - /// Provides information about URL records - /// - public partial class UrlRecordService : IUrlRecordService + public partial class UrlRecordService : ScopedServiceBase, IUrlRecordService { - #region Constants + /// + /// 0 = segment (EntityName.IdRange), 1 = language id + /// + 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; - // {0} = id, {1} = name, {2} = language - private const string URLRECORD_KEY = "SmartStore.urlrecord.{0}-{1}-{2}"; - private const string URLRECORD_ALL_ACTIVESLUGS_KEY = "SmartStore.urlrecord.all-active-slugs"; - private const string URLRECORD_PATTERN_KEY = "SmartStore.urlrecord."; + private readonly IDictionary _prefetchedCollections; + private static int _lastCacheSegmentSize = -1; - #endregion + public UrlRecordService( + ICacheManager cacheManager, + IRepository urlRecordRepository, + SeoSettings seoSettings, + PerformanceSettings performanceSettings) + { + _cacheManager = cacheManager; + _urlRecordRepository = urlRecordRepository; + _seoSettings = seoSettings; + _performanceSettings = performanceSettings; - #region Fields + _prefetchedCollections = new Dictionary(StringComparer.OrdinalIgnoreCase); - private readonly IRepository _urlRecordRepository; - private readonly ICacheManager _cacheManager; - private readonly SeoSettings _seoSettings; + ValidateCacheState(); + } - #endregion + private void ValidateCacheState() + { + // Ensure that after a segment size change the cache segments are invalidated. + var size = _performanceSettings.CacheSegmentSize; + var changed = _lastCacheSegmentSize == -1; - #region Ctor + if (size <= 0) + { + _performanceSettings.CacheSegmentSize = size = 1; + } - public UrlRecordService(ICacheManager cacheManager, IRepository urlRecordRepository, SeoSettings seoSettings) - { - this._cacheManager = cacheManager; - this._urlRecordRepository = urlRecordRepository; - this._seoSettings = seoSettings; - } + if (_lastCacheSegmentSize > 0 && _lastCacheSegmentSize != size) + { + _cacheManager.RemoveByPattern(URLRECORD_SEGMENT_PATTERN); + changed = true; + } - #endregion + if (changed) + { + Interlocked.Exchange(ref _lastCacheSegmentSize, size); + } + } - #region Methods + protected override void OnClearCache() + { + _cacheManager.Remove(URLRECORD_ALL_PATTERN); + } - public virtual void DeleteUrlRecord(UrlRecord urlRecord) + public virtual void DeleteUrlRecord(UrlRecord urlRecord) { - if (urlRecord == null) - throw new ArgumentNullException("urlRecord"); + Guard.NotNull(urlRecord, nameof(urlRecord)); - _urlRecordRepository.Delete(urlRecord); + try + { + // cache + ClearCacheSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); + HasChanges = true; - _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); - } + // db + _urlRecordRepository.Delete(urlRecord); + } + catch { } + } public virtual UrlRecord GetUrlRecordById(int urlRecordId) { @@ -77,25 +112,35 @@ public virtual IList GetUrlRecordsByIds(int[] urlRecordIds) public virtual void InsertUrlRecord(UrlRecord urlRecord) { - if (urlRecord == null) - throw new ArgumentNullException("urlRecord"); + Guard.NotNull(urlRecord, nameof(urlRecord)); - _urlRecordRepository.Insert(urlRecord); + try + { + // db + _urlRecordRepository.Insert(urlRecord); + HasChanges = true; - //cache - _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); + // cache + ClearCacheSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); + } + catch { } } public virtual void UpdateUrlRecord(UrlRecord urlRecord) { - if (urlRecord == null) - throw new ArgumentNullException("urlRecord"); + Guard.NotNull(urlRecord, nameof(urlRecord)); - _urlRecordRepository.Update(urlRecord); + try + { + // db + _urlRecordRepository.Update(urlRecord); + HasChanges = true; - //cache - _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); - } + // cache + ClearCacheSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); + } + catch { } + } public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, int? languageId, bool? isActive) { @@ -124,7 +169,7 @@ public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSiz public virtual IList GetUrlRecordsFor(string entityName, int entityId, bool activeOnly = false) { - Guard.ArgumentNotEmpty(() => entityName); + Guard.NotEmpty(entityName, nameof(entityName)); var query = from ur in _urlRecordRepository.Table where ur.EntityId == entityId && @@ -139,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. @@ -148,30 +270,48 @@ 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); + } + string slug = null; if (_seoSettings.LoadAllUrlAliasesOnStartup) { 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)) @@ -181,30 +321,37 @@ orderby x.Id descending } else { - string cacheKey = string.Format(URLRECORD_KEY, entityId, entityName, languageId); - slug = _cacheManager.Get(cacheKey, () => + var slugs = GetCacheSegment(entityName, entityId, languageId); + + if (!slugs.TryGetValue(entityId, out slug)) { - var query = from ur in _urlRecordRepository.Table - where ur.EntityId == entityId && - ur.EntityName == entityName && - ur.LanguageId == languageId && - ur.IsActive - orderby ur.Id descending - select ur.Slug; - return query.FirstOrDefault() ?? string.Empty; - }); + return string.Empty; + } } return slug; } - public virtual UrlRecord SaveSlug(T entity, string slug, int languageId) where T : BaseEntity, ISlugSupported + protected string GetActiveSlugUncached(int entityId, string entityName, int languageId) + { + var query = from x in _urlRecordRepository.TableUntracked + where + x.EntityId == entityId && + x.EntityName == entityName && + x.LanguageId == languageId && + x.IsActive + orderby x.Id descending + select x.Slug; + + return query.FirstOrDefault().EmptyNull(); + } + + public virtual UrlRecord SaveSlug(T entity, string slug, int languageId) where T : BaseEntity, ISlugSupported { - if (entity == null) - throw new ArgumentNullException("entity"); + 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 @@ -360,6 +507,78 @@ public virtual int CountSlugsPerEntity(string entityName, int entityId) return count; } - #endregion - } + protected virtual IDictionary GetCacheSegment(string entityName, int entityId, int languageId) + { + Guard.NotEmpty(entityName, nameof(entityName)); + + var segmentKey = GetSegmentKeyPart(entityName, entityId, out var minEntityId, out var maxEntityId); + var cacheKey = BuildCacheSegmentKey(segmentKey, languageId); + + return _cacheManager.Get(cacheKey, () => + { + using (new DbContextScope(proxyCreation: false, lazyLoading: false)) + { + 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; + } + }, independent: true); + } + + /// + /// Clears the cached segment from the cache + /// + protected virtual void ClearCacheSegment(string entityName, int entityId, int? languageId = null) + { + if (IsInScope) + return; + + var segmentKey = GetSegmentKeyPart(entityName, entityId); + + if (languageId.HasValue && languageId.Value > 0) + { + _cacheManager.Remove(BuildCacheSegmentKey(segmentKey, languageId.Value)); + } + else + { + _cacheManager.RemoveByPattern(URLRECORD_SEGMENT_PATTERN.FormatInvariant(segmentKey)); + } + + // always delete this (in case when LoadAllOnStartup is true) + _cacheManager.Remove(URLRECORD_ALL_ACTIVESLUGS_KEY); + } + + private string BuildCacheSegmentKey(string segment, int languageId) + { + return String.Format(URLRECORD_SEGMENT_KEY, segment, languageId); + } + + 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(_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 new file mode 100644 index 0000000000..17a8e5356e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs @@ -0,0 +1,837 @@ +using System; +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; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Catalog; +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 + { + 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. + /// + private const int MaximumSiteMapCount = 50000; + + /// + /// 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 2.000 nodes keeps the file size below 10MB. + /// + 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 IProductService _productService; + private readonly IManufacturerService _manufacturerService; + private readonly ITopicService _topicService; + private readonly ILanguageService _languageService; + private readonly ICustomerService _customerService; + private readonly ICatalogSearchService _catalogSearchService; + 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, + IManufacturerService manufacturerService, + ITopicService topicService, + ILanguageService languageService, + ICustomerService customerService, + ICatalogSearchService catalogSearchService, + IUrlRecordService urlRecordService, + ICommonServices services, + ILockFileManager lockFileManager, + UrlHelper urlHelper) + { + _categoryService = categoryService; + _productService = productService; + _manufacturerService = manufacturerService; + _topicService = topicService; + _languageService = languageService; + _customerService = customerService; + _catalogSearchService = catalogSearchService; + _urlRecordService = urlRecordService; + _services = services; + _lockFileManager = lockFileManager; + _urlHelper = urlHelper; + + _tenantFolder = _services.ApplicationEnvironment.TenantFolder; + _baseDir = _tenantFolder.Combine("Sitemaps"); + + Logger = NullLogger.Instance; + + } + + public ILogger Logger { get; set; } + + public virtual async Task GetSitemapPartAsync(int index = 0) + { + return await GetSitemapPartAsync(index, false); + } + + private async Task GetSitemapPartAsync(int index, bool isRetry) + { + 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); + } + + private bool SitemapFileExists(int storeId, int languageId, int index, out string path, out string name) + { + path = BuildSitemapFilePath(storeId, languageId, index, out name); + + // Does not work reliably with symlinks due to framework caching + //var exists = _tenantFolder.FileExists(path); + + 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 virtual async Task RebuildAsync(XmlSitemapBuildContext ctx) + { + Guard.NotNull(ctx, nameof(ctx)); + + var languageData = new Dictionary(); + + foreach (var language in ctx.Languages) + { + var lockFilePath = GetLockFilePath(ctx.Store.Id, language.Id); + + if (_lockFileManager.TryAcquireLock(lockFilePath, out var lockFile)) + { + // Process only languages that are unlocked right now + // It is possible that an HTTP request triggered the generation + // of a language specific sitemap. + + var sitemapDir = BuildSitemapDirPath(ctx.Store.Id, language.Id); + var data = new LanguageData + { + 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; + } + } + + 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(); + + 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()) + { + var indexDocument = CreateSitemapIndexDocument(indexNodes[data.Language.Id]); + await SaveTempAsync(new List { indexDocument }, data, 0); + } + + // Save finally (actually renames temp folder) + SaveFinal(data); + } + } + finally + { + // Undo impersonation + _services.WorkContext.CurrentCustomer = prevCustomer; + sitemaps.Clear(); + + foreach (var data in languageData.Values) + { + if (_tenantFolder.DirectoryExists(data.TempDir)) + { + _tenantFolder.TryDeleteDirectory(data.TempDir); + } + } + + //GC.Collect(); + //GC.WaitForPendingFinalizers(); + } + } + } + + private string BuildBaseUrl(Store store, Language language) + { + var host = _services.StoreService.GetHost(store).EnsureEndsWith("/"); + + var locSettings = _services.Settings.LoadSetting(store.Id); + if (locSettings.SeoFriendlyUrlsForLanguagesEnabled) + { + var defaultLangId = _languageService.GetDefaultLanguageId(store.Id); + if (language.Id != defaultLangId || locSettings.DefaultLanguageRedirectBehaviour < DefaultLanguageRedirectBehaviour.StripSeoCode) + { + host += language.GetTwoLetterISOLanguageName() + "/"; + } + } + + return host; + } + + 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) + { + // Delete current sitemap dir + _tenantFolder.TryDeleteDirectory(data.FinalDir); + + var source = _tenantFolder.MapPath(data.TempDir); + var dest = _tenantFolder.MapPath(data.FinalDir); + + // 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++; + } + } + + 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) + { + yield return new NamedEntity { EntityName = "Category", Id = x.Id, LastMod = x.UpdatedOnUtc }; + } + } + + if (queries.Manufacturers != null) + { + var manufacturers = queries.Manufacturers.Select(x => new { x.Id, x.UpdatedOnUtc }).ToList(); + foreach (var x in manufacturers) + { + yield return new NamedEntity { EntityName = "Manufacturer", Id = x.Id, LastMod = x.UpdatedOnUtc }; + } + } + + if (queries.Topics != null) + { + var topics = queries.Topics.Select(x => new { x.Id }).ToList(); + foreach (var x in topics) + { + 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; + + //var limit = 0; + while (maxId > 1) + { + 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(); + + if (batch.First().EntityName == "Product") + { + // 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 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 result; + } + + protected virtual List GetSiteMapDocuments(IReadOnlyCollection nodes) + { + int siteMapCount = (int)Math.Ceiling(nodes.Count / (double)MaximumSiteMapNodeCount); + CheckSitemapCount(siteMapCount); + + var siteMaps = Enumerable + .Range(0, siteMapCount) + .Select(x => + { + return new KeyValuePair>( + x + 1, + nodes.Skip(x * MaximumSiteMapNodeCount).Take(MaximumSiteMapNodeCount)); + }); + + var siteMapDocuments = new List(siteMapCount); + + foreach (var kvp in siteMaps) + { + siteMapDocuments.Add(this.GetSitemapDocument(kvp.Value)); + } + + return siteMapDocuments; + } + + /// + /// Gets the sitemap XML document for the specified set of nodes. + /// + /// The sitemap nodes. + /// The sitemap XML document for the specified set of nodes. + private string GetSitemapDocument(IEnumerable nodes) + { + //var languages = _languageService.GetAllLanguages(); + + XNamespace ns = SiteMapsNamespace; + XNamespace xhtml = XhtmlNamespace; + + XElement root = new XElement( + ns + "urlset", + new XAttribute(XNamespace.Xmlns + "xhtml", xhtml)); + + foreach (var node in nodes) + { + // url + var xel = new XElement + ( + ns + "url", + // url/loc + new XElement(ns + "loc", node.Loc), + // url/lastmod + node.LastMod == null ? null : new XElement( + ns + "lastmod", + node.LastMod.Value.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz")), + // url/changefreq + node.ChangeFreq == null ? null : new XElement( + ns + "changefreq", + node.ChangeFreq.Value.ToString().ToLowerInvariant()), + // url/priority + node.Priority == null ? null : new XElement( + ns + "priority", + node.Priority.Value.ToString("F1", CultureInfo.InvariantCulture)) + ); + + if (node.Links != null) + { + foreach (var entry in node.Links) + { + // url/xhtml:link[culture] + xel.Add(new XElement + ( + xhtml + "link", + new XAttribute("rel", "alternate"), + new XAttribute("hreflang", entry.Lang), + new XAttribute("href", entry.Href) + )); + } + } + + root.Add(xel); + } + + XDocument document = new XDocument(root); + var xml = document.ToString(SaveOptions.DisableFormatting); + CheckDocumentSize(xml); + + return xml; + } + + /// + /// 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) + { + XNamespace ns = SiteMapsNamespace; + + XElement root = new XElement(ns + "sitemapindex"); + + foreach (var node in nodes) + { + 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); + + root.Add(xel); + } + + var document = new XDocument(root); + var xml = document.ToString(SaveOptions.DisableFormatting); + CheckDocumentSize(xml); + + return xml; + } + + private string GetSitemapIndexUrl(int index, string baseUrl) + { + var url = _urlHelper.RouteUrl("XmlSitemap", new { index }).TrimStart('/'); + return baseUrl + url; + } + + private QueryHolder CreateQueries(XmlSitemapBuildContext ctx) + { + var storeId = ctx.Store.Id; + + if (_services.StoreService.IsSingleStoreMode()) + { + storeId = 0; + } + + // Always work with store-dependant setting + var seoSettings = _services.Settings.LoadSetting(storeId); + + var holder = new QueryHolder(); + + if (seoSettings.XmlSitemapIncludesCategories) + { + holder.Categories = _categoryService.BuildCategoriesQuery(showHidden: false, storeId: storeId); + } + + if (seoSettings.XmlSitemapIncludesManufacturers) + { + holder.Manufacturers = _manufacturerService.GetManufacturers(false).OrderBy(x => x.DisplayOrder).ThenBy(x => x.Name); + } + + if (seoSettings.XmlSitemapIncludesTopics) + { + holder.Topics = _topicService.GetAllTopics(storeId).AlterQuery(q => + { + return q.Where(t => t.IncludeInSitemap && !t.RenderAsWidget); + }).SourceQuery; + } + + if (seoSettings.XmlSitemapIncludesProducts) + { + var searchQuery = new CatalogSearchQuery() + .VisibleOnly(_services.WorkContext.CurrentCustomer) + .VisibleIndividuallyOnly(true) + .HasStoreId(storeId); + + holder.Products = _catalogSearchService.PrepareQuery(searchQuery); + } + + return holder; + } + + protected void ProcessCustomNodes(XmlSitemapBuildContext ctx, Multimap sitemaps) + { + } + + /// + /// Checks the size of the XML sitemap document. If it is over 10MB, logs an error. + /// + /// The sitemap XML document. + private void CheckDocumentSize(string siteMapXml) + { + if (siteMapXml.Length >= MaximumSiteMapSizeInBytes) + { + Logger.Error(new InvalidOperationException($"Sitemap exceeds the maximum size of 10MB. This is because you have unusually long URL's. Consider reducing the MaximumSitemapNodeCount. Size:<{siteMapXml.Length}>")); + } + } + + /// + /// Checks the count of the number of sitemaps. If it is over 50,000, logs an error. + /// + /// The sitemap count. + private void CheckSitemapCount(int sitemapCount) + { + if (sitemapCount > MaximumSiteMapCount) + { + var ex = new InvalidOperationException($"Sitemap index file exceeds the maximum number of allowed sitemaps of 50,000. Count:<{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/XmlSitemapNode.cs b/src/Libraries/SmartStore.Services/Seo/XmlSitemapNode.cs new file mode 100644 index 0000000000..a3d2f89f6b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Seo/XmlSitemapNode.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Services.Seo +{ + public class XmlSitemapNode + { + public string Loc { get; set; } + public DateTime? LastMod { get; set; } + public ChangeFrequency? ChangeFreq { get; set; } + public float? Priority { get; set; } + public IEnumerable Links { get; set; } + + public class LinkEntry + { + public string Lang { get; set; } + public string Href { get; set; } + } + } + + /// + /// Represents a sitemap update frequency + /// + public enum ChangeFrequency + { + /// + /// Always + /// + Always, + /// + /// Hourly + /// + Hourly, + /// + /// Daily + /// + Daily, + /// + /// Weekly + /// + Weekly, + /// + /// Monthly + /// + Monthly, + /// + /// Yearly + /// + Yearly, + /// + /// Never + /// + Never + } +} 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/ServiceCacheBuster.cs b/src/Libraries/SmartStore.Services/ServiceCacheBuster.cs new file mode 100644 index 0000000000..feaca72088 --- /dev/null +++ b/src/Libraries/SmartStore.Services/ServiceCacheBuster.cs @@ -0,0 +1,45 @@ +using System; +using SmartStore.Core.Caching; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core; + +namespace SmartStore.Services +{ + public partial class ServiceCacheBuster : DbSaveHook + { + public const string STORE_LANGUAGE_MAP_KEY = "svc:storelangmap*"; + + private readonly ICacheManager _cacheManager; + + public ServiceCacheBuster(ICacheManager cacheManager) + { + _cacheManager = cacheManager; + } + + protected override void OnInserted(BaseEntity entity, IHookedEntity entry) + { + if (entry.EntityType != typeof(Store) && entry.EntityType != typeof(Language)) + throw new NotImplementedException(); + + _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); + } + + protected override void OnDeleted(BaseEntity entity, IHookedEntity entry) + { + if (entry.EntityType != typeof(Store) && entry.EntityType != typeof(Language)) + throw new NotImplementedException(); + + _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); + } + + protected override void OnUpdated(BaseEntity entity, IHookedEntity entry) + { + if (entry.EntityType != typeof(Language)) + throw new NotImplementedException(); + + _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); + } + } +} diff --git a/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs b/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs deleted file mode 100644 index 49dc170136..0000000000 --- a/src/Libraries/SmartStore.Services/ServiceCacheConsumer.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Core.Caching; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; -using SmartStore.Services.Tasks; -using SmartStore.Services.Stores; - -namespace SmartStore.Services -{ - public class ServiceCacheConsumer : - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer> - { - public const string STORE_LANGUAGE_MAP_KEY = "sm.svc.storelangmap"; - - private readonly ICacheManager _cacheManager; - - public ServiceCacheConsumer(ICacheManager cacheManager) - { - _cacheManager = cacheManager; - } - - public void HandleEvent(EntityInserted eventMessage) - { - _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); - } - - public void HandleEvent(EntityDeleted eventMessage) - { - _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); - } - - public void HandleEvent(EntityInserted eventMessage) - { - _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); - } - - public void HandleEvent(EntityUpdated eventMessage) - { - _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); - } - - public void HandleEvent(EntityDeleted eventMessage) - { - _cacheManager.Remove(STORE_LANGUAGE_MAP_KEY); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs index 9018fab4b6..89672e6ec6 100644 --- a/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Plugins; namespace SmartStore.Services.Shipping { - /// - /// Shipping service interface - /// - public partial interface IShippingService + /// + /// Shipping service interface + /// + public partial interface IShippingService { /// /// Load active shipping rate computation methods @@ -50,12 +49,13 @@ public partial interface IShippingService ShippingMethod GetShippingMethodById(int shippingMethodId); - /// - /// Gets all shipping methods - /// + /// + /// Gets all shipping methods + /// /// Shipping option request to filter out shipping methods. null to load all shipping methods. - /// Shipping method collection - IList GetAllShippingMethods(GetShippingOptionRequest request = null); + /// Whether to filter methods by store identifier. + /// Shipping method collection + IList GetAllShippingMethods(GetShippingOptionRequest request = null, int storeId = 0); /// /// Inserts a shipping method @@ -77,7 +77,6 @@ public partial interface IShippingService /// Shopping cart item weight decimal GetShoppingCartItemWeight(OrganizedShoppingCartItem shoppingCartItem); - /// /// Gets shopping cart item total weight /// @@ -89,8 +88,10 @@ public partial interface IShippingService /// Gets shopping cart weight /// /// Cart + /// Whether to include free shipping products /// Shopping cart weight - decimal GetShoppingCartTotalWeight(IList cart); + decimal GetShoppingCartTotalWeight(IList cart, bool includeFreeShippingProducts = true); + /// /// Create shipment package from shopping cart diff --git a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs index 3a8c261164..8cd9b39030 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs @@ -62,9 +62,6 @@ public virtual void DeleteShipment(Shipment shipment) _shipmentRepository.Delete(shipment); - //event notifications - _eventPublisher.EntityDeleted(shipment); - if (orderId != 0) { var order = _orderRepository.GetById(orderId); @@ -111,21 +108,16 @@ public virtual IList GetShipmentsByIds(int[] shipmentIds) var query = from o in _shipmentRepository.Table.Expand(x => x.Order) where shipmentIds.Contains(o.Id) select o; + var shipments = query.ToList(); - //sort by passed identifiers - var sortedOrders = new List(); - foreach (int id in shipmentIds) - { - var shipment = shipments.Find(x => x.Id == id); - if (shipment != null) - sortedOrders.Add(shipment); - } - return sortedOrders; - } + + // sort by passed identifier sequence + return shipments.OrderBySequence(shipmentIds).ToList(); + } public virtual Multimap GetShipmentsByOrderIds(int[] orderIds) { - Guard.ArgumentNotNull(() => orderIds); + Guard.NotNull(orderIds, nameof(orderIds)); var query = from x in _shipmentRepository.TableUntracked.Expand(x => x.ShipmentItems) @@ -167,7 +159,6 @@ public virtual void InsertShipment(Shipment shipment) _shipmentRepository.Insert(shipment); //event notification - _eventPublisher.EntityInserted(shipment); _eventPublisher.PublishOrderUpdated(shipment.Order); } @@ -183,7 +174,6 @@ public virtual void UpdateShipment(Shipment shipment) _shipmentRepository.Update(shipment); //event notification - _eventPublisher.EntityUpdated(shipment); _eventPublisher.PublishOrderUpdated(shipment.Order); } @@ -202,9 +192,6 @@ public virtual void DeleteShipmentItem(ShipmentItem shipmentItem) _siRepository.Delete(shipmentItem); - //event notifications - _eventPublisher.EntityDeleted(shipmentItem); - if (orderId != 0) { var order = _orderRepository.GetById(orderId); @@ -237,9 +224,6 @@ public virtual void InsertShipmentItem(ShipmentItem shipmentItem) _siRepository.Insert(shipmentItem); - //event notifications - _eventPublisher.EntityInserted(shipmentItem); - if (shipmentItem.Shipment != null && shipmentItem.Shipment.Order != null) { _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); @@ -266,9 +250,6 @@ public virtual void UpdateShipmentItem(ShipmentItem shipmentItem) _siRepository.Update(shipmentItem); - //event notifications - _eventPublisher.EntityUpdated(shipmentItem); - if (shipmentItem.Shipment != null && shipmentItem.Shipment.Order != null) { _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs index fedfbe3891..b83fdc68ee 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingExtentions.cs @@ -5,7 +5,7 @@ namespace SmartStore.Services.Shipping { - public static class ShippingExtentions + public static class ShippingExtentions { public static bool IsShippingRateComputationMethodActive(this Provider srcm, ShippingSettings shippingSettings) { @@ -23,14 +23,5 @@ public static bool IsShippingRateComputationMethodActive(this Provider c.Id == countryId) != null; - return result; - } } } diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 4f6db7fa9c..0e4c091f2f 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; @@ -21,14 +22,12 @@ namespace SmartStore.Services.Shipping { public partial class ShippingService : IShippingService { - #region Fields - private readonly static object _lock = new object(); private static IList _shippingMethodFilterTypes = null; private readonly IRepository _shippingMethodRepository; - private readonly ILogger _logger; - private readonly IProductAttributeParser _productAttributeParser; + private readonly IRepository _storeMappingRepository; + private readonly IProductAttributeParser _productAttributeParser; private readonly IProductService _productService; private readonly ICheckoutAttributeParser _checkoutAttributeParser; private readonly IGenericAttributeService _genericAttributeService; @@ -38,31 +37,12 @@ public partial class ShippingService : IShippingService private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; private readonly ITypeFinder _typeFinder; + private readonly ICommonServices _services; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Shipping method repository - /// Logger - /// Product attribute parser - /// Product service - /// Checkout attribute parser - /// Generic attribute service - /// Localization service - /// Shipping settings - /// Plugin finder - /// Event published - /// Shopping cart settings - /// Setting service public ShippingService( IRepository shippingMethodRepository, - ILogger logger, - IProductAttributeParser productAttributeParser, + IRepository storeMappingRepository, + IProductAttributeParser productAttributeParser, IProductService productService, ICheckoutAttributeParser checkoutAttributeParser, IGenericAttributeService genericAttributeService, @@ -71,29 +51,31 @@ public ShippingService( ShoppingCartSettings shoppingCartSettings, ISettingService settingService, IProviderManager providerManager, - ITypeFinder typeFinder) + ITypeFinder typeFinder, + ICommonServices services) { - this._shippingMethodRepository = shippingMethodRepository; - this._logger = logger; - this._productAttributeParser = productAttributeParser; - this._productService = productService; - this._checkoutAttributeParser = checkoutAttributeParser; - this._genericAttributeService = genericAttributeService; - this._shippingSettings = shippingSettings; - this._eventPublisher = eventPublisher; - this._shoppingCartSettings = shoppingCartSettings; - this._settingService = settingService; - this._providerManager = providerManager; - this._typeFinder = typeFinder; + _shippingMethodRepository = shippingMethodRepository; + _storeMappingRepository = storeMappingRepository; + _productAttributeParser = productAttributeParser; + _productService = productService; + _checkoutAttributeParser = checkoutAttributeParser; + _genericAttributeService = genericAttributeService; + _shippingSettings = shippingSettings; + _eventPublisher = eventPublisher; + _shoppingCartSettings = shoppingCartSettings; + _settingService = settingService; + _providerManager = providerManager; + _typeFinder = typeFinder; + _services = services; T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + QuerySettings = DbQuerySettings.Default; } public Localizer T { get; set; } - - #endregion - - #region Methods + public ILogger Logger { get; set; } + public DbQuerySettings QuerySettings { get; set; } #region Shipping rate computation methods @@ -169,9 +151,6 @@ public virtual void DeleteShippingMethod(ShippingMethod shippingMethod) throw new ArgumentNullException("shippingMethod"); _shippingMethodRepository.Delete(shippingMethod); - - //event notification - _eventPublisher.EntityDeleted(shippingMethod); } /// @@ -187,24 +166,42 @@ public virtual ShippingMethod GetShippingMethodById(int shippingMethodId) return _shippingMethodRepository.GetById(shippingMethodId); } - public virtual IList GetAllShippingMethods(GetShippingOptionRequest request = null) + public virtual IList GetAllShippingMethods(GetShippingOptionRequest request = null, int storeId = 0) { var query = from sm in _shippingMethodRepository.Table - orderby sm.DisplayOrder select sm; - var allMethods = query.ToList(); + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + query = + from x in query + join sm in _storeMappingRepository.TableUntracked + on new { c1 = x.Id, c2 = "ShippingMethod" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm + from sm in x_sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == sm.StoreId + select x; + + query = + from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + } + + var allMethods = query.OrderBy(x => x.DisplayOrder).ToList(); if (request == null) + { return allMethods; + } IList allFilters = null; var filterRequest = new ShippingFilterRequest { Option = request }; var activeShippingMethods = allMethods.Where(s => { - // shipping method filtering + // Shipping method filtering. if (allFilters == null) allFilters = GetAllShippingMethodFilters(); @@ -229,9 +226,6 @@ public virtual void InsertShippingMethod(ShippingMethod shippingMethod) throw new ArgumentNullException("shippingMethod"); _shippingMethodRepository.Insert(shippingMethod); - - //event notification - _eventPublisher.EntityInserted(shippingMethod); } /// @@ -244,30 +238,22 @@ public virtual void UpdateShippingMethod(ShippingMethod shippingMethod) throw new ArgumentNullException("shippingMethod"); _shippingMethodRepository.Update(shippingMethod); - - //event notification - _eventPublisher.EntityUpdated(shippingMethod); } #endregion #region Workflow - /// - /// Gets shopping cart item weight (of one item) - /// - /// Shopping cart item - /// Shopping cart item weight public virtual decimal GetShoppingCartItemWeight(OrganizedShoppingCartItem shoppingCartItem) { if (shoppingCartItem == null) throw new ArgumentNullException("shoppingCartItem"); - decimal weight = decimal.Zero; + var weight = decimal.Zero; if (shoppingCartItem.Item.Product != null) { - decimal attributesTotalWeight = decimal.Zero; + var attributesTotalWeight = decimal.Zero; if (!String.IsNullOrEmpty(shoppingCartItem.Item.AttributesXml)) { @@ -293,33 +279,36 @@ public virtual decimal GetShoppingCartItemWeight(OrganizedShoppingCartItem shopp return weight; } - /// - /// Gets shopping cart item total weight - /// - /// Shopping cart item - /// Shopping cart item weight public virtual decimal GetShoppingCartItemTotalWeight(OrganizedShoppingCartItem shoppingCartItem) { if (shoppingCartItem == null) throw new ArgumentNullException("shoppingCartItem"); - decimal totalWeight = GetShoppingCartItemWeight(shoppingCartItem) * shoppingCartItem.Item.Quantity; + var totalWeight = GetShoppingCartItemWeight(shoppingCartItem) * shoppingCartItem.Item.Quantity; return totalWeight; } - /// - /// Gets shopping cart weight - /// - /// Cart - /// Shopping cart weight - public virtual decimal GetShoppingCartTotalWeight(IList cart) + public virtual decimal GetShoppingCartTotalWeight(IList cart, bool includeFreeShippingProducts = true) { - Customer customer = cart.GetCustomer(); + var totalWeight = decimal.Zero; + var customer = cart.GetCustomer(); - decimal totalWeight = decimal.Zero; - // shopping cart items - foreach (var shoppingCartItem in cart) - totalWeight += GetShoppingCartItemTotalWeight(shoppingCartItem); + // shopping cart items + foreach (var cartItem in cart) + { + var product = cartItem.Item.Product; + if (product != null) + { + if (!includeFreeShippingProducts && product.IsFreeShipping) + { + // skip product + } + else + { + totalWeight += GetShoppingCartItemTotalWeight(cartItem); + } + } + } // checkout attributes if (customer != null) @@ -329,20 +318,15 @@ public virtual decimal GetShoppingCartTotalWeight(IList - /// Create shipment package from shopping cart - /// - /// Shopping cart - /// Shipping address - /// Store identifier - /// Shipment package public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress, int storeId) { var request = new GetShippingOptionRequest(); @@ -365,16 +349,11 @@ public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList - /// Gets available shipping options - /// - /// Shopping cart - /// Shipping address - /// Filter by shipping rate computation method identifier; null to load shipping options of all shipping rate computation methods - /// Load records allows only in specified store; pass 0 to load all records - /// Shipping options - public virtual GetShippingOptionResponse GetShippingOptions(IList cart, Address shippingAddress, - string allowedShippingRateComputationMethodSystemName = "", int storeId = 0) + public virtual GetShippingOptionResponse GetShippingOptions( + IList cart, + Address shippingAddress, + string allowedShippingRateComputationMethodSystemName = "", + int storeId = 0) { if (cart == null) throw new ArgumentNullException("cart"); @@ -401,10 +380,7 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList 0; + + foreach (string error in getShippingOptionResponse.Errors) { result.AddError(error); - _logger.Warning(string.Concat(srcm.Metadata.FriendlyName, ": ", error)); + if (hasItemsToShip) + { + Logger.Warn(error); + } } } } @@ -458,7 +439,5 @@ public virtual IList GetAllShippingMethodFilters() } #endregion - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 1edfe58738..723ecf1212 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -10,7 +11,7 @@ Properties SmartStore.Services SmartStore.Services - v4.5.1 + v4.6.1 512 @@ -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,102 +64,116 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\AngleSharp.0.9.11\lib\net45\AngleSharp.dll + + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll True - - ..\..\packages\CronExpressionDescriptor.1.17.0\lib\net45\CronExpressionDescriptor.dll + + ..\..\packages\CronExpressionDescriptor.1.21.2\lib\net45\CronExpressionDescriptor.dll + True - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + True - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + True - - False - ..\..\packages\EPPlus.4.0.3\lib\net20\EPPlus.dll + + ..\..\packages\EPPlus.4.5.3.1\lib\net40\EPPlus.dll - - ..\..\packages\ImageResizer.4.0.4\lib\net45\ImageResizer.dll - True + + ..\..\packages\ImageProcessor.2.7.0.100\lib\net452\ImageProcessor.dll - - ..\..\packages\ImageResizer.Plugins.PrettyGifs.4.0.4\lib\net45\ImageResizer.Plugins.PrettyGifs.dll - True + + ..\..\packages\LumenWorksCsvReader.4.0.0\lib\net461\LumenWorks.Framework.IO.dll - - ..\..\packages\LumenWorksCsvReader.3.9\lib\net20\LumenWorks.Framework.IO.dll - True + + ..\..\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\ncrontab.2.0.0\lib\net20\NCrontab.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.12.0.1\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\NReco.PdfGenerator.1.1.15\lib\net20\NReco.PdfGenerator.dll True - - False - ..\..\packages\NReco.PdfGenerator.1.1.5.0\lib\net20\NReco.PdfGenerator.dll + + ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + + + ..\..\packages\PreMailer.Net.2.0.1\lib\net45\PreMailer.Net.dll + + - - ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll + + ..\..\packages\System.Linq.Dynamic.1.0.7\lib\net40\System.Linq.Dynamic.dll + True + + + ..\..\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 - - ..\..\packages\UAParser.2.0.0.0\lib\net40-Client\UAParser.dll + + ..\..\packages\UAParser.2.1.0.0\lib\net40-Client\UAParser.dll True @@ -166,13 +184,49 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + @@ -192,9 +246,13 @@ + + + + @@ -219,12 +277,13 @@ - + + @@ -232,24 +291,69 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + - - + @@ -277,21 +381,18 @@ - - - - - + + @@ -310,8 +411,6 @@ - - @@ -327,37 +426,23 @@ - - - - - - - - - - - + - + - - - + - - - + @@ -383,7 +468,7 @@ - + @@ -392,21 +477,14 @@ - - - - - - - @@ -472,7 +550,38 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -484,14 +593,16 @@ - - - - + + + + + + - + @@ -509,7 +620,6 @@ - @@ -519,6 +629,7 @@ + @@ -529,6 +640,7 @@ + @@ -544,7 +656,6 @@ - @@ -603,7 +714,9 @@ Nop_Services_EuropaCheckVatService_checkVatService - + + + diff --git a/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs index ee938acb15..02e9f61346 100644 --- a/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs @@ -2,12 +2,7 @@ using System.Collections.Generic; using System.Linq; using SmartStore.Core; -using SmartStore.Core.Caching; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; -using SmartStore.Services.Security; namespace SmartStore.Services.Stores { @@ -73,29 +68,81 @@ public partial interface IStoreMappingService /// Store mapping void UpdateStoreMapping(StoreMapping storeMapping); + /// + /// Find store identifiers with granted access (mapped to the entity) + /// + /// Entity name to check + /// Entity id to check + /// Store identifiers + int[] GetStoresIdsWithAccess(string entityName, int entityId); + + /// + /// Checks whether an entity can be accessed in a store (mapped to this store) + /// + /// Entity name to check + /// Entity id to check + /// true - authorized; otherwise, false + bool Authorize(string entityName, int entityId); + + /// + /// Checks whether an entity can be accessed in a store (mapped to this store) + /// + /// Entity name to check + /// Entity id to check + /// Store identifier to check against + /// true - authorized; otherwise, false + bool Authorize(string entityName, int entityId, int storeId); + } + + public static class IStoreMappingServiceExtensions + { /// /// Find store identifiers with granted access (mapped to the entity) /// /// Type - /// Wntity + /// Entity /// Store identifiers - int[] GetStoresIdsWithAccess(T entity) where T : BaseEntity, IStoreMappingSupported; + public static int[] GetStoresIdsWithAccess(this IStoreMappingService svc, T entity) where T : BaseEntity, IStoreMappingSupported + { + if (entity == null) + return new int[0]; + + return svc.GetStoresIdsWithAccess(entity.GetEntityName(), entity.Id); + } /// /// Authorize whether entity could be accessed in the current store (mapped to this store) /// /// Type - /// Wntity + /// Entity /// true - authorized; otherwise, false - bool Authorize(T entity) where T : BaseEntity, IStoreMappingSupported; + public static bool Authorize(this IStoreMappingService svc, T entity) where T : BaseEntity, IStoreMappingSupported + { + if (entity == null) + return false; + + if (!entity.LimitedToStores) + return true; + + return svc.Authorize(entity.GetEntityName(), entity.Id); + } /// /// Authorize whether entity could be accessed in a store (mapped to this store) /// /// Type /// Entity - /// Store identifier + /// Store identifier to check against /// true - authorized; otherwise, false - bool Authorize(T entity, int storeId) where T : BaseEntity, IStoreMappingSupported; + public static bool Authorize(this IStoreMappingService svc, T entity, int storeId) where T : BaseEntity, IStoreMappingSupported + { + if (entity == null) + return false; + + if (!entity.LimitedToStores) + return true; + + return svc.Authorize(entity.GetEntityName(), entity.Id, storeId); + } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Stores/IStoreService.cs b/src/Libraries/SmartStore.Services/Stores/IStoreService.cs index 31d9e5d0bd..90a6da14ce 100644 --- a/src/Libraries/SmartStore.Services/Stores/IStoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/IStoreService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Security; namespace SmartStore.Services.Stores { @@ -49,5 +50,16 @@ public partial interface IStoreService /// /// Store entity bool IsStoreDataValid(Store store); + + /// + /// Gets the store host name + /// + /// The store to get the host name for + /// + /// If null, checks whether all pages should be secured per . + /// If true, returns the secure url, but only if SSL is enabled for the store. + /// + /// The host name + string GetHost(Store store, bool? secure = null); } } \ 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 c2ccb86f17..d5578486ca 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs @@ -8,74 +8,44 @@ namespace SmartStore.Services.Stores { - /// - /// Store mapping service - /// public partial class StoreMappingService : IStoreMappingService { - #region Constants - - private const string STOREMAPPING_BY_ENTITYID_NAME_KEY = "SmartStore.storemapping.entityid-name-{0}-{1}"; - private const string STOREMAPPING_PATTERN_KEY = "SmartStore.storemapping."; - - #endregion - - #region Fields + /// + /// 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; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Store context - /// Store mapping repository - public StoreMappingService(ICacheManager cacheManager, + public StoreMappingService( + ICacheManager cacheManager, IStoreContext storeContext, IStoreService storeService, IRepository storeMappingRepository) { - this._cacheManager = cacheManager; - this._storeContext = storeContext; - this._storeService = storeService; - this._storeMappingRepository = storeMappingRepository; + _cacheManager = cacheManager; + _storeContext = storeContext; + _storeService = storeService; + _storeMappingRepository = storeMappingRepository; - this.QuerySettings = DbQuerySettings.Default; + QuerySettings = DbQuerySettings.Default; } public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Methods - - /// - /// Deletes a store mapping record - /// - /// Store mapping record public virtual void DeleteStoreMapping(StoreMapping storeMapping) { - if (storeMapping == null) - throw new ArgumentNullException("storeMapping"); + Guard.NotNull(storeMapping, nameof(storeMapping)); _storeMappingRepository.Delete(storeMapping); - //cache - _cacheManager.RemoveByPattern(STOREMAPPING_PATTERN_KEY); + ClearCacheSegment(storeMapping.EntityName, storeMapping.EntityId); } - /// - /// Gets a store mapping record - /// - /// Store mapping record identifier - /// Store mapping record public virtual StoreMapping GetStoreMappingById(int storeMappingId) { if (storeMappingId == 0) @@ -85,19 +55,12 @@ public virtual StoreMapping GetStoreMappingById(int storeMappingId) return storeMapping; } - /// - /// Gets store mapping records - /// - /// Type - /// Entity - /// Store mapping records public virtual IList GetStoreMappings(T entity) where T : BaseEntity, IStoreMappingSupported { - if (entity == null) - throw new ArgumentNullException("entity"); + 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 && @@ -107,12 +70,6 @@ public virtual IList GetStoreMappings(T entity) where T : BaseE return storeMappings; } - /// - /// Gets store mapping records - /// - /// Could be null - /// Could be 0 - /// Store mapping record query public virtual IQueryable GetStoreMappingsFor(string entityName, int entityId) { var query = _storeMappingRepository.Table; @@ -126,12 +83,6 @@ public virtual IQueryable GetStoreMappingsFor(string entityName, i return query; } - /// - /// Save the store napping for an entity - /// - /// Entity type - /// The entity - /// Array of selected store ids public virtual void SaveStoreMappings(T entity, int[] selectedStoreIds) where T : BaseEntity, IStoreMappingSupported { var existingStoreMappings = GetStoreMappings(entity); @@ -153,37 +104,24 @@ public virtual void SaveStoreMappings(T entity, int[] selectedStoreIds) where } } - /// - /// Inserts a store mapping record - /// - /// Store mapping public virtual void InsertStoreMapping(StoreMapping storeMapping) { - if (storeMapping == null) - throw new ArgumentNullException("storeMapping"); + Guard.NotNull(storeMapping, nameof(storeMapping)); _storeMappingRepository.Insert(storeMapping); - //cache - _cacheManager.RemoveByPattern(STOREMAPPING_PATTERN_KEY); + ClearCacheSegment(storeMapping.EntityName, storeMapping.EntityId); } - /// - /// Inserts a store mapping record - /// - /// Type - /// Store id - /// Entity public virtual void InsertStoreMapping(T entity, int storeId) where T : BaseEntity, IStoreMappingSupported { - if (entity == null) - throw new ArgumentNullException("entity"); + Guard.NotNull(entity, nameof(entity)); if (storeId == 0) - throw new ArgumentOutOfRangeException("storeId"); + throw new ArgumentOutOfRangeException(nameof(storeId)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); var storeMapping = new StoreMapping { @@ -195,90 +133,113 @@ public virtual void InsertStoreMapping(T entity, int storeId) where T : BaseE InsertStoreMapping(storeMapping); } - /// - /// Updates the store mapping record - /// - /// Store mapping public virtual void UpdateStoreMapping(StoreMapping storeMapping) { - if (storeMapping == null) - throw new ArgumentNullException("storeMapping"); + Guard.NotNull(storeMapping, nameof(storeMapping)); _storeMappingRepository.Update(storeMapping); - //cache - _cacheManager.RemoveByPattern(STOREMAPPING_PATTERN_KEY); + ClearCacheSegment(storeMapping.EntityName, storeMapping.EntityId); } - /// - /// Find store identifiers with granted access (mapped to the entity) - /// - /// Type - /// Wntity - /// Store identifiers - public virtual int[] GetStoresIdsWithAccess(T entity) where T : BaseEntity, IStoreMappingSupported + public virtual int[] GetStoresIdsWithAccess(string entityName, int entityId) { - if (entity == null) + Guard.NotEmpty(entityName, nameof(entityName)); + + if (entityId <= 0) return new int[0]; - int entityId = entity.Id; - string entityName = typeof(T).Name; + var cacheSegment = GetCacheSegment(entityName, entityId); - string key = string.Format(STOREMAPPING_BY_ENTITYID_NAME_KEY, entityId, entityName); - return _cacheManager.Get(key, () => + if (!cacheSegment.TryGetValue(entityId, out var storeIds)) { - var query = from sm in _storeMappingRepository.Table - where sm.EntityId == entityId && - sm.EntityName == entityName - select sm.StoreId; - var result = query.ToArray(); - //little hack here. nulls aren't cacheable so set it to "" - if (result == null) - result = new int[0]; - return result; - }); + return Array.Empty(); + } + + return storeIds; } - /// - /// Authorize whether entity could be accessed in the current store (mapped to this store) - /// - /// Type - /// Wntity - /// true - authorized; otherwise, false - public virtual bool Authorize(T entity) where T : BaseEntity, IStoreMappingSupported + public bool Authorize(string entityName, int entityId) { - return Authorize(entity, _storeContext.CurrentStore.Id); + return Authorize(entityName, entityId, _storeContext.CurrentStore.Id); } - /// - /// Authorize whether entity could be accessed in a store (mapped to this store) - /// - /// Type - /// Entity - /// Store - /// true - authorized; otherwise, false - public virtual bool Authorize(T entity, int storeId) where T : BaseEntity, IStoreMappingSupported + public virtual bool Authorize(string entityName, int entityId, int storeId) { - if (entity == null) + Guard.NotEmpty(entityName, nameof(entityName)); + + if (entityId <= 0) return false; - if (storeId == 0) - //return true if no store specified/found + if (storeId <= 0) + // return true if no store specified/found return true; if (QuerySettings.IgnoreMultiStore) return true; - if (!entity.LimitedToStores) - return true; + // 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, () => + { + 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) + { + 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 { } + } - foreach (var storeIdWithAccess in GetStoresIdsWithAccess(entity)) - if (storeId == storeIdWithAccess) - //yes, we have such permission - return true; + private string BuildCacheSegmentKey(string segment) + { + return String.Format(STOREMAPPING_SEGMENT_KEY, segment); + } - //no permission found - 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 diff --git a/src/Libraries/SmartStore.Services/Stores/StoreService.cs b/src/Libraries/SmartStore.Services/Stores/StoreService.cs index ac21431c74..f8cde15ab0 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreService.cs @@ -1,146 +1,68 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web.Mvc; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; -using SmartStore.Services.Localization; +using SmartStore.Data.Caching; namespace SmartStore.Services.Stores { - /// - /// Store service - /// public partial class StoreService : IStoreService { - #region Constants - private const string STORES_ALL_KEY = "SmartStore.stores.all"; - private const string STORES_PATTERN_KEY = "SmartStore.stores."; - private const string STORES_BY_ID_KEY = "SmartStore.stores.id-{0}"; - #endregion - - #region Fields - private readonly IRepository _storeRepository; - private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - private bool? _isSingleStoreMode = null; - - #endregion - - #region Ctor - - public StoreService( - IRequestCache requestCache, - IRepository storeRepository, - IEventPublisher eventPublisher) - { - this._requestCache = requestCache; - this._storeRepository = storeRepository; - this._eventPublisher = eventPublisher; - } - #endregion - - #region Methods + private bool? _isSingleStoreMode = null; - /// - /// Deletes a store - /// - /// Store - public virtual void DeleteStore(Store store) + public StoreService(IRepository storeRepository) { - if (store == null) - throw new ArgumentNullException("store"); - - var allStores = GetAllStores(); - if (allStores.Count == 1) - throw new Exception("You cannot delete the only configured store."); - - _storeRepository.Delete(store); - - _requestCache.RemoveByPattern(STORES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(store); + _storeRepository = storeRepository; } - /// - /// Gets all stores - /// - /// Store collection public virtual IList GetAllStores() { - string key = STORES_ALL_KEY; - return _requestCache.Get(key, () => - { - var query = _storeRepository.Table - .Expand(x => x.PrimaryStoreCurrency) - .Expand(x => x.PrimaryExchangeRateCurrency) - .OrderBy(x => x.DisplayOrder) - .ThenBy(x => x.Name); - - var stores = query.ToList(); - return stores; - }); + var query = _storeRepository.Table + .Expand(x => x.PrimaryStoreCurrency) + .Expand(x => x.PrimaryExchangeRateCurrency) + .OrderBy(x => x.DisplayOrder) + .ThenBy(x => x.Name); + + var stores = query.ToListCached("db.store.all"); + return stores; } - /// - /// Gets a store - /// - /// Store identifier - /// Store public virtual Store GetStoreById(int storeId) { if (storeId == 0) return null; - string key = string.Format(STORES_BY_ID_KEY, storeId); - return _requestCache.Get(key, () => - { - return _storeRepository.GetById(storeId); - }); + return _storeRepository.GetByIdCached(storeId, "db.store.id-" + storeId); } - /// - /// Inserts a store - /// - /// Store public virtual void InsertStore(Store store) { - if (store == null) - throw new ArgumentNullException("store"); + Guard.NotNull(store, nameof(store)); _storeRepository.Insert(store); - - _requestCache.RemoveByPattern(STORES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(store); } - /// - /// Updates the store - /// - /// Store public virtual void UpdateStore(Store store) { - if (store == null) - throw new ArgumentNullException("store"); + Guard.NotNull(store, nameof(store)); _storeRepository.Update(store); + } + + public virtual void DeleteStore(Store store) + { + Guard.NotNull(store, nameof(store)); - _requestCache.RemoveByPattern(STORES_PATTERN_KEY); + var allStores = GetAllStores(); + if (allStores.Count == 1) + throw new Exception("You cannot delete the only configured store."); - //event notification - _eventPublisher.EntityUpdated(store); + _storeRepository.Delete(store); } - /// - /// True if there's only one store. Otherwise False. - /// public virtual bool IsSingleStoreMode() { if (!_isSingleStoreMode.HasValue) @@ -151,14 +73,9 @@ public virtual bool IsSingleStoreMode() return _isSingleStoreMode.Value; } - /// - /// True if the store data is valid. Otherwise False. - /// - /// Store entity public virtual bool IsStoreDataValid(Store store) { - if (store == null) - throw new ArgumentNullException("store"); + Guard.NotNull(store, nameof(store)); if (store.Url.IsEmpty()) return false; @@ -181,12 +98,17 @@ public virtual bool IsStoreDataValid(Store store) return store.Url.IsWebUrl(); } } - catch (Exception) + catch { return false; } } - #endregion + public string GetHost(Store store, bool? secure = null) + { + Guard.NotNull(store, nameof(store)); + + return store.GetHost(secure ?? store.ForceSslForAllPages); + } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs b/src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs deleted file mode 100644 index a79a13c4ec..0000000000 --- a/src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; -using SmartStore.Services.Stores; -using SmartStore.Utilities; - -namespace SmartStore.Services.Tasks -{ - public class ChangeTaskSchedulerBaseUrlConsumer : - IConsumer>, - IConsumer>, - IConsumer> - { - private readonly ITaskScheduler _taskScheduler; - private readonly IStoreService _storeService; - private readonly HttpContextBase _httpContext; - private readonly bool _shouldChange; - - public ChangeTaskSchedulerBaseUrlConsumer(ITaskScheduler taskScheduler, IStoreService storeService, HttpContextBase httpContext) - { - this._taskScheduler = taskScheduler; - this._storeService = storeService; - this._httpContext = httpContext; - this._shouldChange = CommonHelper.GetAppSetting("sm:TaskSchedulerBaseUrl").IsWebUrl() == false; - } - - public void HandleEvent(EntityInserted eventMessage) - { - HandleEventCore(); - } - - public void HandleEvent(EntityUpdated eventMessage) - { - HandleEventCore(); - } - - public void HandleEvent(EntityDeleted eventMessage) - { - HandleEventCore(); - } - - private void HandleEventCore() - { - if (_shouldChange) - { - _taskScheduler.SetBaseUrl(_storeService, _httpContext); - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs index c35b4b7128..28598352c2 100644 --- a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs +++ b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using NCrontab; -using CronExpressionDescriptor; using System.Threading; +using CronExpressionDescriptor; +using NCrontab; namespace SmartStore.Services.Tasks { - public static class CronExpression { - public static bool IsValid(string expression) { try @@ -41,7 +38,7 @@ public static IEnumerable GetFutureSchedules(string expression, DateTi public static IEnumerable GetFutureSchedules(string expression, DateTime baseTime, DateTime endTime, int max = 10) { - Guard.ArgumentNotEmpty(() => expression); + Guard.NotEmpty(expression, nameof(expression)); var schedule = CrontabSchedule.Parse(expression); return schedule.GetNextOccurrences(baseTime, endTime).Take(max); @@ -49,8 +46,6 @@ public static IEnumerable GetFutureSchedules(string expression, DateTi public static string GetFriendlyDescription(string expression) { - Guard.ArgumentNotEmpty(() => expression); - var options = new Options { DayOfWeekStartIndexZero = true, diff --git a/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs index 7e3c5ade52..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 { @@ -108,7 +176,7 @@ public static ScheduleTask GetTaskByType(this IScheduleTaskService service) w public static ScheduleTask GetTaskByType(this IScheduleTaskService service, Type taskType) { - Guard.ArgumentNotNull(() => taskType); + Guard.NotNull(taskType, nameof(taskType)); var name = taskType.AssemblyQualifiedNameWithoutVersion(); 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 f852d12952..0070ad48f6 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs @@ -1,12 +1,8 @@ -using System; -using System.Web; +using System.Collections.Generic; using System.Linq; +using System.Web; using SmartStore.Core; using SmartStore.Services.Stores; -using System.Web.Hosting; -using System.Collections.Generic; -using System.Threading; -using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks { @@ -32,12 +28,12 @@ public interface ITaskScheduler bool IsActive { get; } /// - /// Gets a instance used - /// to signal a task cancellation request. + /// Gets the unique key by which a instance + /// can be retrieved from an instance /// - /// A identifier - /// A instance if the task is running, null otherwise - CancellationTokenSource GetCancelTokenSourceFor(int scheduleTaskId); + /// The schedule task id + /// A unique string key + string GetAsyncStateKey(int scheduleTaskId); /// /// Starts/initializes the scheduler @@ -72,7 +68,6 @@ public interface ITaskScheduler public static class ITaskSchedulerExtensions { - internal static void SetBaseUrl(this ITaskScheduler scheduler, IStoreService storeService, HttpContextBase httpContext) { string url = ""; @@ -93,6 +88,5 @@ internal static void SetBaseUrl(this ITaskScheduler scheduler, IStoreService sto scheduler.BaseUrl = url; } - } } diff --git a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs index 3329fbf3a7..665fca2d30 100644 --- a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs +++ b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs @@ -1,31 +1,23 @@ -using SmartStore.Core; +using System; +using System.Web.Mvc; +using System.Web.Mvc.Filters; using SmartStore.Core.Events; using SmartStore.Core.Infrastructure; -using SmartStore.Services.Stores; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web.Mvc; -using SmartStore.Core.Data; using SmartStore.Core.Logging; +using SmartStore.Services.Stores; using SmartStore.Utilities; namespace SmartStore.Services.Tasks { - public class InitializeSchedulerFilter : IAuthorizationFilter + public class InitializeSchedulerFilter : IAuthenticationFilter { private readonly static object s_lock = new object(); private static int s_errCount; private static bool s_initializing = false; - public void OnAuthorization(AuthorizationContext filterContext) + public void OnAuthentication(AuthenticationContext filterContext) { - if (filterContext == null || filterContext.HttpContext == null) - return; - - var request = filterContext.HttpContext.Request; + var request = filterContext?.HttpContext?.Request; if (request == null) return; @@ -38,15 +30,15 @@ public void OnAuthorization(AuthorizationContext filterContext) { s_initializing = true; - ILogger logger = EngineContext.Current.Resolve(); - ITaskScheduler taskScheduler = EngineContext.Current.Resolve(); + var eventPublisher = EngineContext.Current.Resolve(); + var logger = EngineContext.Current.Resolve().CreateLogger(); + var taskScheduler = EngineContext.Current.Resolve(); try { var taskService = EngineContext.Current.Resolve(); var storeService = EngineContext.Current.Resolve(); - var eventPublisher = EngineContext.Current.Resolve(); - + var tasks = taskService.GetAllTasks(true); taskService.CalculateFutureSchedules(tasks, true /* isAppStart */); @@ -64,7 +56,7 @@ public void OnAuthorization(AuthorizationContext filterContext) taskScheduler.SweepIntervalMinutes = CommonHelper.GetAppSetting("sm:TaskSchedulerSweepInterval", 1); taskScheduler.Start(); - logger.Information("Initialized TaskScheduler with base url '{0}'".FormatInvariant(taskScheduler.BaseUrl)); + logger.Info("Initialized TaskScheduler with base url '{0}'".FormatInvariant(taskScheduler.BaseUrl)); eventPublisher.Publish(new AppInitScheduledTasksEvent { ScheduledTasks = tasks }); } @@ -72,7 +64,7 @@ public void OnAuthorization(AuthorizationContext filterContext) { s_errCount++; s_initializing = false; - logger.Error("Error while initializing Task Scheduler", ex); + logger.Error(ex, "Error while initializing Task Scheduler"); } finally { @@ -85,11 +77,15 @@ public void OnAuthorization(AuthorizationContext filterContext) if (tooManyFailures && logger != null) { - logger.Warning("Stopped trying to initialize the Task Scheduler: too many failed attempts in succession (10+). Maybe uncommenting the setting 'sm:TaskSchedulerBaseUrl' in web.config solves the problem?"); + logger.Warn("Stopped trying to initialize the Task Scheduler: too many failed attempts in succession (10+). Maybe uncommenting the setting 'sm:TaskSchedulerBaseUrl' in web.config solves the problem?"); } } } } } - } + + public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) + { + } + } } diff --git a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs index fe23f1ceef..0b4e3ef725 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs @@ -1,46 +1,52 @@ 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.Core.Logging; using SmartStore.Services.Helpers; +using SmartStore.Utilities; namespace SmartStore.Services.Tasks { - /// - /// Task service - /// public partial class ScheduleTaskService : IScheduleTaskService { - #region Fields - private readonly IRepository _taskRepository; - private readonly IDateTimeHelper _dateTimeHelper; - - #endregion - - #region Ctor - - public ScheduleTaskService(IRepository taskRepository, IDateTimeHelper dateTimeHelper) + 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) { - this._taskRepository = taskRepository; - this._dateTimeHelper = dateTimeHelper; + _taskRepository = taskRepository; + _taskHistoryRepository = taskHistoryRepository; + _dtHelper = dtHelper; + _env = env; + _commonSettings = commonSettings; T = NullLocalizer.Instance; + Logger = NullLogger.Instance; } public Localizer T { get; set; } - - #endregion - - #region Methods + public ILogger Logger { get; set; } public virtual void DeleteTask(ScheduleTask task) { - if (task == null) - throw new ArgumentNullException("task"); + Guard.NotNull(task, nameof(task)); _taskRepository.Delete(task); } @@ -50,8 +56,11 @@ public virtual ScheduleTask GetTaskById(int taskId) if (taskId == 0) return null; - return _taskRepository.GetById(taskId); - } + return Retry.Run( + () => _taskRepository.GetById(taskId), + 3, TimeSpan.FromMilliseconds(100), + RetryOnDeadlockException); + } public virtual ScheduleTask GetTaskByType(string type) { @@ -69,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(); } @@ -85,129 +94,72 @@ public virtual IList GetAllTasks(bool includeDisabled = false) } query = query.OrderByDescending(t => t.Enabled); - var tasks = query.ToList(); - return tasks; - } + return Retry.Run( + () => query.ToList(), + 3, TimeSpan.FromMilliseconds(100), + 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 query.ToList(); - } - - public virtual bool HasRunningTasks() - { - var query = GetRunningTasksQuery(); - return query.Any(); + 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 IsTaskRunning(int taskId) - { - if (taskId <= 0) - return false; - - var query = GetRunningTasksQuery(); - query.Where(t => t.Id == taskId); - return query.Any(); - } - - public virtual IList GetRunningTasks() - { - return GetRunningTasksQuery().ToList(); - } - - 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) { - if (task == null) - throw new ArgumentNullException("task"); + Guard.NotNull(task, nameof(task)); - _taskRepository.Insert(task); + _taskRepository.Insert(task); } public virtual void UpdateTask(ScheduleTask task) { - if (task == null) - throw new ArgumentNullException("task"); - - bool saveFailed; - bool? autoCommit = null; + Guard.NotNull(task, nameof(task)); - do + try { - saveFailed = false; - - // ALWAYS save immediately - try - { - autoCommit = _taskRepository.AutoCommitEnabled; - _taskRepository.AutoCommitEnabled = true; - _taskRepository.Update(task); - } - catch (DbUpdateConcurrencyException ex) - { - saveFailed = true; - - var entry = ex.Entries.Single(); - var current = (ScheduleTask)entry.CurrentValues.ToObject(); // from current scope - - // When 'StopOnError' is true, the 'Enabled' property could have been be set to true on exception. - var enabledModified = entry.Property("Enabled").IsModified; - - // 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); - } - } - finally - { - _taskRepository.AutoCommitEnabled = autoCommit; - } - } while (saveFailed); + _taskRepository.Update(task); + } + catch (Exception ex) + { + Logger.Error(ex); + throw; + } } public ScheduleTask GetOrAddTask(Action newAction) where T : ITask { - Guard.ArgumentNotNull(() => newAction); + Guard.NotNull(newAction, nameof(newAction)); var type = typeof(T); @@ -230,28 +182,73 @@ public ScheduleTask GetOrAddTask(Action newAction) where T : IT public void CalculateFutureSchedules(IEnumerable tasks, bool isAppStart = false) { - Guard.ArgumentNotNull(() => tasks); + Guard.NotNull(tasks, nameof(tasks)); - using (var scope = new DbContextScope(autoCommit: false)) + foreach (var task in tasks) { - var now = DateTime.UtcNow; - foreach (var task in tasks) + task.NextRunUtc = GetNextSchedule(task); + if (isAppStart) + { + FixTypeName(task); + } + else { - 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"); - } - } - this.UpdateTask(task); + UpdateTask(task); } + } + + if (isAppStart) + { + // On app start this method's execution is thread-safe, making it sufficient + // to commit all changes in one go. + _taskRepository.Context.SaveChanges(); + } - scope.Commit(); + 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. + if (task.Type.IndexOf(", ") > 0) + { + task.Type = task.Type.Replace(", ", ", "); } } @@ -261,17 +258,279 @@ public void CalculateFutureSchedules(IEnumerable tasks, bool isApp { try { - var baseTime = DateTime.UtcNow; + var localTimeZone = _dtHelper.DefaultStoreTimeZone; + var baseTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, localTimeZone); var next = CronExpression.GetNextSchedule(task.CronExpression, baseTime); - return next; + var utcTime = _dtHelper.ConvertToUtcTime(next, localTimeZone); + + return utcTime; + } + catch (Exception ex) + { + Logger.ErrorFormat(ex, "Could not calculate next schedule time for task '{0}'", task.Name); } - catch { } } return null; } - #endregion + private static void RetryOnDeadlockException(int attemp, Exception ex) + { + var isDeadLockException = + (ex as EntityCommandExecutionException).IsDeadlockException() || + (ex as SqlException).IsDeadlockException(); + + if (!isDeadLockException) + { + // 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 9b0f1aaad5..fa52d51967 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs @@ -1,35 +1,36 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using Autofac; -using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Tasks; 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) - { - this._componentContext = componentContext; - this._originalTask = originalTask; - this.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); } @@ -40,13 +41,13 @@ public T ResolveNamed(string name) where T : class /// /// A cancellation token for the running task. - /// You can use ThrowIfCancellationRequested() for a hard or IsCancellationRequested for a soft break. + /// You can use ThrowIfCancellationRequested() for a hard, or IsCancellationRequested for a soft break. /// 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 @@ -76,29 +77,84 @@ public void SetProgress(int value, int maximum, string message, bool immediately /// Percentual progress. Can be null or a value between 0 and 100. /// Progress message. Can be null. /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. - public void SetProgress(int? progress, string message, bool immediately = false) + public virtual void SetProgress(int? progress, string message, bool immediately = false) { - if (progress.HasValue) - Guard.ArgumentInRange(progress.Value, 0, 100, "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.ChangeState(_originalTask, System.Data.Entity.EntityState.Modified); dbContext.SaveChanges(); } catch { } } } + + /// + /// Creates a transient context, which can be passed to methods requiring a instance. + /// Such methods are usually executed by the task executor in the background. If a manual invocation of such + /// methods is required, this is the way to go. + /// + /// The component context + /// A transient context + public static TaskExecutionContext CreateTransientContext(IComponentContext componentContext) + { + return CreateTransientContext(componentContext, CancellationToken.None); + } + + /// + /// Creates a transient context, which can be passed to methods requiring a instance. + /// Such methods are usually executed by the task executor in the background. If a manual invocation of such + /// methods is required, this is the way to go. + /// + /// The component context + /// The cancellation token + /// A transient context + public static TaskExecutionContext CreateTransientContext(IComponentContext componentContext, CancellationToken cancellationToken) + { + var originalHistoryEntry = new ScheduleTaskHistory + { + ScheduleTask = new ScheduleTask + { + Name = "Transient", + IsHidden = true, + Enabled = true + } + }; + + var context = new TransientTaskExecutionContext(componentContext, originalHistoryEntry); + context.CancellationToken = cancellationToken; + context.ScheduleTaskHistory = originalHistoryEntry.Clone(); + + return context; + } + } + + internal class TransientTaskExecutionContext : TaskExecutionContext + { + public TransientTaskExecutionContext(IComponentContext componentContext, ScheduleTaskHistory originalHistoryEntry) + : base(componentContext, originalHistoryEntry) + { + } + + public override void SetProgress(int? progress, string message, bool immediately = false) + { + base.SetProgress(progress, message, false /* skip committing to DB */); + } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index 8fd93c2956..93e71a4f7a 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -1,46 +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 ICustomerService _customerService; - 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 CurrentStoreIdParamName = "CurrentStoreId"; - public TaskExecutor( + public TaskExecutor( IScheduleTaskService scheduledTaskService, - IDbContext dbContext, - ICustomerService customerService, - IWorkContext workContext, - IComponentContext componentContext, - Func taskResolver) + IComponentContext componentContext, + IAsyncState asyncState, + Func taskResolver, + IApplicationEnvironment env) { - this._scheduledTaskService = scheduledTaskService; - this._dbContext = dbContext; - this._customerService = customerService; - this._workContext = workContext; - this._componentContext = componentContext; - this._taskResolver = taskResolver; + _scheduledTaskService = scheduledTaskService; + _componentContext = componentContext; + _asyncState = asyncState; + _taskResolver = taskResolver; + _env = env; Logger = NullLogger.Instance; T = NullLocalizer.Instance; @@ -49,142 +44,169 @@ 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 + }; - Debug.WriteLineIf(taskType == null, "Invalid task type: " + task.Type.NaIfEmpty()); + try + { + taskType = Type.GetType(entity.Type); + if (taskType == null) + { + Logger.DebugFormat("Invalid scheduled task type: {0}", entity.Type.NaIfEmpty()); + } if (taskType == null) return; if (!PluginManager.IsActivePluginAssembly(taskType.Assembly)) return; - } - catch + + entity.ScheduleTaskHistory.Add(historyEntry); + _scheduledTaskService.UpdateTask(entity); + } + catch { return; } try { - Customer customer = null; - - // try virtualize current customer (which is necessary when user manually executes a task) - if (taskParameters != null && taskParameters.ContainsKey(CurrentCustomerIdParamName)) - { - customer = _customerService.GetCustomerById(taskParameters[CurrentCustomerIdParamName].ToInt()); - } - - if (customer == null) - { - // no virtualization: set background task system customer as current customer - customer = _customerService.GetCustomerBySystemName(SystemCustomerNames.BackgroundTask); - } - - _workContext.CurrentCustomer = customer; - - // 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.Current.SetCancelTokenSource(cts, stateName); + _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() }; - 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.Warning(T("Admin.System.ScheduleTasks.Cancellation", task.Name), exception); - else - Logger.Error(string.Concat(T("Admin.System.ScheduleTasks.RunningError", task.Name), ": ", exception.Message), exception); + canceled = ex is OperationCanceledException; + lastError = ex.ToAllMessages(true); - if (throwOnError) + if (canceled) { - throw; + Logger.Warn(ex, T("Admin.System.ScheduleTasks.Cancellation", entity.Name)); + } + else + { + Logger.Error(ex, string.Concat(T("Admin.System.ScheduleTasks.RunningError", entity.Name), ": ", ex.Message)); } } finally { - // remove from AsyncState - if (stateName.HasValue()) - { - AsyncState.Current.Remove(stateName); - } - - 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; } - if (task.Enabled) + try + { + Logger.DebugFormat("Executed scheduled task: {0}. Elapsed: {1} ms.", entity.Type, (now - historyEntry.StartedOnUtc).TotalMilliseconds); + + // Remove from AsyncState. + if (stateName.HasValue()) + { + _asyncState.Remove(stateName); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } + + if (entity.Enabled) { - task.NextRunUtc = _scheduledTaskService.GetNextSchedule(task); - } + 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); } - } - private CancellationTokenSource CreateCompositeCancellationTokenSource(CancellationToken userCancellationToken) - { - return CancellationTokenSource.CreateLinkedTokenSource(AsyncRunner.AppShutdownCancellationToken, userCancellationToken); - } + 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 0476b865e6..f8fca60dc9 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Web.Hosting; using SmartStore.Core.Async; using SmartStore.Core.Domain.Tasks; @@ -19,6 +16,8 @@ namespace SmartStore.Services.Tasks { public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegisteredObject { + private readonly ICacheManager _cache; + private bool _intervalFixed; private int _sweepInterval; private string _baseUrl; @@ -26,15 +25,24 @@ public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegistere private bool _shuttingDown; private int _errCount; - public DefaultTaskScheduler() + public DefaultTaskScheduler(ICacheManager cache) { + _cache = cache; + _sweepInterval = 1; _timer = new System.Timers.Timer(); _timer.Elapsed += Elapsed; + Logger = NullLogger.Instance; HostingEnvironment.RegisterObject(this); } + public ILogger Logger + { + get; + set; + } + public int SweepIntervalMinutes { get { return _sweepInterval; } @@ -88,25 +96,23 @@ public bool IsActive get { return _timer.Enabled; } } - public CancellationTokenSource GetCancelTokenSourceFor(int scheduleTaskId) + public string GetAsyncStateKey(int scheduleTaskId) { - var cts = AsyncState.Current.GetCancelTokenSource(scheduleTaskId.ToString()); - return cts; + return scheduleTaskId.ToString(); } private string CreateAuthToken() { string authToken = Guid.NewGuid().ToString(); - var cacheManager = EngineContext.Current.Resolve(); - cacheManager.Set(GenerateAuthTokenCacheKey(authToken), true, TimeSpan.FromMinutes(1)); + _cache.Put(GenerateAuthTokenCacheKey(authToken), true, TimeSpan.FromMinutes(1)); return authToken; } private string GenerateAuthTokenCacheKey(string authToken) { - return "Scheduler.AuthToken." + authToken; + return "Scheduler:AuthToken:" + authToken; } public bool VerifyAuthToken(string authToken) @@ -114,11 +120,10 @@ public bool VerifyAuthToken(string authToken) if (authToken.IsEmpty()) return false; - var cacheManager = EngineContext.Current.Resolve(); var cacheKey = GenerateAuthTokenCacheKey(authToken); - if (cacheManager.Contains(cacheKey)) + if (_cache.Contains(cacheKey)) { - cacheManager.Remove(cacheKey); + _cache.Remove(cacheKey); return true; } @@ -127,14 +132,14 @@ public bool VerifyAuthToken(string authToken) public void RunSingleTask(int scheduleTaskId, IDictionary taskParameters = null) { - string query = ""; + taskParameters = taskParameters ?? new Dictionary(); - if (taskParameters != null && taskParameters.Any()) - { - var qs = new QueryString(); - taskParameters.Each(x => qs.Add(x.Key, x.Value)); - query = qs.ToString(); - } + // User executes task in backend explicitly + taskParameters["Explicit"] = "true"; + + var qs = new QueryString(); + taskParameters.Each(x => qs.Add(x.Key, x.Value)); + var query = qs.ToString(); CallEndpoint(new Uri("{0}/Execute/{1}{2}".FormatInvariant(_baseUrl, scheduleTaskId, query))); } @@ -187,10 +192,7 @@ protected internal virtual void CallEndpoint(Uri uri) { // 10 failed attempts in succession. Stop the timer! this.Stop(); - using (var logger = new TraceLogger()) - { - logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); - } + Logger.Info("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); } } else @@ -198,10 +200,7 @@ protected internal virtual void CallEndpoint(Uri uri) _errCount = 0; var response = t.Result; - //using (var logger = new TraceLogger()) - //{ - // logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); - //} + //_logger.DebugFormat("TaskScheduler Sweep called successfully: {0}", response.GetResponseStream().AsString()); response.Dispose(); } @@ -210,29 +209,32 @@ protected internal virtual void CallEndpoint(Uri uri) private void HandleException(AggregateException exception, Uri uri) { - using (var logger = new TraceLogger()) - { - string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(uri.OriginalString); - var wex = exception.InnerExceptions.OfType().FirstOrDefault(); + string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(uri.OriginalString); + var wex = exception.InnerExceptions.OfType().FirstOrDefault(); - if (wex == null) - { - logger.Error(msg, exception.InnerException); - } - else if (wex.Response == null) - { - logger.Error(msg, wex); - } - else + if (wex == null) + { + Logger.Error(exception.InnerException, msg); + } + else if (wex.Response == null) + { + Logger.Error(wex, msg); + } + else + { + using (var response = wex.Response as HttpWebResponse) { - using (var response = wex.Response as HttpWebResponse) + if (response != null) { - if (response != null) + var statusCode = (int)response.StatusCode; + if (statusCode < 500) { - msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); + // Any internal server error (>= 500) already handled by TaskSchedulerController's exception filter + msg += " HTTP {0}, {1}".FormatCurrent(statusCode, response.StatusDescription); + Logger.Error(msg); } - logger.Error(msg); } + } } } @@ -259,5 +261,4 @@ void IRegisteredObject.Stop(bool immediate) HostingEnvironment.UnregisterObject(this); } } - } diff --git a/src/Libraries/SmartStore.Services/Tax/ITaxService.cs b/src/Libraries/SmartStore.Services/Tax/ITaxService.cs index 336fbce00a..95bbe83faf 100644 --- a/src/Libraries/SmartStore.Services/Tax/ITaxService.cs +++ b/src/Libraries/SmartStore.Services/Tax/ITaxService.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Plugins; +using SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Tax { @@ -83,105 +84,140 @@ public partial interface ITaxService /// Price decimal GetProductPrice(Product product, decimal price, Customer customer, out decimal taxRate); - /// - /// Gets price - /// + /// + /// Gets price + /// /// Product - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetProductPrice(Product product, decimal price, bool includingTax, Customer customer, out decimal taxRate); - - /// - /// Gets price - /// + /// Price + /// Customer + /// Currency + /// Tax rate + /// Price + decimal GetProductPrice(Product product, decimal price, Customer customer, Currency currency, out decimal taxRate); + + /// + /// Gets price + /// /// Product - /// Tax category identifier - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// A value indicating whether price already includes tax - /// Tax rate - /// Price - decimal GetProductPrice(Product product, int taxCategoryId, decimal price, - bool includingTax, Customer customer, - bool priceIncludesTax, out decimal taxRate); - - - - - /// - /// Gets shipping price - /// - /// Price - /// Customer - /// Price - decimal GetShippingPrice(decimal price, Customer customer); - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - decimal GetShippingPrice(decimal price, bool includingTax, Customer customer); - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate); - - - - - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// Customer - /// Price - decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer); - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer); - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate); - - - - - - - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Price - decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav); + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + decimal GetProductPrice(Product product, decimal price, bool includingTax, Customer customer, out decimal taxRate); + + /// + /// Gets price + /// + /// Product + /// Tax category identifier + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Currency + /// A value indicating whether price already includes tax + /// Tax rate + /// Price + decimal GetProductPrice(Product product, + int taxCategoryId, + decimal price, + bool includingTax, + Customer customer, + Currency currency, + bool priceIncludesTax, + out decimal taxRate); + + + + + /// + /// Gets the shipping price + /// + /// Price + /// Customer + /// Shipping price + decimal GetShippingPrice(decimal price, Customer customer); + + /// + /// Gets the shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Shipping price + decimal GetShippingPrice(decimal price, bool includingTax, Customer customer); + + /// + /// Gets the shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Shipping price + decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate); + + /// + /// Gets the shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax category id + /// Tax rate + /// Shipping price + decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, int taxCategoryId, out decimal taxRate); + + + + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// Customer + /// Payment fee + decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer); + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Payment fee + decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer); + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Payment fee + decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate); + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax category id + /// Tax rate + /// Payment fee + decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, int taxCategoryId, out decimal taxRate); + + + + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Price + decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav); /// /// Gets checkout attribute value price diff --git a/src/Libraries/SmartStore.Services/Tax/TaxCategoryService.cs b/src/Libraries/SmartStore.Services/Tax/TaxCategoryService.cs index 4577151ce1..4038498b9b 100644 --- a/src/Libraries/SmartStore.Services/Tax/TaxCategoryService.cs +++ b/src/Libraries/SmartStore.Services/Tax/TaxCategoryService.cs @@ -1,132 +1,66 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; +using SmartStore.Data.Caching; namespace SmartStore.Services.Tax { - /// - /// Tax category service - /// public partial class TaxCategoryService : ITaxCategoryService { - #region Constants - private const string TAXCATEGORIES_ALL_KEY = "SmartStore.taxcategory.all"; - private const string TAXCATEGORIES_PATTERN_KEY = "SmartStore.taxcategory."; - private const string TAXCATEGORIES_BY_ID_KEY = "SmartStore.taxcategory.id-{0}"; - #endregion - - #region Fields - private readonly IRepository _taxCategoryRepository; private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - - #endregion - - #region Ctor public TaxCategoryService( - IRequestCache requestCache, IRepository taxCategoryRepository, IEventPublisher eventPublisher) { - _requestCache = requestCache; _taxCategoryRepository = taxCategoryRepository; _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - /// - /// Deletes a tax category - /// - /// Tax category public virtual void DeleteTaxCategory(TaxCategory taxCategory) { if (taxCategory == null) throw new ArgumentNullException("taxCategory"); _taxCategoryRepository.Delete(taxCategory); - - _requestCache.RemoveByPattern(TAXCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityDeleted(taxCategory); } - /// - /// Gets all tax categories - /// - /// Tax category collection public virtual IList GetAllTaxCategories() { - string key = string.Format(TAXCATEGORIES_ALL_KEY); - return _requestCache.Get(key, () => - { - var query = from tc in _taxCategoryRepository.Table - orderby tc.DisplayOrder - select tc; - var taxCategories = query.ToList(); - return taxCategories; - }); - } + var query = from tc in _taxCategoryRepository.Table + orderby tc.DisplayOrder + select tc; + + var taxCategories = query.ToListCached("db.taxcategory.all"); + return taxCategories; + } - /// - /// Gets a tax category - /// - /// Tax category identifier - /// Tax category public virtual TaxCategory GetTaxCategoryById(int taxCategoryId) { if (taxCategoryId == 0) return null; - string key = string.Format(TAXCATEGORIES_BY_ID_KEY, taxCategoryId); - return _requestCache.Get(key, () => - { - return _taxCategoryRepository.GetById(taxCategoryId); - }); - } + return _taxCategoryRepository.GetByIdCached(taxCategoryId, "db.taxcategory.id-" + taxCategoryId); + } - /// - /// Inserts a tax category - /// - /// Tax category public virtual void InsertTaxCategory(TaxCategory taxCategory) { if (taxCategory == null) throw new ArgumentNullException("taxCategory"); _taxCategoryRepository.Insert(taxCategory); - - _requestCache.RemoveByPattern(TAXCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityInserted(taxCategory); } - /// - /// Updates the tax category - /// - /// Tax category public virtual void UpdateTaxCategory(TaxCategory taxCategory) { if (taxCategory == null) throw new ArgumentNullException("taxCategory"); _taxCategoryRepository.Update(taxCategory); - - _requestCache.RemoveByPattern(TAXCATEGORIES_PATTERN_KEY); - - //event notification - _eventPublisher.EntityUpdated(taxCategory); } - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Tax/TaxService.cs b/src/Libraries/SmartStore.Services/Tax/TaxService.cs index 056c456606..6d5cb78344 100644 --- a/src/Libraries/SmartStore.Services/Tax/TaxService.cs +++ b/src/Libraries/SmartStore.Services/Tax/TaxService.cs @@ -11,6 +11,7 @@ using SmartStore.Core.Plugins; using SmartStore.Services.Common; using SmartStore.Services.Directory; +using SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Tax { @@ -49,13 +50,6 @@ public TaxAddressKey(int customerId, bool productIsEsd) #region Ctor - /// - /// Ctor - /// - /// Address service - /// Work context - /// Tax settings - /// Plugin finder public TaxService( IAddressService addressService, IWorkContext workContext, @@ -146,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; } @@ -229,7 +217,7 @@ protected virtual Address GetTaxAddress(Customer customer, Product product = nul /// Percent /// Increase /// New price - protected decimal CalculatePrice(decimal price, decimal percent, bool increase) + protected decimal CalculatePrice(decimal price, decimal percent, bool increase, Currency currency) { decimal result = decimal.Zero; if (percent == decimal.Zero) @@ -241,16 +229,12 @@ protected decimal CalculatePrice(decimal price, decimal percent, bool increase) } else { - if (_cartSettings.RoundPricesDuringCalculation) - { - // Gross > Net RoundFix - result = price - Math.Round((price) / (100 + percent) * percent, 2); - } - else - { - result = price - (price) / (100 + percent) * percent; - } + var decreaseValue = (price) / (100 + percent) * percent; + result = price - decreaseValue; } + + // Gross > Net RoundFix + result = result.RoundIfEnabledFor(currency); return result; } @@ -386,11 +370,9 @@ protected virtual decimal GetTaxRateCore(Product product, int taxCategoryId, Cus /// Price /// Tax rate /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - out decimal taxRate) + public virtual decimal GetProductPrice(Product product, decimal price, out decimal taxRate) { - var customer = _workContext.CurrentCustomer; - return GetProductPrice(product, price, customer, out taxRate); + return GetProductPrice(product, price, _workContext.CurrentCustomer, out taxRate); } /// @@ -401,56 +383,65 @@ public virtual decimal GetProductPrice(Product product, decimal price, /// Customer /// Tax rate /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - Customer customer, out decimal taxRate) + public virtual decimal GetProductPrice(Product product, decimal price, Customer customer, out decimal taxRate) { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetProductPrice(product, price, includingTax, customer, out taxRate); } - /// - /// Gets price - /// + public virtual decimal GetProductPrice(Product product, decimal price, Customer customer, Currency currency, out decimal taxRate) + { + var includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var priceIncludesTax = _taxSettings.PricesIncludeTax; + var taxCategoryId = product.TaxCategoryId; // 0; // (VATFIX) + + return GetProductPrice(product, taxCategoryId, price, includingTax, customer, currency, priceIncludesTax, out taxRate); + } + + /// + /// Gets price + /// /// Product - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - bool includingTax, Customer customer, out decimal taxRate) + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, bool includingTax, Customer customer, out decimal taxRate) { - bool priceIncludesTax = _taxSettings.PricesIncludeTax; - int taxCategoryId = product.TaxCategoryId; // 0; // (VATFIX) - return GetProductPrice(product, taxCategoryId, price, includingTax, - customer, priceIncludesTax, out taxRate); - } + var priceIncludesTax = _taxSettings.PricesIncludeTax; + var taxCategoryId = product.TaxCategoryId; // 0; // (VATFIX) - /// - /// Gets price - /// + return GetProductPrice(product, taxCategoryId, price, includingTax, customer, _workContext.WorkingCurrency, priceIncludesTax, out taxRate); + } + + /// + /// Gets price + /// /// Product - /// Tax category identifier - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// A value indicating whether price already includes tax - /// Tax rate - /// Price - public virtual decimal GetProductPrice( + /// Tax category identifier + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// A value indicating whether price already includes tax + /// Tax rate + /// Price + public virtual decimal GetProductPrice( Product product, int taxCategoryId, decimal price, bool includingTax, Customer customer, - bool priceIncludesTax, + Currency currency, + bool priceIncludesTax, out decimal taxRate) { // don't calculate if price is 0 if (price == decimal.Zero) { taxRate = decimal.Zero; - return taxRate; + return decimal.Zero; } taxRate = GetTaxRate(product, taxCategoryId, customer); @@ -460,7 +451,7 @@ public virtual decimal GetProductPrice( { if (!includingTax) { - price = CalculatePrice(price, taxRate, false); + price = CalculatePrice(price, taxRate, false, currency); } } // Admin: NET prices @@ -468,7 +459,7 @@ public virtual decimal GetProductPrice( { if (includingTax) { - price = CalculatePrice(price, taxRate, true); + price = CalculatePrice(price, taxRate, true, currency); } } @@ -482,108 +473,81 @@ public virtual decimal GetProductPrice( - /// - /// Gets shipping price - /// - /// Price - /// Customer - /// Price - public virtual decimal GetShippingPrice(decimal price, Customer customer) + public virtual decimal GetShippingPrice(decimal price, Customer customer) { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var includingTax = (_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); return GetShippingPrice(price, includingTax, customer); } - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer) { - decimal taxRate = decimal.Zero; + var taxRate = decimal.Zero; return GetShippingPrice(price, includingTax, customer, out taxRate); } - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate) { - taxRate = decimal.Zero; - - if (!_taxSettings.ShippingIsTaxable) - { - return price; - } - - bool priceIncludesTax = _taxSettings.ShippingPriceIncludesTax; - int taxClassId = _taxSettings.ShippingTaxClassId; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - + return GetShippingPrice(price, includingTax, customer, _taxSettings.ShippingTaxClassId, out taxRate); + } + public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, int taxCategoryId, out decimal taxRate) + { + taxRate = decimal.Zero; + + if (!_taxSettings.ShippingIsTaxable) + return price; + + var result = GetProductPrice( + null, + taxCategoryId, + price, + includingTax, + customer, + _workContext.WorkingCurrency, + _taxSettings.ShippingPriceIncludesTax, + out taxRate); + + return result; + } - /// - /// Gets payment method additional handling fee - /// - /// Price - /// Customer - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer) + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer) { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var includingTax = (_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); return GetPaymentMethodAdditionalFee(price, includingTax, customer); } - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer) { - decimal taxRate = decimal.Zero; - return GetPaymentMethodAdditionalFee(price, includingTax, - customer, out taxRate); + var taxRate = decimal.Zero; + return GetPaymentMethodAdditionalFee(price, includingTax, customer, out taxRate); } - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate) + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate) + { + return GetPaymentMethodAdditionalFee(price, includingTax, customer, _taxSettings.PaymentMethodAdditionalFeeTaxClassId, out taxRate); + } + + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, int taxCategoryId, out decimal taxRate) { taxRate = decimal.Zero; if (!_taxSettings.PaymentMethodAdditionalFeeIsTaxable) - { return price; - } - - bool priceIncludesTax = _taxSettings.PaymentMethodAdditionalFeeIncludesTax; - int taxClassId = _taxSettings.PaymentMethodAdditionalFeeTaxClassId; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } + var result = GetProductPrice( + null, + taxCategoryId, + price, + includingTax, + customer, + _workContext.WorkingCurrency, + _taxSettings.PaymentMethodAdditionalFeeIncludesTax, + out taxRate); + return result; + } @@ -640,17 +604,16 @@ public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, taxRate = decimal.Zero; - bool priceIncludesTax = _taxSettings.PricesIncludeTax; + var priceIncludesTax = _taxSettings.PricesIncludeTax; + var taxClassId = cav.CheckoutAttribute.TaxCategoryId; + var price = cav.PriceAdjustment; - decimal price = cav.PriceAdjustment; if (cav.CheckoutAttribute.IsTaxExempt) { return price; } - int taxClassId = cav.CheckoutAttribute.TaxCategoryId; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); + return GetProductPrice(null, taxClassId, price, includingTax, customer, _workContext.WorkingCurrency, priceIncludesTax, out taxRate); } diff --git a/src/Libraries/SmartStore.Services/Themes/ThemeValidationException.cs b/src/Libraries/SmartStore.Services/Themes/ThemeValidationException.cs new file mode 100644 index 0000000000..060775b69b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Themes/ThemeValidationException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Services.Themes +{ + public class ThemeValidationException : Exception + { + public ThemeValidationException(string message, IDictionary attemptedVars) + : base(message) + { + Guard.NotNull(attemptedVars, nameof(attemptedVars)); + + AttemptedVars = attemptedVars; + } + + public IDictionary AttemptedVars { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs b/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs index 85f69384db..79f000af32 100644 --- a/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs +++ b/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs @@ -9,31 +9,40 @@ using SmartStore.Core.Caching; using SmartStore.Core.Events; using System.Xml; +using SmartStore.Core; +using System.Net; +using System.Web; +using System.IO; namespace SmartStore.Services.Themes { - public class ThemeVariablesService : IThemeVariablesService { private const string THEMEVARS_BY_THEME_KEY = "SmartStore.themevars.theme-{0}-{1}"; private const string THEMEVARS_PATTERN_KEY = "SmartStore.themevars."; - - private readonly IRepository _rsVariables; + + private readonly IRepository _rsVariables; private readonly IThemeRegistry _themeRegistry; private readonly IRequestCache _requestCache; private readonly IEventPublisher _eventPublisher; + private readonly Lazy _themeFileResolver; + private readonly HttpContextBase _httpContext; - public ThemeVariablesService( + public ThemeVariablesService( IRepository rsVariables, IThemeRegistry themeRegistry, IRequestCache requestCache, - IEventPublisher eventPublisher) + IEventPublisher eventPublisher, + Lazy themeFileResolver, + HttpContextBase httpContext) { - this._rsVariables = rsVariables; - this._themeRegistry = themeRegistry; - this._requestCache = requestCache; - this._eventPublisher = eventPublisher; - } + _rsVariables = rsVariables; + _themeRegistry = themeRegistry; + _requestCache = requestCache; + _eventPublisher = eventPublisher; + _themeFileResolver = themeFileResolver; + _httpContext = httpContext; + } public virtual ExpandoObject GetThemeVariables(string themeName, int storeId) { @@ -56,10 +65,12 @@ public virtual ExpandoObject GetThemeVariables(string themeName, int storeId) }); // ...then merge with persisted runtime records - var query = from v in _rsVariables.Table + var query = from v in _rsVariables.TableUntracked where v.StoreId == storeId && v.Theme.Equals(themeName, StringComparison.OrdinalIgnoreCase) select v; - query.Each(v => { + + query.ToList().Each(v => + { if (v.Value.HasValue() && dict.ContainsKey(v.Name)) { dict[v.Name] = v.Value; @@ -72,45 +83,90 @@ public virtual ExpandoObject GetThemeVariables(string themeName, int storeId) public virtual void DeleteThemeVariables(string themeName, int storeId) { - Guard.ArgumentNotEmpty(themeName, "themeName"); + DeleteThemeVariablesInternal(themeName, storeId); + } - var query = from v in _rsVariables.Table - where v.StoreId == storeId && v.Theme.Equals(themeName, StringComparison.OrdinalIgnoreCase) - select v; + private void DeleteThemeVariablesInternal(string themeName, int storeId) + { + Guard.NotEmpty(themeName, nameof(themeName)); - if (query.Any()) - { - using (var scope = new DbContextScope(ctx: _rsVariables.Context, autoCommit: false)) + var query = from v in _rsVariables.Table + where v.StoreId == storeId && v.Theme == themeName + select v; + + if (query.Any()) + { + using (var scope = new DbContextScope(ctx: _rsVariables.Context, autoCommit: false)) { query.Each(v => { _rsVariables.Delete(v); - _eventPublisher.EntityDeleted(v); }); _requestCache.Remove(THEMEVARS_BY_THEME_KEY.FormatInvariant(themeName, storeId)); _rsVariables.Context.SaveChanges(); } - } - } + } + } public virtual int SaveThemeVariables(string themeName, int storeId, IDictionary variables) - { - Guard.ArgumentNotEmpty(themeName, "themeName"); - Guard.Against(!_themeRegistry.ThemeManifestExists(themeName), "The theme '{0}' does not exist in the registry.".FormatInvariant(themeName)); - Guard.ArgumentNotNull(variables, "variables"); + { + Guard.NotEmpty(themeName, nameof(themeName)); + Guard.Against(!_themeRegistry.ThemeManifestExists(themeName), "The theme '{0}' does not exist in the registry.".FormatInvariant(themeName)); + Guard.NotNull(variables, nameof(variables)); - if (!variables.Any()) - return 0; + if (!variables.Any()) + return 0; - var count = 0; - var infos = _themeRegistry.GetThemeManifest(themeName).Variables; + var manifest = _themeRegistry.GetThemeManifest(themeName); + if (manifest == null) + { + throw new ArgumentException("Theme '{0}' does not exist".FormatInvariant(themeName), nameof(themeName)); + } + + // Get current for later restore on parse error + var currentVars = GetThemeVariables(themeName, storeId); + + // Save + var result = SaveThemeVariablesInternal(manifest, storeId, variables); + + if (result.TouchedVariablesCount > 0) + { + // Check for parsing error + string error = ValidateSass(manifest, storeId); + if (error.HasValue()) + { + // Restore previous vars + try + { + DeleteThemeVariablesInternal(themeName, storeId); + } + finally + { + // We do it here to absolutely ensure that this gets called + SaveThemeVariablesInternal(manifest, storeId, currentVars); + } + + throw new ThemeValidationException(error, variables); + } + } + + return result.TouchedVariablesCount; + } + + private SaveThemeVariablesResult SaveThemeVariablesInternal(ThemeManifest manifest, int storeId, IDictionary variables) + { + var result = new SaveThemeVariablesResult(); + var infos = manifest.Variables; using (var scope = new DbContextScope(ctx: _rsVariables.Context, autoCommit: false)) { var unsavedVars = new List(); - var savedThemeVars = _rsVariables.Table.Where(v => v.StoreId == storeId && v.Theme.Equals(themeName, StringComparison.OrdinalIgnoreCase)).ToList(); + var savedThemeVars = _rsVariables.Table + .Where(v => v.StoreId == storeId && v.Theme == manifest.ThemeName) + .ToDictionary(x => x.Name); + bool touched = false; foreach (var v in variables) @@ -125,16 +181,15 @@ public virtual int SaveThemeVariables(string themeName, int storeId, IDictionary var value = v.Value == null ? string.Empty : v.Value.ToString(); - var savedThemeVar = savedThemeVars.FirstOrDefault(x => x.Name == v.Key); + var savedThemeVar = savedThemeVars.Get(v.Key); if (savedThemeVar != null) { if (value.IsEmpty() || String.Equals(info.DefaultValue, value, StringComparison.CurrentCultureIgnoreCase)) { // it's either null or the default value, so delete _rsVariables.Delete(savedThemeVar); - _eventPublisher.EntityDeleted(savedThemeVar); + result.Deleted.Add(savedThemeVar); touched = true; - count++; } else { @@ -142,9 +197,8 @@ public virtual int SaveThemeVariables(string themeName, int storeId, IDictionary if (!savedThemeVar.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) { savedThemeVar.Value = value; - _eventPublisher.EntityUpdated(savedThemeVar); + result.Updated.Add(savedThemeVar); touched = true; - count++; } } } @@ -156,15 +210,14 @@ public virtual int SaveThemeVariables(string themeName, int storeId, IDictionary unsavedVars.Add(v.Key); savedThemeVar = new ThemeVariable { - Theme = themeName, + Theme = manifest.ThemeName, Name = v.Key, Value = value, StoreId = storeId }; _rsVariables.Insert(savedThemeVar); - _eventPublisher.EntityInserted(savedThemeVar); + result.Inserted.Add(savedThemeVar); touched = true; - count++; } } } @@ -175,13 +228,13 @@ public virtual int SaveThemeVariables(string themeName, int storeId, IDictionary } } - return count; + return result; } public int ImportVariables(string themeName, int storeId, string configurationXml) { - Guard.ArgumentNotEmpty(themeName, "themeName"); - Guard.ArgumentNotEmpty(configurationXml, "configurationXml"); + Guard.NotEmpty(themeName, nameof(themeName)); + Guard.NotEmpty(configurationXml, nameof(configurationXml)); var dict = new Dictionary(); @@ -213,7 +266,7 @@ public int ImportVariables(string themeName, int storeId, string configurationXm public string ExportVariables(string themeName, int storeId) { - Guard.ArgumentNotEmpty(themeName, "themeName"); + Guard.NotEmpty(themeName, nameof(themeName)); var vars = this.GetThemeVariables(themeName, storeId) as IDictionary; @@ -256,6 +309,90 @@ public string ExportVariables(string themeName, int storeId) return sb.ToString(); } - } + + /// + /// Validates the result SASS file by calling it's url. + /// + /// Theme name + /// Stored Id + /// The error message when a parsing error occured, null otherwise + private string ValidateSass(ThemeManifest manifest, int storeId) + { + string error = string.Empty; + + var virtualPath = "~/Themes/{0}/Content/theme.scss".FormatCurrent(manifest.ThemeName); + var resolver = _themeFileResolver.Value; + var file = resolver.Resolve(virtualPath); + if (file != null) + { + virtualPath = file.ResultVirtualPath; + } + + var url = "{0}?storeId={1}&theme={2}&validate=1".FormatInvariant( + WebHelper.GetAbsoluteUrl(virtualPath, _httpContext.Request), + storeId, + manifest.ThemeName); + + var request = WebHelper.CreateHttpRequestForSafeLocalCall(new Uri(url)); + WebResponse response = null; + + try + { + response = request.GetResponse(); + } + catch (WebException ex) + { + if (ex.Response is HttpWebResponse) + { + var webResponse = (HttpWebResponse)ex.Response; + var statusCode = webResponse.StatusCode; + + if (statusCode == HttpStatusCode.InternalServerError) + { + // catch only 500, as this indicates a parsing error. + var stream = webResponse.GetResponseStream(); + + using (var streamReader = new StreamReader(stream)) + { + // read the content (the error message has been put there) + error = streamReader.ReadToEnd(); + streamReader.Close(); + stream.Close(); + } + } + } + } + catch (Exception ex) + { + error = ex.Message; + } + finally + { + if (response != null) + response.Close(); + } + + return error; + } + } + + class SaveThemeVariablesResult + { + public SaveThemeVariablesResult() + { + Inserted = new List(); + Updated = new List(); + Deleted = new List(); + } + + public IList Inserted { get; private set; } + public IList Updated { get; private set; } + public IList Deleted { get; private set; } + + public int TouchedVariablesCount + { + get { return Inserted.Count + Updated.Count + Deleted.Count; } + } + } } 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 9061637581..07152bf79b 100644 --- a/src/Libraries/SmartStore.Services/Topics/TopicService.cs +++ b/src/Libraries/SmartStore.Services/Topics/TopicService.cs @@ -1,173 +1,144 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; +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 { - /// - /// Topic service - /// public partial class TopicService : ITopicService { - private const string TOPICS_ALL_KEY = "SmartStore.topics.all-{0}"; - private const string TOPICS_PATTERN_KEY = "SmartStore.topics."; - - #region Fields - + private readonly ICommonServices _services; private readonly IRepository _topicRepository; private readonly IRepository _storeMappingRepository; - private readonly IEventPublisher _eventPublisher; - private readonly IRequestCache _requestCache; - - #endregion - - #region Ctor + private readonly IStoreMappingService _storeMappingService; + private readonly IRepository _aclRepository; public TopicService( + ICommonServices services, IRepository topicRepository, IRepository storeMappingRepository, - IEventPublisher eventPublisher, - IRequestCache requestCache) + IStoreMappingService storeMappingService, + IRepository aclRepository) { - _topicRepository = topicRepository; + _services = services; + _topicRepository = topicRepository; _storeMappingRepository = storeMappingRepository; - _eventPublisher = eventPublisher; - _requestCache = requestCache; + _storeMappingService = storeMappingService; + _aclRepository = aclRepository; this.QuerySettings = DbQuerySettings.Default; } public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Methods - - /// - /// Deletes a topic - /// - /// Topic public virtual void DeleteTopic(Topic topic) { - if (topic == null) - throw new ArgumentNullException("topic"); - - _topicRepository.Delete(topic); - - _requestCache.RemoveByPattern(TOPICS_PATTERN_KEY); + Guard.NotNull(topic, nameof(topic)); - //event notification - _eventPublisher.EntityDeleted(topic); + _topicRepository.Delete(topic); } - /// - /// Gets a topic - /// - /// The topic identifier - /// Topic public virtual Topic GetTopicById(int topicId) { if (topicId == 0) return null; - return _topicRepository.GetById(topicId); + return _topicRepository.GetByIdCached(topicId, "db.topic.id-" + topicId); } - /// - /// Gets a topic - /// - /// The topic system name - /// Store identifier - /// Topic - public virtual Topic GetTopicBySystemName(string systemName, int storeId) + public virtual Topic GetTopicBySystemName(string systemName, int storeId = 0, bool checkPermission = true) { - if (String.IsNullOrEmpty(systemName)) - return null; - - var allTopics = GetAllTopics(storeId); + if (systemName.IsEmpty()) + return null; - var topic = allTopics - .OrderBy(x => x.Id) - .FirstOrDefault(x => x.SystemName.IsCaseInsensitiveEqual(systemName)); + 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)); - return topic; + return result; } - /// - /// Gets all topics - /// - /// Store identifier; pass 0 to load all records - /// Topics - public virtual IList GetAllTopics(int storeId) + public virtual IPagedList GetAllTopics(int storeId = 0, int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) { - var result = _requestCache.Get(TOPICS_ALL_KEY.FormatInvariant(storeId), () => + 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()) { - var query = _topicRepository.Table; - - //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 - select t; - - //only distinct items (group by ID) - query = from t in query - group t by t.Id into tGroup - orderby tGroup.Key - select tGroup.FirstOrDefault(); - } - - query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); - - return query.ToList(); - }); + query = query.Where(x => x.SystemName == systemName); + } - return result; - } + // Store mapping + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = from t in query + 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; + + 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(); - /// - /// Inserts a topic - /// - /// Topic - public virtual void InsertTopic(Topic topic) - { - if (topic == null) - throw new ArgumentNullException("topic"); + 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; - _topicRepository.Insert(topic); + joinApplied = true; + } - _requestCache.RemoveByPattern(TOPICS_PATTERN_KEY); + if (joinApplied) + { + // Only distinct topics (group by ID) + query = from t in query + group t by t.Id into tGroup + orderby tGroup.Key + select tGroup.FirstOrDefault(); + } - //event notification - _eventPublisher.EntityInserted(topic); - } + query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); - /// - /// Updates the topic - /// - /// Topic - public virtual void UpdateTopic(Topic topic) + return query; + } + + public virtual void InsertTopic(Topic topic) { - if (topic == null) - throw new ArgumentNullException("topic"); + Guard.NotNull(topic, nameof(topic)); - _topicRepository.Update(topic); + _topicRepository.Insert(topic); + } - _requestCache.RemoveByPattern(TOPICS_PATTERN_KEY); + public virtual void UpdateTopic(Topic topic) + { + Guard.NotNull(topic, nameof(topic)); - //event notification - _eventPublisher.EntityUpdated(topic); + _topicRepository.Update(topic); } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/Reference.cs b/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/Reference.cs index dc2b98b1a8..02ecf2d637 100644 --- a/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/Reference.cs +++ b/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/Reference.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.34209 +// Laufzeitversion:4.0.30319.42000 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ // -// Der Quellcode wurde automatisch mit Microsoft.VSDesigner generiert. Version 4.0.30319.34209. +// Der Quellcode wurde automatisch mit Microsoft.VSDesigner generiert. Version 4.0.30319.42000. // #pragma warning disable 1591 @@ -23,10 +23,10 @@ namespace SmartStore.Services.EuropaCheckVatService { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Web.Services.WebServiceBindingAttribute(Name="checkVatBinding", Namespace="urn:ec.europa.eu:taxud:vies:services:checkVat")] + [System.Web.Services.WebServiceBindingAttribute(Name="checkVatBinding", Namespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types")] public partial class checkVatService : System.Web.Services.Protocols.SoapHttpClientProtocol { private System.Threading.SendOrPostCallback checkVatOperationCompleted; @@ -37,7 +37,7 @@ public partial class checkVatService : System.Web.Services.Protocols.SoapHttpCli /// public checkVatService() { - this.Url = global::SmartStore.Services.Properties.Settings.Default.SmartStore_Services_EuropaCheckVatService_checkVatService; + this.Url = global::SmartStore.Services.Properties.Settings.Default.Nop_Services_EuropaCheckVatService_checkVatService; if ((this.IsLocalFileSystemWebService(this.Url) == true)) { this.UseDefaultCredentials = true; this.useDefaultCredentialsSetExplicitly = false; @@ -222,7 +222,7 @@ private bool IsLocalFileSystemWebService(string url) { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types")] public enum matchCode { @@ -237,11 +237,11 @@ public enum matchCode { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void checkVatCompletedEventHandler(object sender, checkVatCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class checkVatCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -303,11 +303,11 @@ public string address { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void checkVatApproxCompletedEventHandler(object sender, checkVatApproxCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class checkVatApproxCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/matchCode.datasource b/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/matchCode.datasource index 9e42d3b6ec..39c1b02382 100644 --- a/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/matchCode.datasource +++ b/src/Libraries/SmartStore.Services/Web References/EuropaCheckVatService/matchCode.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.Services.EuropaCheckVatService.matchCode, Web References.EuropaCheckVatService.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.Services.EuropaCheckVatService.matchCode, Web References.EuropaCheckVatService.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/app.config b/src/Libraries/SmartStore.Services/app.config index a9250b2fff..08f9834796 100644 --- a/src/Libraries/SmartStore.Services/app.config +++ b/src/Libraries/SmartStore.Services/app.config @@ -21,7 +21,7 @@ - + @@ -29,7 +29,19 @@ - + + + + + + + + + + + + + @@ -39,4 +51,4 @@ - + diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index d078b22719..be5db3727d 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -1,20 +1,27 @@  - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/AmazonPaySettings.cs b/src/Plugins/SmartStore.AmazonPay/AmazonPaySettings.cs new file mode 100644 index 0000000000..21cce48dbe --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/AmazonPaySettings.cs @@ -0,0 +1,65 @@ +using SmartStore.AmazonPay.Services; +using SmartStore.Core.Configuration; + +namespace SmartStore.AmazonPay +{ + public class AmazonPaySettings : ISettings + { + public AmazonPaySettings() + { + Marketplace = "de"; + DataFetching = AmazonPayDataFetchingType.Ipn; + TransactionType = AmazonPayTransactionType.Authorize; + AuthorizeMethod = AmazonPayAuthorizeMethod.Omnichronous; + SaveEmailAndPhone = AmazonPaySaveDataType.OnlyIfEmpty; + AddOrderNotes = true; + InformCustomerAboutErrors = true; + InformCustomerAddErrors = true; + PollingMaxOrderCreationDays = 31; + + PayButtonColor = "Gold"; + PayButtonSize = "small"; + AuthButtonType = "LwA"; + AuthButtonColor = "Gold"; + AuthButtonSize = "medium"; + } + + public bool UseSandbox { get; set; } + + public string SellerId { get; set; } + public string AccessKey { get; set; } + public string SecretKey { get; set; } + public string ClientId { get; set; } + public string Marketplace { get; set; } + + public AmazonPayDataFetchingType DataFetching { get; set; } + public AmazonPayTransactionType TransactionType { get; set; } + public AmazonPayAuthorizeMethod AuthorizeMethod { get; set; } + + public AmazonPaySaveDataType? SaveEmailAndPhone { get; set; } + public bool ShowPayButtonForAdminOnly { get; set; } + public bool ShowButtonInMiniShoppingCart { get; set; } + + public int PollingMaxOrderCreationDays { get; set; } + + public decimal AdditionalFee { get; set; } + public bool AdditionalFeePercentage { get; set; } + + public bool AddOrderNotes { get; set; } + + public bool InformCustomerAboutErrors { get; set; } + public bool InformCustomerAddErrors { get; set; } + + public string PayButtonColor { get; set; } + public string PayButtonSize { get; set; } + + public string AuthButtonType { get; set; } + public string AuthButtonColor { get; set; } + public string AuthButtonSize { get; set; } + + public bool CanSaveEmailAndPhone(string value) + { + return (SaveEmailAndPhone == AmazonPaySaveDataType.Always || (SaveEmailAndPhone == AmazonPaySaveDataType.OnlyIfEmpty && value.IsEmpty())); + } + } +} diff --git a/src/Plugins/SmartStore.AmazonPay/Content/SmartStore.AmazonPay.css b/src/Plugins/SmartStore.AmazonPay/Content/SmartStore.AmazonPay.css new file mode 100644 index 0000000000..c6a9d38b4c --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Content/SmartStore.AmazonPay.css @@ -0,0 +1,36 @@ + +#amazon-pay-address-book-widget { + min-width: 300px; + width: 100%; + max-width: 900px; + min-height: 228px; + height: 240px; + max-height: 400px; +} + +#amazon-pay-wallet-widget { + min-width: 300px; + width: 100%; + max-width: 900px; + min-height: 228px; + height: 240px; + max-height: 400px; +} + +#amazon-pay-read-address-book-widget { + min-width: 266px; + width: 100%; + max-width: 900px; + min-height: 145px; + height: 165px; + max-height: 180px; +} + +#amazon-pay-read-wallet-widget { + min-width: 266px; + width: 100%; + max-width: 900px; + min-height: 145px; + height: 165px; + max-height: 180px; +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Content/branding.gif b/src/Plugins/SmartStore.AmazonPay/Content/branding.gif deleted file mode 100644 index bc6f68849f..0000000000 Binary files a/src/Plugins/SmartStore.AmazonPay/Content/branding.gif and /dev/null differ diff --git a/src/Plugins/SmartStore.AmazonPay/Content/branding.png b/src/Plugins/SmartStore.AmazonPay/Content/branding.png new file mode 100644 index 0000000000..179fb46130 Binary files /dev/null and b/src/Plugins/SmartStore.AmazonPay/Content/branding.png differ diff --git a/src/Plugins/SmartStore.AmazonPay/Content/images/logo.gif b/src/Plugins/SmartStore.AmazonPay/Content/images/logo.gif deleted file mode 100644 index bc6f68849f..0000000000 Binary files a/src/Plugins/SmartStore.AmazonPay/Content/images/logo.gif and /dev/null differ diff --git a/src/Plugins/SmartStore.AmazonPay/Content/smartstore.amazonpay.css b/src/Plugins/SmartStore.AmazonPay/Content/smartstore.amazonpay.css deleted file mode 100644 index 2920ce6f7f..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Content/smartstore.amazonpay.css +++ /dev/null @@ -1,14 +0,0 @@ -.config-logo { - width: 339px; - height: 74px; -} - -.amazon-pay-button .selection-text { - text-align: right; - font-weight: bold; - padding: 12px 15px 5px 0; -} - -.amazon-pay-button .button-container { - text-align: right; -} diff --git a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs index 4a76ef8295..35fbe06a06 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs @@ -1,15 +1,69 @@ -using System.Web.Mvc; +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(IAmazonPayService apiService) + public AmazonPayCheckoutController( + HttpContextBase httpContext, + IAmazonPayService apiService, + IGenericAttributeService genericAttributeService, + IOrderProcessingService orderProcessingService) { + _httpContext = httpContext; _apiService = apiService; + _genericAttributeService = genericAttributeService; + _orderProcessingService = orderProcessingService; + } + + public ActionResult OrderReferenceCreated(string orderReferenceId/*, string accessToken*/) + { + var success = false; + var error = string.Empty; + + try + { + var state = _httpContext.GetAmazonPayState(Services.Localization); + state.OrderReferenceId = orderReferenceId; + + //if (accessToken.HasValue()) + //{ + // state.AccessToken = accessToken; + //} + + if (state.OrderReferenceId.IsEmpty()) + { + success = false; + error = T("Plugins.Payments.AmazonPay.MissingOrderReferenceId"); + } + + if (state.AccessToken.IsEmpty()) + { + success = false; + error = error.Grow(T("Plugins.Payments.AmazonPay.MissingAddressConsentToken"), " "); + } + } + catch (Exception ex) + { + error = ex.Message; + } + + return new JsonResult { Data = new { success = success, error = error } }; } public ActionResult BillingAddress() @@ -19,22 +73,30 @@ public ActionResult BillingAddress() public ActionResult ShippingAddress() { - var model = _apiService.ProcessPluginRequest(AmazonPayRequestType.Address, TempData); + var model = _apiService.CreateViewModel(AmazonPayRequestType.Address, TempData); return GetActionResult(model); } public ActionResult PaymentMethod() { - var model = _apiService.ProcessPluginRequest(AmazonPayRequestType.Payment, TempData); + var model = _apiService.CreateViewModel(AmazonPayRequestType.PaymentMethod, TempData); return GetActionResult(model); } [HttpPost] - public ActionResult PaymentMethod(bool? UseRewardPoints) + public ActionResult PaymentMethod(FormCollection form) { - _apiService.ApplyRewardPoints(UseRewardPoints ?? false); + // 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 = "" }); } @@ -43,5 +105,108 @@ public ActionResult PaymentInfo() { return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); } - } + + public ActionResult CheckoutCompleted() + { + var note = _httpContext.Session["AmazonPayCheckoutCompletedNote"] as string; + if (note.HasValue()) + { + return Content(note); + } + + 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 d6da586aca..669cc4c009 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs @@ -1,11 +1,18 @@ -using System.Collections.Generic; +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.AmazonPay.Settings; -using SmartStore.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.Stores; +using SmartStore.Services.Tasks; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; @@ -14,18 +21,24 @@ namespace SmartStore.AmazonPay.Controllers { public class AmazonPayController : PaymentControllerBase { - private readonly IAmazonPayService _apiService; - private readonly ICommonServices _services; - private readonly IStoreService _storeService; + private readonly HttpContextBase _httpContext; + private readonly IAmazonPayService _apiService; + private readonly Lazy _scheduleTaskService; + private readonly Lazy _openAuthenticationService; + private readonly Lazy _externalAuthenticationSettings; public AmazonPayController( - IAmazonPayService apiService, - ICommonServices services, - IStoreService storeService) + HttpContextBase httpContext, + IAmazonPayService apiService, + Lazy scheduleTaskService, + Lazy openAuthenticationService, + Lazy externalAuthenticationSettings) { + _httpContext = httpContext; _apiService = apiService; - _services = services; - _storeService = storeService; + _scheduleTaskService = scheduleTaskService; + _openAuthenticationService = openAuthenticationService; + _externalAuthenticationSettings = externalAuthenticationSettings; } [NonAction] @@ -42,59 +55,204 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) return paymentInfo; } - [AdminAuthorize] - public ActionResult Configure() + [AdminAuthorize, LoadSetting] + public ActionResult Configure(AmazonPaySettings settings) { var model = new ConfigurationModel(); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); - - model.Copy(settings, true); + MiniMapper.Map(settings, model); _apiService.SetupConfiguration(model); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); - return View(model); } [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(); + return Configure(settings); ModelState.Clear(); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); + model.AccessKey = model.AccessKey.TrimSafe(); + model.ClientId = model.ClientId.TrimSafe(); + model.SecretKey = model.SecretKey.TrimSafe(); + model.SellerId = model.SellerId.TrimSafe(); + + MiniMapper.Map(model, settings); - model.Copy(settings, false); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - using (_services.Settings.BeginBatch()) + using (Services.Settings.BeginScope()) { - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + Services.Settings.SaveSetting(settings, x => x.DataFetching, 0, false); + Services.Settings.SaveSetting(settings, x => x.PollingMaxOrderCreationDays, 0, false); + } - _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) + { + task.Enabled = settings.DataFetching == AmazonPayDataFetchingType.Polling; + + _scheduleTaskService.Value.UpdateTask(task); } - _apiService.DataPollingTaskUpdate(settings.DataFetching == AmazonPayDataFetchingType.Polling, model.PollingTaskMinutes * 60); + NotifySuccess(T("Plugins.Payments.AmazonPay.ConfigSaveNote")); + + return RedirectToConfiguration(AmazonPayPlugin.SystemName); + } + + [HttpPost, AdminAuthorize] + public ActionResult SaveAccessData(string accessData) + { + try + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + _apiService.ShareKeys(accessData, storeScope); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.AmazonPay.ConfigSaveNote")); + NotifySuccess(T("Plugins.Payments.AmazonPay.SaveAccessDataSucceeded")); + } + catch (Exception exception) + { + NotifyError(exception.Message); + } - return Configure(); + return RedirectToConfiguration(AmazonPayPlugin.SystemName); + } + + [ValidateInput(false)] + public ActionResult ShareKey(string payload) + { + Response.AddHeader("Access-Control-Allow-Origin", "https://payments.amazon.com"); + Response.AddHeader("Access-Control-Allow-Methods", "GET, POST"); + Response.AddHeader("Access-Control-Allow-Headers", "Content-Type"); + + try + { + _apiService.ShareKeys(payload, 0); + } + catch (Exception exception) + { + Response.StatusCode = 400; + return Json(new { result = "error", message = exception.Message }); + } + + return Json(new { result = "success" }); } [HttpPost] [ValidateInput(false)] - [RequireHttpsByConfigAttribute(SslRequirement.Yes)] public ActionResult IPNHandler() { _apiService.ProcessIpn(Request); return Content("OK"); } - } + + // 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."); + } + + 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); + if (model != null) + { + return View(model); + } + + return new EmptyResult(); + } + + 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)) + { + NotifyError(T("Plugins.Payments.AmazonPay.AuthenticationNotActive")); + return new RedirectResult(Url.LogOn(returnUrl)); + } + + var result = _apiService.Authorize(returnUrl); + switch (result.AuthenticationStatus) + { + case OpenAuthenticationStatus.Error: + result.Errors.Each(x => NotifyError(x)); + return new RedirectResult(Url.LogOn(returnUrl)); + case OpenAuthenticationStatus.AssociateOnLogon: + return new RedirectResult(Url.LogOn(returnUrl)); + case OpenAuthenticationStatus.AutoRegisteredEmailValidation: + return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.EmailValidation, returnUrl }); + case OpenAuthenticationStatus.AutoRegisteredAdminApproval: + return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.AdminApproval, returnUrl }); + case OpenAuthenticationStatus.AutoRegisteredStandard: + return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.Standard, returnUrl }); + default: + if (result.Result != null) + return result.Result; + + if (HttpContext.Request.IsAuthenticated) + return RedirectToReferrer(returnUrl, "~/"); + + 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 ef5a8c90d9..2bebbbaf81 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayShoppingCartController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayShoppingCartController.cs @@ -3,19 +3,18 @@ namespace SmartStore.AmazonPay.Controllers { - public class AmazonPayShoppingCartController : AmazonPayControllerBase + public class AmazonPayShoppingCartController : AmazonPayControllerBase { - private readonly IAmazonPayService _apiService; + private readonly IAmazonPayService _apiService; public AmazonPayShoppingCartController(IAmazonPayService apiService) { _apiService = apiService; } - public ActionResult LoginHandler(string orderReferenceId) + public ActionResult PayButtonHandler() { - var model = _apiService.ProcessPluginRequest(AmazonPayRequestType.LoginHandler, TempData, orderReferenceId); - + var model = _apiService.CreateViewModel(AmazonPayRequestType.PayButtonHandler, TempData); return GetActionResult(model); } @@ -24,10 +23,11 @@ public ActionResult ShoppingCart() { if (ControllerContext.ParentActionViewContext.RequestContext.RouteData.IsRouteEqual("ShoppingCart", "Cart")) { - var model = _apiService.ProcessPluginRequest(AmazonPayRequestType.ShoppingCart, TempData); + var model = _apiService.CreateViewModel(AmazonPayRequestType.ShoppingCart, TempData); return GetActionResult(model); } + return new EmptyResult(); } @@ -36,7 +36,7 @@ public ActionResult OrderReviewData(bool renderAmazonPayView) { if (renderAmazonPayView) { - var model = _apiService.ProcessPluginRequest(AmazonPayRequestType.OrderReviewData, TempData); + var model = _apiService.CreateViewModel(AmazonPayRequestType.OrderReviewData, TempData); return View(model); } @@ -48,24 +48,11 @@ public ActionResult MiniShoppingCart(bool renderAmazonPayView) { if (renderAmazonPayView) { - var model = _apiService.ProcessPluginRequest(AmazonPayRequestType.MiniShoppingCart, TempData); + var model = _apiService.CreateViewModel(AmazonPayRequestType.MiniShoppingCart, TempData); return GetActionResult(model); } return new EmptyResult(); } - - [ChildActionOnly] - public ActionResult WidgetLibrary() - { - // not possible to load it asynchronously cause of document.write inside - string widgetUrl = _apiService.GetWidgetUrl(); - - if (widgetUrl.HasValue()) - { - return this.Content("".FormatWith(widgetUrl)); - } - return new EmptyResult(); - } - } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/DependencyRegistrar.cs b/src/Plugins/SmartStore.AmazonPay/DependencyRegistrar.cs index a8a2263ede..b163acf6e8 100644 --- a/src/Plugins/SmartStore.AmazonPay/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.AmazonPay/DependencyRegistrar.cs @@ -1,11 +1,9 @@ using Autofac; using Autofac.Integration.Mvc; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Infrastructure.DependencyManagement; -using SmartStore.Core.Plugins; -using SmartStore.AmazonPay.Api; using SmartStore.AmazonPay.Filters; using SmartStore.AmazonPay.Services; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Web.Controllers; namespace SmartStore.AmazonPay @@ -15,7 +13,6 @@ public class DependencyRegistrar : IDependencyRegistrar public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); if (isActiveModule) { diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 8237c4a7ad..7449e587bb 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -1,8 +1,8 @@ -FriendlyName: Pay with Amazon +FriendlyName: Login and Pay with Amazon SystemName: SmartStore.AmazonPay -Version: 2.6.0 Group: Payment -MinAppVersion: 2.5.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 78f10c09e2..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Linq; -using SmartStore.AmazonPay.Services; -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(MessageTokensAddedEvent messageTokenEvent) - { - if (!messageTokenEvent.Message.Name.IsCaseInsensitiveEqual("OrderPlaced.CustomerNotification")) - return; - - var storeId = _services.StoreContext.CurrentStore.Id; - - if (!_pluginFinder.IsPluginReady(_services.Settings, AmazonPayCore.SystemName, storeId)) - return; - - var order = _orderService.SearchOrders(storeId, _services.WorkContext.CurrentCustomer.Id, null, null, null, null, null, null, null, null, 0, 1).FirstOrDefault(); - - var isAmazonPayment = (order != null && order.PaymentMethodSystemName.IsCaseInsensitiveEqual(AmazonPayCore.SystemName)); - var tokenValue = (isAmazonPayment ? _services.Localization.GetResource("Plugins.Payments.AmazonPay.BillingAddressMessageNote") : ""); - - messageTokenEvent.Tokens.Add(new Token("SmartStore.AmazonPay.BillingAddressMessageNote", tokenValue)); - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Extensions/AmazonPayApiExtensions.cs b/src/Plugins/SmartStore.AmazonPay/Extensions/AmazonPayApiExtensions.cs deleted file mode 100644 index 8d1c2e6f08..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Extensions/AmazonPayApiExtensions.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using OffAmazonPaymentsService; -using OffAmazonPaymentsService.Model; -using SmartStore.AmazonPay.Extensions; -using SmartStore.AmazonPay.Services; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Directory; -using SmartStore.Services.Localization; - -namespace SmartStore.AmazonPay.Api -{ - public static class AmazonPayApiExtensions - { - public static bool GetErrorStrings(this OffAmazonPaymentsServiceException exception, out string shortMessage, out string fullMessage) - { - shortMessage = fullMessage = null; - - try - { - - if (exception.Message.HasValue()) - { - shortMessage = exception.Message; - var sb = new StringBuilder(); - - sb.AppendLine("Caught Exception: " + exception.Message); - sb.AppendLine("Response Status Code: " + exception.StatusCode); - sb.AppendLine("Error Code: " + exception.ErrorCode); - sb.AppendLine("Error Type: " + exception.ErrorType); - sb.AppendLine("Request ID: " + exception.RequestId); - sb.AppendLine("XML: " + exception.XML); - - if (exception.ResponseHeaderMetadata != null) - sb.AppendLine("ResponseHeaderMetadata: " + exception.ResponseHeaderMetadata.ToString()); - - fullMessage = sb.ToString(); - } - } - catch (Exception) { } - - return shortMessage.HasValue(); - } - - public static string ToFormatedAddress(this Address amazonAddress, ICountryService countryService, IStateProvinceService stateProvinceService) - { - var sb = new StringBuilder(); - - try - { - var city = (amazonAddress.IsSetCity() ? amazonAddress.City : null); - var zip = (amazonAddress.IsSetPostalCode() ? amazonAddress.PostalCode : null); - - sb.AppendLine(""); - - if (amazonAddress.Name.HasValue()) - sb.AppendLine(amazonAddress.Name); - - if (amazonAddress.AddressLine1.HasValue()) - sb.AppendLine(amazonAddress.AddressLine1); - - if (amazonAddress.AddressLine2.HasValue()) - sb.AppendLine(amazonAddress.AddressLine2); - - if (amazonAddress.AddressLine3.HasValue()) - sb.AppendLine(amazonAddress.AddressLine3); - - sb.AppendLine(zip.Grow(city, " ")); - - if (amazonAddress.IsSetStateOrRegion()) - { - var stateProvince = stateProvinceService.GetStateProvinceByAbbreviation(amazonAddress.StateOrRegion); - - if (stateProvince == null) - sb.AppendLine(amazonAddress.StateOrRegion); - else - sb.AppendLine("{0} {1}".FormatWith(amazonAddress.StateOrRegion, stateProvince.GetLocalized(x => x.Name))); - } - - if (amazonAddress.IsSetCountryCode()) - { - var country = countryService.GetCountryByTwoOrThreeLetterIsoCode(amazonAddress.CountryCode); - - if (country == null) - sb.AppendLine(amazonAddress.CountryCode); - else - sb.AppendLine("{0} {1}".FormatWith(amazonAddress.CountryCode, country.GetLocalized(x => x.Name))); - } - - if (amazonAddress.Phone.HasValue()) - { - sb.AppendLine(amazonAddress.Phone); - } - } - catch (Exception exc) - { - exc.Dump(); - } - - return sb.ToString(); - } - - public static void ToAddress(this Address amazonAddress, SmartStore.Core.Domain.Common.Address address, ICountryService countryService, - IStateProvinceService stateProvinceService, out bool countryAllowsShipping, out bool countryAllowsBilling) - { - countryAllowsShipping = countryAllowsBilling = true; - - if (amazonAddress.IsSetName()) - { - address.ToFirstAndLastName(amazonAddress.Name); - } - - if (amazonAddress.IsSetAddressLine1()) - { - address.Address1 = amazonAddress.AddressLine1.TrimSafe().Truncate(4000); - } - - if (amazonAddress.IsSetAddressLine2()) - { - address.Address2 = amazonAddress.AddressLine2.TrimSafe().Truncate(4000); - } - - if (amazonAddress.IsSetAddressLine3()) - { - address.Address2 = address.Address2.Grow(amazonAddress.AddressLine3.TrimSafe(), ", ").Truncate(4000); - } - - // normalize - if (address.Address1.IsEmpty() && address.Address2.HasValue()) - { - address.Address1 = address.Address2; - address.Address2 = null; - } - else if (address.Address1.HasValue() && address.Address1 == address.Address2) - { - address.Address2 = null; - } - - if (amazonAddress.IsSetCity()) - { - address.City = amazonAddress.City.TrimSafe().Truncate(4000); - } - - if (amazonAddress.IsSetPostalCode()) - { - address.ZipPostalCode = amazonAddress.PostalCode.TrimSafe().Truncate(4000); - } - - if (amazonAddress.IsSetPhone()) - { - address.PhoneNumber = amazonAddress.Phone.TrimSafe().Truncate(4000); - } - - if (amazonAddress.IsSetCountryCode()) - { - var country = countryService.GetCountryByTwoOrThreeLetterIsoCode(amazonAddress.CountryCode); - - if (country != null) - { - address.CountryId = country.Id; - countryAllowsShipping = country.AllowsShipping; - countryAllowsBilling = country.AllowsBilling; - } - } - - if (amazonAddress.IsSetStateOrRegion()) - { - var stateProvince = stateProvinceService.GetStateProvinceByAbbreviation(amazonAddress.StateOrRegion); - - if (stateProvince != null) - address.StateProvinceId = stateProvince.Id; - } - - //amazonAddress.District, amazonAddress.County ?? - - if (address.CountryId == 0) - address.CountryId = null; - - if (address.StateProvinceId == 0) - address.StateProvinceId = null; - } - - public static void ToAddress(this OrderReferenceDetails details, SmartStore.Core.Domain.Common.Address address, ICountryService countryService, - IStateProvinceService stateProvinceService, out bool countryAllowsShipping, out bool countryAllowsBilling) - { - countryAllowsShipping = countryAllowsBilling = true; - - if (details.IsSetBuyer() && details.Buyer.IsSetEmail()) - { - address.Email = details.Buyer.Email; - } - - if (details.IsSetDestination() && details.Destination.IsSetPhysicalDestination()) - { - details.Destination.PhysicalDestination.ToAddress(address, countryService, stateProvinceService, out countryAllowsShipping, out countryAllowsBilling); - } - } - - public static Order GetOrderByAmazonId(this IRepository orderRepository, string amazonId) - { - // S02-9777218-8608106 OrderReferenceId - // S02-9777218-8608106-A088344 Auth ID - // S02-9777218-8608106-C088344 Capture ID - - if (amazonId.HasValue()) - { - string amazonOrderReferenceId = amazonId.Substring(0, amazonId.LastIndexOf('-')); - if (amazonOrderReferenceId.HasValue()) - { - var orders = orderRepository.Table - .Where(x => x.PaymentMethodSystemName == AmazonPayCore.SystemName && x.AuthorizationTransactionId.StartsWith(amazonOrderReferenceId)) - .ToList(); - - if (orders.Count() == 1) - return orders.FirstOrDefault(); - } - } - return null; - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.AmazonPay/Extensions/MiscExtensions.cs deleted file mode 100644 index 13f63ef965..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Extensions/MiscExtensions.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Web; -using SmartStore.AmazonPay.Services; -using SmartStore.Core.Domain.Common; -using SmartStore.Services.Common; -using SmartStore.Services.Localization; - -namespace SmartStore.AmazonPay.Extensions -{ - public static class MiscExtensions - { - public static void ToFirstAndLastName(this string name, out string firstName, out string lastName) - { - if (!string.IsNullOrWhiteSpace(name)) - { - int index = name.LastIndexOf(' '); - if (index == -1) - { - firstName = ""; - lastName = name; - } - else - { - firstName = name.Substring(0, index); - lastName = name.Substring(index + 1); - } - - firstName = firstName.EmptyNull().Truncate(4000); - lastName = lastName.EmptyNull().Truncate(4000); - } - else - { - firstName = lastName = ""; - } - } - - public static void ToFirstAndLastName(this Address address, string name) - { - string firstName, lastName; - name.ToFirstAndLastName(out firstName, out lastName); - - address.FirstName = firstName; - address.LastName = lastName; - } - - public static bool HasAmazonPayState(this HttpContextBase httpContext) - { - var checkoutState = httpContext.GetCheckoutState(); - - if (checkoutState != null && checkoutState.CustomProperties.ContainsKey(AmazonPayCore.AmazonPayCheckoutStateKey)) - { - var state = checkoutState.CustomProperties[AmazonPayCore.AmazonPayCheckoutStateKey] as AmazonPayCheckoutState; - - return (state != null && state.OrderReferenceId.HasValue()); - } - return false; - } - - public static AmazonPayCheckoutState GetAmazonPayState(this HttpContextBase httpContext, ILocalizationService localizationService) - { - AmazonPayCheckoutState state = null; - var checkoutState = httpContext.GetCheckoutState(); - - if (checkoutState == null || (state = (AmazonPayCheckoutState)checkoutState.CustomProperties[AmazonPayCore.AmazonPayCheckoutStateKey]) == null) - throw new SmartException(localizationService.GetResource("Plugins.Payments.AmazonPay.MissingCheckoutSessionState")); - - return state; - } - - public static Address FindAddress(this List
addresses, Address address, bool uncompleteToo) - { - var match = addresses.FindAddress(address.FirstName, address.LastName, - address.PhoneNumber, address.Email, address.FaxNumber, address.Company, - address.Address1, address.Address2, - address.City, address.StateProvinceId, address.ZipPostalCode, address.CountryId); - - if (match == null && uncompleteToo) - { - // compare with AmazonPayApiExtensions.ToAddress - - match = addresses.FirstOrDefault(x => - x.FirstName == null && x.LastName == null && - x.Address1 == null && x.Address2 == null && - x.City == address.City && x.ZipPostalCode == address.ZipPostalCode && - x.PhoneNumber == null && - x.CountryId == address.CountryId && x.StateProvinceId == address.StateProvinceId - ); - } - - return match; - } - } -} diff --git a/src/Plugins/SmartStore.AmazonPay/Filters/AmazonPayCheckoutFilter.cs b/src/Plugins/SmartStore.AmazonPay/Filters/AmazonPayCheckoutFilter.cs index 9efdbb4fd7..ef444da26e 100644 --- a/src/Plugins/SmartStore.AmazonPay/Filters/AmazonPayCheckoutFilter.cs +++ b/src/Plugins/SmartStore.AmazonPay/Filters/AmazonPayCheckoutFilter.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using SmartStore.AmazonPay.Extensions; using SmartStore.AmazonPay.Services; namespace SmartStore.AmazonPay.Filters @@ -27,20 +26,24 @@ public void OnActionExecuting(ActionExecutingContext filterContext) if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) return; - if (!filterContext.HttpContext.HasAmazonPayState()) - return; - - string actionName = filterContext.ActionDescriptor.ActionName; + var actionName = filterContext.ActionDescriptor.ActionName; if (!IsInterceptableAction(actionName)) return; + if (actionName.IsCaseInsensitiveEqual("ShippingMethod") || actionName.IsCaseInsensitiveEqual("PaymentMethod")) + { + if (!filterContext.HttpContext.HasAmazonPayState()) + return; + } + if (actionName.IsCaseInsensitiveEqual("ShippingMethod")) { - var model = _apiService.Value.ProcessPluginRequest(AmazonPayRequestType.ShippingMethod, filterContext.Controller.TempData); + var model = _apiService.Value.CreateViewModel(AmazonPayRequestType.ShippingMethod, filterContext.Controller.TempData); - if (model.Result == AmazonPayResultType.Redirect) // shipping to selected address not possible + if (model.Result == AmazonPayResultType.Redirect) { + // Shipping to selected address not possible. var urlHelper = new UrlHelper(filterContext.HttpContext.Request.RequestContext); var url = urlHelper.Action("ShippingAddress", "Checkout", new { area = "" }); @@ -54,18 +57,18 @@ public void OnActionExecuted(ActionExecutedContext filterContext) if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) return; - if (!filterContext.HttpContext.HasAmazonPayState()) - return; + var actionName = filterContext.ActionDescriptor.ActionName; - string actionName = filterContext.ActionDescriptor.ActionName; + if (actionName.IsCaseInsensitiveEqual("ShippingMethod")) + return; - if (!IsInterceptableAction(actionName)) + if (!IsInterceptableAction(actionName)) return; - if (actionName.IsCaseInsensitiveEqual("ShippingMethod")) - return; + if (!filterContext.HttpContext.HasAmazonPayState()) + return; - var routeValues = new RouteValueDictionary(new { action = actionName, controller = "AmazonPayCheckout" }); + var routeValues = new RouteValueDictionary(new { action = actionName, controller = "AmazonPayCheckout" }); filterContext.Result = new RedirectToRouteResult("SmartStore.AmazonPay", routeValues); } diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml index fbe79d7119..baa01c2299 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml @@ -1,53 +1,64 @@  - Bezahlen mit Amazon + Amazon Pay + + + Amazon Pay - - Bezahlen mit Amazon - - Registrieren Sie sich zunächst bei Amazon Payments.

-

So richten Sie "Bezahlen mit Amazon" ein:

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

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

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

      ]]> +
    ]]> - - Bitte beachten Sie, dass es sich bei dieser Rechnungsadresse unter Umständen nicht um die für diese Bestellung gültige handelt! - + + Bitte beachten Sie, dass es sich bei dieser Rechnungsadresse unter Umständen nicht um die für diese Bestellung gültige handelt! + - Es wurde keine Auftrags-Referenz-ID durch Amazon übermittelt! + Es wurde keine Auftrags-Referenz-ID durch Amazon übermittelt. + + + Es wurde keine Access-Token durch Amazon übermittelt. + + + Unvollständige Amazon Profildaten! - Die Zahlungsart "Bezahlen mit Amazon" ist für Shop "{0}" nicht verfügbar. + Die Zahlungsart "Login und Bezahlen mit Amazon" ist für Shop "{0}" nicht verfügbar. - Fehlender Checkout-Sitzungsstatus für "Bezahlen mit Amazon". Ihre Zahlung kann leider nicht bearbeitet werden. Bitte gehen Sie zurück in den Warenkorb und Durchlaufen Sie den Checkout erneut. + Fehlender Checkout-Sitzungsstatus für "Login und Bezahlen mit Amazon". Ihre Zahlung kann leider nicht bearbeitet werden. Bitte gehen Sie zurück in den Warenkorb und Durchlaufen Sie den Checkout erneut. - Ein Auftrag mit der Zahlungsart "Bezahlen mit Amazon" und der Kennung {0} wurde nicht gefunden. + Ein Auftrag mit der Zahlungsart "Login und Bezahlen mit Amazon" und der Kennung '{0}' wurde nicht gefunden. + + Leider wurde die Zahlung zu Ihrer Bestellung in unserem Onlineshop von Amazon Pay zurückgewiesen. Bitte wählen Sie eine andere Zahlart. + - Leider wurde die Zahlung zu Ihrer Bestellung in unserem Onlineshop von Amazon Payments zurückgewiesen. Bitte kontaktieren Sie uns.

    ]]> + Leider wurde die Zahlung zu Ihrer Bestellung in unserem Onlineshop von Amazon Pay zurückgewiesen. Bitte kontaktieren Sie uns.

    ]]>
    - - oder - - - Amazon Payments meldet + + Beim Zahlungsvorgang ist ein Problem aufgetreten. Ihre Bestellung wurde nicht aufgegeben und Ihr Konto nicht belastet. + + + Mit dieser Zahlungsart ist ein Problem aufgetreten. Um Ihre Bestellung abzuschließen, wählen Sie bitte eine andere aus. + + + Amazon Pay meldet Ein Versand in das ausgewählte Land ist leider nicht möglich. Bitte wählen Sie eine andere Versandadresse. @@ -55,21 +66,69 @@ Textvorschläge:
      Die Einstellungen wurden erfolgreich gespeichert. Starten Sie die Anwendung bitte neu, falls "Aktualisierung des Zahlungsstatus" geändert wurde. - - Zugangsdaten;Datenaustausch;Gestaltung;Sonstiges + + Amazon Authentifizierung ist nicht aktiv! + + + Ihre Zahlung mit Amazon Pay ist derzeit noch in Prüfung. Bitte beachten Sie, dass wir uns mit Ihnen in Kürze per E-Mail in Verbindung setzen werden, falls noch Unklarheiten bestehen sollten. + + + Jetzt registrieren + + + + Jetzt registrieren, falls Sie noch über keine Zugangsdaten verfügen. Sie erhalten durch Amazon Pay einen Satz neuer Zugangsdaten, die Sie bitte im Dialog Zugangsdaten einfügen eintragen.]]> + + + + Zugangsdaten speichern + + + Zugangsdaten einfügen + + + Fügen Sie hier Ihre Zugangsdaten ein (merchant_id, access_key, secret_key, client_id etc.)... + + + Der Access-Token fehlt! + + + Amazon Datenabruf + + + Die Zugangsdaten wurden erfolgreich gespeichert. - - {0};Die Rechnungsadresse wurde nicht von Amazon übernommen, da sie entweder fehlt oder eine Rechnungslegung in dieses Land deaktiviert ist.{0}]]> + + Der Payload Parameter fehlt. + + + Eine Verschlüsselung von Zugangsdaten wird nicnt unterstützt. + + + Die Amazon Rechnungsanschrift fehlt. Bitte hinterlegen Sie eine Rechnungsanschrift bei Amazon oder wählen Sie eine andere Zahlart. + + + Daten von Amazon wurden verarbeitet + + + Zugangsdaten;Datenaustausch;Layout;Verschiedenes Mitteilungstyp;Mitteilungs ID;Autorisierungs ID;Buchungs ID;Rückerstatattungs ID;Referenz ID;Status;Statusaktualisierung;Gebühr;Autorisierungsbetrag;Buchungsbetrag;Erstattungsbetrag;Sofort buchen;Erstellt am;Verfällt am - + + Sandbox benutzen Legt fest, ob die Sandbox (Testumgebung) genutzt werden soll. + + Zahlungs-Button nur für Administratoren sichtbar + + + Legt fest, ob der Zahlungs-Button nur für Administratoren sichtbar ist. Dient dem Testen ohne Kunden die Zahlart bereits anbieten zu müssen. + Ihre Händlernummer @@ -88,53 +147,109 @@ Textvorschläge:
        Großbritannien + + USA + + + Japan + - Ihr Zugangsschlüssel + Ihre Access Key ID - Den Zugangsschlüssel finden Sie bei Amazon's Seller Central unter Integration - MWS Access Key. + Die Access Key ID finden Sie bei Amazon's Seller Central unter Integration - MWS Access Key. - Ihr geheimer Schlüssel + Ihr Secret Access Key - Den geheimen Schlüssel finden Sie bei Amazon's Seller Central unter Integration - MWS Access Key. + Den Secret Access Key finden Sie bei Amazon's Seller Central unter Integration - MWS Access Key. + + + Ihre Client-ID + + + Die Client-ID finden Sie bei Amazon's Seller Central unter Integration - MWS Access Key. + + + Zulässige JavaScript-Ursprünge - - Farbe des Amazon-Login-Button + + Bitte geben Sie diese Ursprünge bei Amazon Seller Central unter Login mit Amazon ein. Die Ursprünge müssen das HTTPS-Protokoll verwenden. - - Die bevorzugte Farbe des Amazon-Login-Button im Warenkorb. + + Zulässige Rückleitungs-URLs - + + Bitte geben Sie diese URLs bei Amazon Seller Central unter Login mit Amazon ein. Das Protokoll der Rückleitungs-URL muss HTTPS sein. + + + Farbe des Zahlungs-Button + + + Legt die bevorzugte Farbe des Zahlungs-Button (Pay-Button) fest. + + Orange - + Hellgrau - - Größe des Amazon-Login-Buttons + + Dunkelgrau + + + Größe des Zahlungs-Button - - Die Größe "Bezahlen mit Amazon" Buttons auf der Warenkorbseite. + + Legt die Größe Zahlungs-Button (Pay-Button) fest. - - Medium (126 x 24 Pixel) + + Klein - - Groß (151 x 27 Pixel) + + Medium - - Extra-Groß (173 x 27 Pixel) + + Groß + + + Extra-Groß + + + Art des Anmelde-Button + + + Legt die bevorzugte Art des Anmelde-Button (Loogin-Button) fest. + + + Login + + + Login mit Amazon + + + Farbe des Anmelde-Button + + + Legt die bevorzugte Farbe des Anmelde-Button (Login-Button) fest. + + + Größe des Anmelde-Button + + + Legt die Größe Anmelde-Button (Login-Button) fest. Aktualisierung des Zahlungsstatus - Legt die Methode fest mit deren Hilfe der Zahlungsstatus aktualisiert werden soll. + Legt die Methode fest, mit deren Hilfe der Zahlungsstatus aktualisiert werden soll. - IPN (Sofortbenachrichtigungen) erfordert, dass ein gültiges SSL-Zertifikat auf diesem Server installiert ist. Achten Sie darauf, das das SSL-Zertifikat von einer vertrauenswürdigen Zertifizierungsstelle ausgegeben werden muss, selbstsignierte Zertifikate sind nicht zulässig. + + + IPN (Instant Payment Notification) @@ -146,7 +261,7 @@ Textvorschläge:
          IPN URL - Bitte geben Sie diese URL bei Amazon Seller Central unter Integrationseinstellungen - Sofortbenachrichtigungs-Einstellungen - Händler-URL ein. + Bitte geben Sie diese URL bei Amazon Seller Central unter Integrationseinstellungen - Sofortbenachrichtigungs-Einstellungen - Händler-URL ein. Zahlungsaktion @@ -161,7 +276,24 @@ Textvorschläge:
            Autorisierung sofort, Abbuchung später - Bitte benutzen Sie "Sofort abbuchen" nur, wenn Sie Ware am selben Tag der Bestellung verschicken und Sie für diesen Dienst zugelassen sind. Aktivieren Sie diese Option bitte erst nach Rücksprache mit Amazon Payments. + + Sofort abbuchen nur, wenn Sie Ware am selben Tag der Bestellung verschicken und Sie für diesen Dienst zugelassen sind. Aktivieren Sie diese Option bitte erst nach Rücksprache mit Amazon Pay.]]> + + + + Autorisierungsmethode + + + Legt die Methode fest, mit der Zahlungen autorisiert werden sollen. + + + Standard (Omnichron) + + + Asynchron + + + Synchron Kundendaten übernehmen @@ -179,61 +311,16 @@ Textvorschläge:
              Button im Miniwarenkorb anzeigen - Legt fest, ob der "Bezahlen mit Amazon" Button auch im Miniwarenkorb angezeigt werden soll. - - - Breite des Adressen-Widgets - - - In Pixel. Gültige Werte für einspaltige Widgets sind 200 bis 399 und für zweispaltige Widgets 400 bis 600. - - - Höhe des Adressen-Widgets - - - In Pixel. Gültige Werte sind 228 bis 400. - - - Breite des Zahlungs-Widgets - - - In Pixel. Gültige Werte für einspaltige Widgets sind 200 bis 399 und für zweispaltige Widgets 400 bis 600. - - - Höhe des Zahlungs-Widgets - - - In Pixel. Gültige Werte sind 228 bis 400. - - - Zusätzliche Gebühren - - - Zusätzliche Gebühren, die dem Kunden für die Inanspruchnahme des Dienstes berechnet werden sollen. - - - Bitte berechnen Sie zusätzliche Gebühren erst nach Rücksprache mit Amazon Payments. Hierfür ist eine ausdrückliche Genehmigung erforderlich. - - - Zusätzliche Gebühren (prozentual) - - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. + Legt fest, ob der Amazon Pay Button auch im Miniwarenkorb angezeigt werden soll. Auftragsnotizen anlegen - Legt fest, dass Auftragsnotizen hinsichtlich des Datenaustausches mit Amazon Payment angelegt werden sollen. - - - Zeitspanne (in Minuten) - - - Legt fest, wie oft Zahlungsdaten vom Amazon Payments Server abgerufen werden sollen. + Legt fest, dass Auftragsnotizen hinsichtlich des Datenaustausches mit Amazon Pay angelegt werden sollen. - Maximales Auftragsalter (in Tagen) + Maximales Auftragsalter Legt fest, dass nur Aufträge, die nicht älter als x Tage sind, in die Aktualisierung der Zahlungsdaten einbezogen werden sollen. @@ -242,12 +329,12 @@ Textvorschläge:
                Über Ablehnung einer Autorisierung informieren - Legt fest, dass Auftragsnotizen im Fall einer Ablehnung einer Autorisierung durch Amazon angelegt werden, die auch für den Kunden einsehbar sind. Zusätzlich wir der Kunde per Email über den Sachverhalt informiert. + Legt fest, dass Auftragsnotizen im Fall einer Ablehnung einer Autorisierung durch Amazon angelegt werden, die auch für den Kunden einsehbar sind. Zusätzlich wird der Kunde per E-Mail über den Sachverhalt informiert. Fehlermeldung anhängen - Legt fest, ob der genaue Wortlaut der Fehlermeldung der Auftragsnotiz bzw. Email angehängt werden soll. + Legt fest, ob der genaue Wortlaut der Fehlermeldung der Auftragsnotiz bzw. E-Mail angehängt werden soll. \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml index d5163d3ef3..caaa7ebf43 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml @@ -1,53 +1,64 @@  - Pay with Amazon + Amazon Pay + + + Amazon Pay - - Pay with Amazon - - Register now at Amazon Payments.

                -

                How to set up "Pay with Amazon":

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

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

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

                    ]]> +
                  ]]> - - Please note that this billing address is possibly not the valid billing address for this order! - + + Please note that this billing address is possibly not the valid billing address for this order! + - There was no order reference ID transmitted by Amazon! + There was no order reference ID transmitted by Amazon. + + + There was no access token transmitted by Amazon. + + + Incomplete Amazon profile data! - Payment method "Pay with Amazon" is not available for store "{0}". + Payment method "Login and Pay with Amazon" is not available for store "{0}". - Missing checkout session state for "Pay with Amazon". Your payment cannot be processed. Please go to your shopping cart and checkout again. + Missing checkout session state for "Login and Pay with Amazon". Your payment cannot be processed. Please go to your shopping cart and checkout again. - Cannot find an order with the payment method "Pay with Amazon" and the identification {0}. + Cannot find an order with the payment method "Login and Pay with Amazon" and the identification '{0}'. + + + Unfortunately Amazon Pay declined the payment for your order in our online shop. Please choose another payment method. - Unfortunately Amazon Payments declined the payment for your order in our online shop. Please contact us.

                  ]]> + Unfortunately Amazon Pay declined the payment for your order in our online shop. Please contact us.

                  ]]>
                  - - or - - - Amazon Payments reports + + There was a problem with your payment. Your order hasn't been placed, and you haven't been charged. + + + Something's wrong with your payment method. To place your order, try another. + + + Amazon Pay reports Shipping to the selected country is not allowed. Please choose an other shipping address. @@ -55,21 +66,69 @@ Text suggestions:
                    The settings were successfully saved. Please restart the application if "Updating the payment status" has been changed. + + Amazon authentication is not active! + + + Your transaction with Amazon Pay is currently being validated. Please be aware that we will inform you shortly as needed. + + + Register now + + + + Register now if you don't have an account yet. Amazon Pay will provide you with a set of new access data, which you can enter in the Paste access data dialog.]]> + + + + Save access data + + + Paste access data + + + Paste here your access data (merchant_id, access_key, secret_key, client_id etc.)... + + + Missing access token! + + + Amazon data polling + + + The access data has been saved successfully. + + + The payload parameter is missing. + + + Encryption of access data is not supported. + + + Missing Amazon billing address of the customer. Please enter a billing address at Amazon or choose another payment method. + + + Data from Amazon has been processed + Access data;Data exchange;Layout;Miscellaneous - - {0}The billing address from Amazon was not applied because it is missing or billing to that country is deactivated.{0};]]> - Message type;Message ID;Authorization ID;Capture ID;Refund ID;Reference ID;State;State update;Fee;Authorized amount;Captured amount;Refunded amount;Capture now;Creation;Expiration - + + Use Sandbox Check the box to use the sandbox (testing environment). + + Show pay button for administrators only + + + Specifies whether the pay button is visible to administrators only. Intended for testing without having to offer the payment method to customers. + Your Merchant ID @@ -88,44 +147,98 @@ Text suggestions:
                      United Kingdom of Great Britain + + USA + + + Japan + - Your access key + Your Access Key ID - You can find the access key at Amazon's Seller Central under Integration - MWS Access Key. + You can find the Access Key ID at Amazon's Seller Central under Integration - MWS Access Key. - Your secret key + Your Secret Access Key - You can find the secret key at Amazon's Seller Central under Integration - MWS Access Key. + You can find the Secret Access Key at Amazon's Seller Central under Integration - MWS Access Key. + + + Your Client ID + + + You can find the Client ID at Amazon's Seller Central under Integration - MWS Access Key. + + + Allowed JavaScript origins - - Amazon login button color + + Please enter these origins at Amazon Seller Central under Login with Amazon. The origins must use the HTTPS protocol. - - The prefered color of the Amazon login button on shopping cart page. + + Allowed redirect URLs - + + Please enter these URLs at Amazon Seller Central under Login with Amazon. The redirect URL protocol must be HTTPS. + + + Pay button color + + + Specifies the prefered color of the Pay button. + + Orange - + Light gray - - Size of Amazon login button + + Dark gray + + + Pay button size - - The size of the "Pay with Amazon" button on the shopping cart page. + + Specifies the size of the Pay button. - - Medium (126 x 24 pixel) + + Small - - Large (151 x 27 pixel) + + Medium - - Extra large (173 x 27 ixel) + + Large + + + Extra large + + + Login button type + + + Specifies the prefered type of the Login button. + + + Login + + + Login with Amazon + + + Login button color + + + Specifies the prefered color of the Login button. + + + Login button size + + + Specifies the size of the Login button. Updating the payment status @@ -134,7 +247,9 @@ Text suggestions:
                        Specifies the method used to update the payment status. - IPN (instant payment notification) requires valid SSL certificate to be installed on this server. Pay attention that the SSL certificate must be issued by a trusted Certificate Authority, self-signed certificates are not permittted. + + + IPN (Instant Payment Notification) @@ -147,7 +262,7 @@ Text suggestions:
                          Please enter this URL at Amazon Seller Central under Integration Settings - Instant Notification Settings - Merchant URL. - + Payment action @@ -161,7 +276,24 @@ Text suggestions:
                            Authorize immediately, debit later - Please use "Immediately debit" method only in the case you are shipping goods on the same day they are ordered and you have been white-listed for this service. Do not activate this option without contacting Amazon Payments first. + + Immediately debit method only in the case you are shipping goods on the same day they are ordered and you have been white-listed for this service. Do not activate this option without contacting Amazon Pay first.]]> + + + + Authorize method + + + Specifies the method by which payments are to be authorized. + + + Standard (Omnichronous) + + + Asynchronous + + + Synchronous Apply customer data @@ -179,61 +311,16 @@ Text suggestions:
                              Show button in mini shopping cart - Specifies to show the "Pay with Amazon" button in the mini shopping cart too. - - - Width of address widget - - - Valid values for one-column widgets are 200 to 399 pixel and for two-column widgets 400 to 600 pixel. - - - Height of address widget - - - Valid values are 228 to 400 pixel. - - - Width of payment widget - - - Valid values for one-column widgets are 200 to 399 pixel and for two-column widgets 400 to 600 pixel. - - - Height of payment widget - - - Valid values are 228 to 400 pixel. - - - Additional fee - - - Enter additional fee to charge your customers. - - - This option requires a permission through Amazon Payments. Please do not calculate additional fees without contacting Amazon Payments first. - - - Additional fee percentage - - - Specifies whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. + Specifies to show the Amazon Pay button in the mini shopping cart too. Create order notes - Specifies that order notes should be created in context of the data exchange with Amazon Payments. - - - Frequency (in minutes) - - - Specifies how often status of the different object shall be polled from Amazon Payments servers. + Specifies that order notes should be created in context of the data exchange with Amazon Pay. - Maximal order age (in days) + Maximal order age Specifies that only orders which are not older than x days to be included in payment data updates. @@ -242,7 +329,7 @@ Text suggestions:
                                Inform about a refusal of an authorization - Specifies to create order notes in case of a declination of an Amazon payment, which are visible for customers too. In addition the customer is informed by email about the case. + Specifies to create order notes in case of a declination of an Amazon Pay, which are visible for customers too. In addition the customer is informed by email about the case. Append error message diff --git a/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs b/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs index 066e9dd9db..77c391b448 100644 --- a/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs +++ b/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs @@ -1,6 +1,6 @@ -using System; -using SmartStore.AmazonPay.Services; +using SmartStore.AmazonPay.Services; using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Models.Common; namespace SmartStore.AmazonPay.Models { @@ -12,48 +12,45 @@ public AmazonPayViewModel() RedirectAction = "Cart"; RedirectController = "ShoppingCart"; Result = AmazonPayResultType.PluginView; - WidgetUrl = AmazonPayCore.UrlWidgetProduction.FormatWith("de"); + BillingAddress = new AddressModel(); } - public string SystemName { get { return AmazonPayCore.SystemName; } } + public string SystemName => AmazonPayPlugin.SystemName; public string SellerId { get; set; } public string ClientId { get; set; } + /// + /// Amazon widget script URL + /// public string WidgetUrl { get; set; } - public string ButtonUrl { get; set; } - public string LoginHandlerUrl { get; set; } + public string ButtonHandlerUrl { get; set; } public bool IsShippable { get; set; } public bool IsRecurring { get; set; } + public string LanguageCode { get; set; } public AmazonPayRequestType Type { get; set; } public AmazonPayResultType Result { get; set; } + public string RedirectAction { get; set; } public string RedirectController { get; set; } public string OrderReferenceId { get; set; } + public string AddressConsentToken { get; set; } public string Warning { get; set; } + public bool Logout { get; set; } - public int AddressWidgetWidth { get; set; } - public int AddressWidgetHeight { get; set; } - - public int PaymentWidgetWidth { get; set; } - public int PaymentWidgetHeight { get; set; } - - public bool DisplayRewardPoints { get; set; } - public int RewardPointsBalance { get; set; } - public string RewardPointsAmount { get; set; } - public bool UseRewardPoints { get; set; } + public string ButtonType { get; set; } + public string ButtonColor { get; set; } + public string ButtonSize { get; set; } public string ShippingMethod { get; set; } + public AddressModel BillingAddress { get; set; } - public string GetWidgetId - { - get - { - return "AmazonPay" + Type.ToString(); - } - } + // Confirmation flow. + public bool IsConfirmed { get; set; } + public string FormData { get; set; } + public bool SubmitForm { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs index aa40e32f0f..cf304df151 100644 --- a/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.AmazonPay.Services; -using SmartStore.AmazonPay.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -10,6 +10,7 @@ namespace SmartStore.AmazonPay.Models public class ConfigurationModel : ModelBase { public string[] ConfigGroups { get; set; } + public string PrimaryStoreCurrencyCode { get; set; } [SmartResourceDisplayName("Plugins.Payments.AmazonPay.UseSandbox")] public bool UseSandbox { get; set; } @@ -21,17 +22,15 @@ public class ConfigurationModel : ModelBase public string AccessKey { get; set; } [SmartResourceDisplayName("Plugins.Payments.AmazonPay.SecretKey")] + [DataType(DataType.Password)] public string SecretKey { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.ClientId")] + public string ClientId { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.Marketplace")] public string Marketplace { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AmazonButtonColor")] - public string AmazonButtonColor { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AmazonButtonSize")] - public string AmazonButtonSize { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.DataFetching")] public AmazonPayDataFetchingType DataFetching { get; set; } public List DataFetchings { get; set; } @@ -39,9 +38,6 @@ public class ConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.AmazonPay.IpnUrl")] public string IpnUrl { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.PollingTaskMinutes")] - public int PollingTaskMinutes { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.PollingMaxOrderCreationDays")] public int PollingMaxOrderCreationDays { get; set; } @@ -49,27 +45,24 @@ public class ConfigurationModel : ModelBase public AmazonPayTransactionType TransactionType { get; set; } public List TransactionTypes { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AuthorizeMethod")] + public AmazonPayAuthorizeMethod AuthorizeMethod { get; set; } + public SelectList AuthorizeMethods { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.SaveEmailAndPhone")] public AmazonPaySaveDataType? SaveEmailAndPhone { get; set; } public List SaveEmailAndPhones { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.ShowPayButtonForAdminOnly")] + public bool ShowPayButtonForAdminOnly { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.ShowButtonInMiniShoppingCart")] public bool ShowButtonInMiniShoppingCart { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AddressWidgetWidth")] - public int AddressWidgetWidth { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AddressWidgetHeight")] - public int AddressWidgetHeight { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.PaymentWidgetWidth")] - public int PaymentWidgetWidth { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.PaymentWidgetHeight")] - public int PaymentWidgetHeight { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AdditionalFee")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AdditionalFeePercentage")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AddOrderNotes")] @@ -81,56 +74,55 @@ public class ConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.AmazonPay.InformCustomerAddErrors")] public bool InformCustomerAddErrors { get; set; } - public void Copy(AmazonPaySettings settings, bool fromSettings) - { - if (fromSettings) - { - UseSandbox = settings.UseSandbox; - SellerId = settings.SellerId; - AccessKey = settings.AccessKey; - SecretKey = settings.SecretKey; - Marketplace = settings.Marketplace; - DataFetching = settings.DataFetching; - PollingMaxOrderCreationDays = settings.PollingMaxOrderCreationDays; - TransactionType = settings.TransactionType; - SaveEmailAndPhone = settings.SaveEmailAndPhone; - ShowButtonInMiniShoppingCart = settings.ShowButtonInMiniShoppingCart; - AmazonButtonColor = settings.AmazonButtonColor; - AmazonButtonSize = settings.AmazonButtonSize; - AddressWidgetWidth = settings.AddressWidgetWidth; - AddressWidgetHeight = settings.AddressWidgetHeight; - PaymentWidgetWidth = settings.PaymentWidgetWidth; - PaymentWidgetHeight = settings.PaymentWidgetHeight; - AdditionalFee = settings.AdditionalFee; - AdditionalFeePercentage = settings.AdditionalFeePercentage; - AddOrderNotes = settings.AddOrderNotes; - InformCustomerAboutErrors = settings.InformCustomerAboutErrors; - InformCustomerAddErrors = settings.InformCustomerAddErrors; - } - else - { - settings.UseSandbox = UseSandbox; - settings.SellerId = SellerId; - settings.AccessKey = AccessKey; - settings.SecretKey = SecretKey; - settings.Marketplace = Marketplace; - settings.DataFetching = DataFetching; - settings.PollingMaxOrderCreationDays = PollingMaxOrderCreationDays; - settings.TransactionType = TransactionType; - settings.SaveEmailAndPhone = SaveEmailAndPhone; - settings.ShowButtonInMiniShoppingCart = ShowButtonInMiniShoppingCart; - settings.AmazonButtonColor = AmazonButtonColor; - settings.AmazonButtonSize = AmazonButtonSize; - settings.AddressWidgetWidth = AddressWidgetWidth; - settings.AddressWidgetHeight = AddressWidgetHeight; - settings.PaymentWidgetWidth = PaymentWidgetWidth; - settings.PaymentWidgetHeight = PaymentWidgetHeight; - settings.AdditionalFee = AdditionalFee; - settings.AdditionalFeePercentage = AdditionalFeePercentage; - settings.AddOrderNotes = AddOrderNotes; - settings.InformCustomerAboutErrors = InformCustomerAboutErrors; - settings.InformCustomerAddErrors = InformCustomerAddErrors; - } - } + + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.PayButtonColor")] + public string PayButtonColor { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.PayButtonSize")] + public string PayButtonSize { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AuthButtonType")] + public string AuthButtonType { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AuthButtonColor")] + public string AuthButtonColor { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AuthButtonSize")] + public string AuthButtonSize { get; set; } + + #region Registration data + + public string RegisterUrl { get; set; } + public string SoftwareVersion { get; set; } + public string PluginVersion { get; set; } + public string LeadCode { get; set; } + public string PlatformId { get; set; } + public string PublicKey { get; set; } + public string KeyShareUrl { get; set; } + public string LanguageLocale { get; set; } + + /// + /// Including all domains and sub domains where the login button appears. SSL mandatory. + /// + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.MerchantLoginDomains")] + public HashSet MerchantLoginDomains { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.MerchantLoginDomains")] + public HashSet CurrentMerchantLoginDomains { get; set; } + + /// + /// Used to populate Allowed Return URLs on the Login with Amazon application. SSL mandatory. Max 512 characters. + /// + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.MerchantLoginRedirectUrls")] + public HashSet MerchantLoginRedirectUrls { get; set; } + [SmartResourceDisplayName("Plugins.Payments.AmazonPay.MerchantLoginRedirectUrls")] + public HashSet CurrentMerchantLoginRedirectUrls { get; set; } + + public string MerchantStoreDescription { get; set; } + public string MerchantPrivacyNoticeUrl { get; set; } + public string MerchantCountry { get; set; } + public string MerchantSandboxIpnUrl { get; set; } + public string MerchantProductionIpnUrl { get; set; } + + #endregion } } diff --git a/src/Plugins/SmartStore.AmazonPay/Plugin.cs b/src/Plugins/SmartStore.AmazonPay/Plugin.cs index 61a133c505..9524984a8d 100644 --- a/src/Plugins/SmartStore.AmazonPay/Plugin.cs +++ b/src/Plugins/SmartStore.AmazonPay/Plugin.cs @@ -3,60 +3,73 @@ using System.Web.Routing; using SmartStore.AmazonPay.Controllers; using SmartStore.AmazonPay.Services; -using SmartStore.AmazonPay.Settings; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services; +using SmartStore.Services.Authentication.External; +using SmartStore.Services.Customers; using SmartStore.Services.Orders; using SmartStore.Services.Payments; +using SmartStore.Services.Tasks; namespace SmartStore.AmazonPay { [DependentWidgets("Widgets.AmazonPay")] - public class Plugin : PaymentPluginBase, IConfigurable + [FriendlyName("Amazon Pay")] + [DisplayOrder(-1)] + public class AmazonPayPlugin : PaymentPluginBase, IExternalAuthenticationMethod, IConfigurable { private readonly IAmazonPayService _apiService; - private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly ICommonServices _services; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IScheduleTaskService _scheduleTaskService; - public Plugin( + public AmazonPayPlugin( IAmazonPayService apiService, + ICommonServices services, IOrderTotalCalculationService orderTotalCalculationService, - ICommonServices services) + IScheduleTaskService scheduleTaskService) { _apiService = apiService; - _orderTotalCalculationService = orderTotalCalculationService; _services = services; + _orderTotalCalculationService = orderTotalCalculationService; + _scheduleTaskService = scheduleTaskService; + + Logger = NullLogger.Instance; } - public override void Install() + public ILogger Logger { get; set; } + + public static string SystemName { - _services.Settings.SaveSetting(new AmazonPaySettings()); + get { return "SmartStore.AmazonPay"; } + } - _services.Localization.ImportPluginResourcesFromXml(this.PluginDescriptor); + public override void Install() + { + _services.Settings.SaveSetting(new AmazonPaySettings()); + _services.Localization.ImportPluginResourcesFromXml(PluginDescriptor); - _apiService.DataPollingTaskInit(); + // Polling task every 30 minutes. + _scheduleTaskService.GetOrAddTask(x => + { + x.Name = _services.Localization.GetResource("Plugins.Payments.AmazonPay.TaskName"); + x.CronExpression = "*/30 * * * *"; + }); base.Install(); } public override void Uninstall() { - _apiService.DataPollingTaskDelete(); - + _scheduleTaskService.TryDeleteTask(); _services.Settings.DeleteSetting(); - - _services.Localization.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); + _services.Localization.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); base.Uninstall(); } - public override PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest processPaymentRequest) - { - var result = _apiService.PreProcessPayment(processPaymentRequest); - return result; - } - public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) { var result = _apiService.ProcessPayment(processPaymentRequest); @@ -71,18 +84,19 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay public override decimal GetAdditionalHandlingFee(IList cart) { var result = decimal.Zero; + try { var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); result = this.CalculateAdditionalFee(_orderTotalCalculationService, cart, settings.AdditionalFee, settings.AdditionalFeePercentage); } - catch (Exception exc) + catch (Exception exception) { - _apiService.LogError(exc); + Logger.Error(exception); } - return result; + return result; } public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) @@ -108,14 +122,36 @@ public override void GetConfigurationRoute(out string actionName, out string con { actionName = "Configure"; controllerName = "AmazonPay"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.AmazonPay.Controllers" }, { "area", AmazonPayCore.SystemName } }; + routeValues = new RouteValueDictionary { { "Namespaces", "SmartStore.AmazonPay.Controllers" }, { "area", SystemName } }; + } + + public void GetPublicInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "AuthenticationPublicInfo"; + controllerName = "AmazonPay"; + routeValues = new RouteValueDictionary { { "Namespaces", "SmartStore.AmazonPay.Controllers" }, { "area", SystemName } }; } public override void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { + try + { + var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); + if (settings.ShowPayButtonForAdminOnly && !_services.WorkContext.CurrentCustomer.IsAdmin()) + { + actionName = controllerName = null; + routeValues = null; + return; + } + } + catch (Exception exception) + { + Logger.Error(exception); + } + actionName = "ShoppingCart"; controllerName = "AmazonPayShoppingCart"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.AmazonPay.Controllers" }, { "area", AmazonPayCore.SystemName } }; + routeValues = new RouteValueDictionary { { "Namespaces", "SmartStore.AmazonPay.Controllers" }, { "area", SystemName } }; } public override Type GetControllerType() diff --git a/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs b/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs index a44b2d50f3..d7d33434cb 100644 --- a/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs +++ b/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs @@ -14,7 +14,7 @@ public void RegisterRoutes(RouteCollection routes) new { controller = "AmazonPay" }, new[] { "SmartStore.AmazonPay.Controllers" } ) - .DataTokens["area"] = AmazonPayCore.SystemName; + .DataTokens["area"] = AmazonPayPlugin.SystemName; // for backward compatibility (IPN!) routes.MapRoute("SmartStore.AmazonPay.Legacy", @@ -22,7 +22,7 @@ public void RegisterRoutes(RouteCollection routes) new { controller = "AmazonPay" }, new[] { "SmartStore.AmazonPay.Controllers" } ) - .DataTokens["area"] = AmazonPayCore.SystemName; + .DataTokens["area"] = AmazonPayPlugin.SystemName; } public int Priority { get { return 0; } } diff --git a/src/Plugins/SmartStore.AmazonPay/Scripts/jquery.deserialize.js b/src/Plugins/SmartStore.AmazonPay/Scripts/jquery.deserialize.js new file mode 100644 index 0000000000..13c00e2a0d --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Scripts/jquery.deserialize.js @@ -0,0 +1,276 @@ +/** + * Copyright (C) 2017 Kyle Florence + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @website https://github.com/kflorence/jquery-deserialize/ + * @version 2.0.0-rc1 + * + * Dual licensed under the MIT and GPLv2 licenses. + */ +(function( factory ) { + if ( typeof module === "object" && module.exports ) { + // Node/CommonJS + module.exports = factory( require( "jquery" ) ); + + } else { + // Browser globals + factory( window.jQuery ); + } +}(function( $ ) { + + /** + * Updates a key/valueArray with the given property and value. Values will always be stored as arrays. + * + * @param prop The property to add the value to. + * @param value The value to add. + * @param obj The object to update. + * @returns {object} Updated object. + */ + function updateKeyValueArray( prop, value, obj ) { + var current = obj[ prop ]; + + if ( current === undefined ) { + obj[ prop ] = [ value ]; + + } else { + current.push( value ); + } + + return obj; + } + + /** + * Get all of the fields contained within the given elements by name. + * + * @param $elements jQuery object of elements. + * @param filter Custom filter to apply to the list of fields. + * @returns {object} All of the fields contained within the given elements, keyed by name. + */ + function getFieldsByName( $elements, filter ) { + var elementsByName = {}; + + // Extract fields from elements + var fields = $elements + .map(function convertFormToElements() { + return this.elements ? $.makeArray( this.elements ) : this; + }) + .filter( filter || ":input:not(:disabled)" ) + .get(); + + $.each( fields, function( index, field ) { + updateKeyValueArray( field.name, field, elementsByName ); + }); + + return elementsByName; + } + + /** + * Figure out the type of an element. Input type will be used first, falling back to nodeName. + * + * @param element DOM element to check type of. + * @returns {string} The element's type. + */ + function getElementType( element ) { + return ( element.type || element.nodeName ).toLowerCase(); + } + + /** + * Normalize the provided data into a key/valueArray store. + * + * @param data The data provided by the user to the plugin. + * @returns {object} The data normalized into a key/valueArray store. + */ + function normalizeData( data ) { + var normalized = {}; + var rPlus = /\+/g; + + // Convert data from .serializeObject() notation + if ( $.isPlainObject( data ) ) { + $.extend( normalized, data ); + + // Convert non-array values into an array + $.each( normalized, function( name, value ) { + if ( !$.isArray( value ) ) { + normalized[ name ] = [ value ]; + } + }); + + // Convert data from .serializeArray() notation + } else if ( $.isArray( data ) ) { + $.each( data, function( index, field ) { + updateKeyValueArray( field.name, field.value, normalized ); + }); + + // Convert data from .serialize() notation + } else if ( typeof data === "string" ) { + $.each( data.split( "&" ), function( index, field ) { + var current = field.split( "=" ); + var name = decodeURIComponent( current[ 0 ].replace( rPlus, "%20" ) ); + var value = decodeURIComponent( current[ 1 ].replace( rPlus, "%20" ) ); + updateKeyValueArray( name, value, normalized ); + }); + } + + return normalized; + } + + /** + * Map of property name -> element types. + * + * @type {object} + */ + var updateTypes = { + checked: [ + "radio", + "checkbox" + ], + selected: [ + "option", + "select-one", + "select-multiple" + ], + value: [ + "button", + "color", + "date", + "datetime", + "datetime-local", + "email", + "hidden", + "month", + "number", + "password", + "range", + "reset", + "search", + "submit", + "tel", + "text", + "textarea", + "time", + "url", + "week" + ] + }; + + /** + * Get the property to update on an element being updated. + * + * @param element The DOM element to get the property for. + * @returns The name of the property to update if element is supported, otherwise `undefined`. + */ + function getPropertyToUpdate( element ) { + var type = getElementType( element ); + var elementProperty = undefined; + + $.each( updateTypes, function( property, types ) { + if ( $.inArray( type, types ) > -1 ) { + elementProperty = property; + return false; + } + }); + + return elementProperty; + } + + /** + * Update the element based on the provided data. + * + * @param element The DOM element to update. + * @param elementIndex The index of this element in the list of elements with the same name. + * @param value The serialized element value. + * @param valueIndex The index of the value in the list of values for elements with the same name. + * @param callback A function to call if the value of an element was updated. + */ + function update( element, elementIndex, value, valueIndex, callback ) { + var property = getPropertyToUpdate( element ); + + // Handle value inputs + // If there are multiple value inputs with the same name, they will be populated by matching indexes. + if ( property == "value" && elementIndex == valueIndex ) { + element.value = value; + callback.call( element, value ); + + // Handle select menus, checkboxes and radio buttons + } else if ( property == "checked" || property == "selected" ) { + var fields = []; + + // Extract option fields from select menus + if ( element.options ) { + $.each( element.options, function( index, option ) { + fields.push( option ); + }); + + } else { + fields.push( element ); + } + + // #37: Remove selection from multiple select menus before deserialization + if ( element.multiple && valueIndex == 0 ) { + element.selectedIndex = -1; + } + + $.each( fields, function( index, field ) { + if ( field.value == value ) { + field[ property ] = true; + callback.call( field, value ); + } + }); + } + } + + /** + * Default plugin options. + * + * @type {object} + */ + var defaultOptions = { + change: $.noop, + complete: $.noop + }; + + /** + * The $.deserialize function. + * + * @param data The data to deserialize. + * @param options Additional options. + * @returns {jQuery} The jQuery object that was provided to the plugin. + */ + $.fn.deserialize = function( data, options ) { + + // Backwards compatible with old arguments: data, callback + if ( $.isFunction( options ) ) { + options = { complete: options }; + } + + options = $.extend( defaultOptions, options || {} ); + data = normalizeData( data ); + + var elementsByName = getFieldsByName( this, options.filter ); + + $.each( data, function( name, values ) { + $.each( elementsByName[ name ], function( elementIndex, element ) { + $.each( values, function( valueIndex, value ) { + update( element, elementIndex, value, valueIndex, options.change ); + }); + }); + }); + + options.complete.call( this ); + + return this; + }; +})); diff --git a/src/Plugins/SmartStore.AmazonPay/Scripts/smartstore.amazonpay.js b/src/Plugins/SmartStore.AmazonPay/Scripts/smartstore.amazonpay.js deleted file mode 100644 index a6859e9443..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Scripts/smartstore.amazonpay.js +++ /dev/null @@ -1,33 +0,0 @@ - -(function (amazonpay, $, undefined) { - - amazonpay.init = function () { - - // show/hide transaction type warning - $('#TransactionType').change(function () { - $('#TransactionTypeWarning').toggle($(this).val() === '2'); - }).trigger('change'); - - // show/hide status fetching warning - $('#DataFetching').change(function () { - var val = $(this).val(), - configTable = $('#AmazonPayConfigTable'); - - $('#DataFetchingWarning').toggle(val === '1'); - - configTable.find('.data-fetching').hide(); - if (val === '1') - configTable.find('.data-fetching-ipn').show(); - else if (val === '2') - configTable.find('.data-fetching-polling').show(); - - }).trigger('change'); - - // show/hide inform customer add errors option - $('#InformCustomerAboutErrors').change(function () { - $('#InformCustomerAddErrorsContainer').toggle($(this).is(':checked')); - }).trigger('change'); - - }; - -}(window.amazonpay = window.amazonpay || {}, jQuery)); \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonAuthenticationParameters.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonAuthenticationParameters.cs new file mode 100644 index 0000000000..d64719b0e7 --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonAuthenticationParameters.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using SmartStore.Services.Authentication.External; + +namespace SmartStore.AmazonPay.Services +{ + [Serializable] + public class AmazonAuthenticationParameters : OpenAuthenticationParameters + { + private IList _claims; + + public override string ProviderSystemName + { + get { return AmazonPayPlugin.SystemName; } + } + + public override IList UserClaims + { + get { return _claims; } + } + + public void AddClaim(UserClaims claim) + { + if (_claims == null) + { + _claims = new List(); + } + + _claims.Add(claim); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs deleted file mode 100644 index 59e41a1e2b..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs +++ /dev/null @@ -1,815 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Web; -using OffAmazonPaymentsService; -using OffAmazonPaymentsService.Model; -using SmartStore.AmazonPay.Extensions; -using SmartStore.AmazonPay.Services; -using SmartStore.AmazonPay.Settings; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Plugins; -using SmartStore.Services; -using SmartStore.Services.Common; -using SmartStore.Services.Directory; -using SmartStore.Services.Helpers; -using SmartStore.Services.Orders; -using SmartStore.Services.Payments; -using SmartStore.Utilities; - -namespace SmartStore.AmazonPay.Api -{ - public class AmazonPayApi : IAmazonPayApi - { - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly IOrderService _orderService; - private readonly IAddressService _addressService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly CurrencySettings _currencySettings; - private readonly IOrderTotalCalculationService _orderTotalCalculationService; - private readonly ICommonServices _services; - - public AmazonPayApi( - ICountryService countryService, - IStateProvinceService stateProvinceService, - IOrderService orderService, - IAddressService addressService, - IDateTimeHelper dateTimeHelper, - CurrencySettings currencySettings, - IOrderTotalCalculationService orderTotalCalculationService, - ICommonServices services) - { - _countryService = countryService; - _stateProvinceService = stateProvinceService; - _orderService = orderService; - _addressService = addressService; - _dateTimeHelper = dateTimeHelper; - _currencySettings = currencySettings; - _orderTotalCalculationService = orderTotalCalculationService; - _services = services; - } - - private string GetRandomId(string prefix) - { - string str = prefix + CommonHelper.GenerateRandomDigitCode(20); - return str.Truncate(32); - } - - public void GetConstraints(OrderReferenceDetails details, IList warnings) - { - try - { - if (details != null && warnings != null && details.IsSetConstraints()) - { - foreach (var constraint in details.Constraints.Constraint) - { - string warning = "{0} ({1})".FormatWith(constraint.Description, constraint.ConstraintID); - warnings.Add(warning); - } - } - } - catch (Exception) { } - } - - public bool FindAndApplyAddress(OrderReferenceDetails details, Customer customer, bool isShippable, bool forceToTakeAmazonAddress) - { - // PlaceOrder requires billing address but we don't get one from Amazon here. so use shipping address instead until we get it from amazon. - bool countryAllowsShipping, countryAllowsBilling; - - var amazonAddress = new SmartStore.Core.Domain.Common.Address() - { - CreatedOnUtc = DateTime.UtcNow - }; - - details.ToAddress(amazonAddress, _countryService, _stateProvinceService, out countryAllowsShipping, out countryAllowsBilling); - - if (isShippable && !countryAllowsShipping) - return false; - - if (amazonAddress.Email.IsEmpty()) - amazonAddress.Email = customer.Email; - - if (forceToTakeAmazonAddress) - { - // first time to get in touch with an amazon address - var existingAddress = customer.Addresses.ToList().FindAddress(amazonAddress, true); - - if (existingAddress == null) - { - customer.Addresses.Add(amazonAddress); - customer.BillingAddress = amazonAddress; - } - else - { - customer.BillingAddress = existingAddress; - } - } - else - { - if (customer.BillingAddress == null) - { - customer.Addresses.Add(amazonAddress); - customer.BillingAddress = amazonAddress; - } - - // we already have the address but it is uncomplete, so just complete it - details.ToAddress(customer.BillingAddress, _countryService, _stateProvinceService, out countryAllowsShipping, out countryAllowsBilling); - - // but now we could have dublicates - int newAddressId = customer.BillingAddress.Id; - var addresses = customer.Addresses.Where(x => x.Id != newAddressId).ToList(); - - var existingAddress = addresses.FindAddress(customer.BillingAddress, false); - - if (existingAddress != null) - { - // remove the new and take the old one - customer.RemoveAddress(customer.BillingAddress); - customer.BillingAddress = existingAddress; - - try - { - _addressService.DeleteAddress(newAddressId); - } - catch (Exception exc) - { - exc.Dump(); - } - } - } - - customer.ShippingAddress = (isShippable ? customer.BillingAddress : null); - - return true; - } - public bool FulfillBillingAddress(AmazonPaySettings settings, Order order, AuthorizationDetails details, out string formattedAddress) - { - formattedAddress = ""; - - if (details == null) - { - AmazonPayApiData data; - details = GetAuthorizationDetails(new AmazonPayClient(settings), order.AuthorizationTransactionId, out data); - } - - if (details == null || !details.IsSetAuthorizationBillingAddress()) - return false; - - bool countryAllowsShipping, countryAllowsBilling; - - // override billing address cause it is just copy of the shipping address - details.AuthorizationBillingAddress.ToAddress(order.BillingAddress, _countryService, _stateProvinceService, out countryAllowsShipping, out countryAllowsBilling); - - if (!countryAllowsBilling) - { - formattedAddress = details.AuthorizationBillingAddress.ToFormatedAddress(_countryService, _stateProvinceService); - return false; - } - - order.BillingAddress.CreatedOnUtc = DateTime.UtcNow; - - if (order.BillingAddress.Email.IsEmpty()) - order.BillingAddress.Email = order.Customer.Email; - - _orderService.UpdateOrder(order); - - formattedAddress = details.AuthorizationBillingAddress.ToFormatedAddress(_countryService, _stateProvinceService); - - return true; - } - - public OrderReferenceDetails GetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, string addressConsentToken = null) - { - var request = new GetOrderReferenceDetailsRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonOrderReferenceId = orderReferenceId; - request.AddressConsentToken = addressConsentToken; - - var response = client.Service.GetOrderReferenceDetails(request); - - if (response != null && response.IsSetGetOrderReferenceDetailsResult()) - { - var detailsResult = response.GetOrderReferenceDetailsResult; - - if (detailsResult.IsSetOrderReferenceDetails()) - return detailsResult.OrderReferenceDetails; - } - return null; - } - - public OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, decimal? orderTotalAmount, - string currencyCode, string orderGuid = null, string storeName = null) - { - var request = new SetOrderReferenceDetailsRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonOrderReferenceId = orderReferenceId; - - var attributes = new OrderReferenceAttributes(); - //attributes.SellerNote = client.Settings.SellerNoteOrderReference.Truncate(1024); - attributes.PlatformId = AmazonPayCore.PlatformId; - - if (orderTotalAmount.HasValue) - { - attributes.OrderTotal = new OrderTotal - { - Amount = orderTotalAmount.Value.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = currencyCode ?? "EUR" - }; - } - - if (orderGuid.HasValue()) - { - attributes.SellerOrderAttributes = new SellerOrderAttributes - { - SellerOrderId = orderGuid, - StoreName = storeName - }; - } - - request.OrderReferenceAttributes = attributes; - - var response = client.Service.SetOrderReferenceDetails(request); - - if (response != null && response.IsSetSetOrderReferenceDetailsResult()) - { - var detailsResult = response.SetOrderReferenceDetailsResult; - - if (detailsResult.IsSetOrderReferenceDetails()) - return detailsResult.OrderReferenceDetails; - } - return null; - } - - public OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, string currencyCode, List cart) - { - decimal orderTotalDiscountAmountBase = decimal.Zero; - Discount orderTotalAppliedDiscount = null; - List appliedGiftCards = null; - int redeemedRewardPoints = 0; - decimal redeemedRewardPointsAmount = decimal.Zero; - - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, - out orderTotalDiscountAmountBase, out orderTotalAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); - - if (shoppingCartTotalBase.HasValue) - { - return SetOrderReferenceDetails(client, orderReferenceId, shoppingCartTotalBase, currencyCode); - } - - return null; - } - - /// Confirm an order reference informs Amazon that the buyer has placed the order. - public void ConfirmOrderReference(AmazonPayClient client, string orderReferenceId) - { - var request = new ConfirmOrderReferenceRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonOrderReferenceId = orderReferenceId; - - var response = client.Service.ConfirmOrderReference(request); - } - - public void CancelOrderReference(AmazonPayClient client, string orderReferenceId) - { - var request = new CancelOrderReferenceRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonOrderReferenceId = orderReferenceId; - - var response = client.Service.CancelOrderReference(request); - } - - public void CloseOrderReference(AmazonPayClient client, string orderReferenceId) - { - var request = new CloseOrderReferenceRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonOrderReferenceId = orderReferenceId; - - var response = client.Service.CloseOrderReference(request); - } - - /// Asynchronous as long as we do not set TransactionTimeout to 0. So transaction is always in pending state after return. - public void Authorize(AmazonPayClient client, ProcessPaymentResult result, List errors, string orderReferenceId, decimal orderTotalAmount, string currencyCode, string orderGuid) - { - var request = new AuthorizeRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonOrderReferenceId = orderReferenceId; - request.AuthorizationReferenceId = GetRandomId("Authorize"); - request.CaptureNow = (client.Settings.TransactionType == AmazonPayTransactionType.AuthorizeAndCapture); - //request.SellerAuthorizationNote = client.Settings.SellerNoteAuthorization.Truncate(256); - - request.AuthorizationAmount = new Price() - { - Amount = orderTotalAmount.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = currencyCode ?? "EUR" - }; - - var response = client.Service.Authorize(request); - - if (response != null && response.IsSetAuthorizeResult() && response.AuthorizeResult.IsSetAuthorizationDetails()) - { - var details = response.AuthorizeResult.AuthorizationDetails; - - result.AuthorizationTransactionId = details.AmazonAuthorizationId; - result.AuthorizationTransactionCode = details.AuthorizationReferenceId; - - if (details.IsSetAuthorizationStatus()) - { - var status = details.AuthorizationStatus; - - if (status.IsSetState()) - { - result.AuthorizationTransactionResult = status.State.ToString(); - } - - if (request.CaptureNow && details.IsSetIdList() && details.IdList.IsSetmember() && details.IdList.member.Count() > 0) - { - result.CaptureTransactionId = details.IdList.member[0]; - } - - if (status.IsSetReasonCode()) - { - if (status.ReasonCode.IsCaseInsensitiveEqual("InvalidPaymentMethod") || status.ReasonCode.IsCaseInsensitiveEqual("AmazonRejected") || - status.ReasonCode.IsCaseInsensitiveEqual("ProcessingFailure") || status.ReasonCode.IsCaseInsensitiveEqual("TransactionTimedOut") || - status.ReasonCode.IsCaseInsensitiveEqual("TransactionTimeout")) - { - if (status.IsSetReasonDescription()) - errors.Add("{0}: {1}".FormatWith(status.ReasonCode, status.ReasonDescription)); - else - errors.Add(status.ReasonCode); - } - } - } - } - - // The response to the Authorize call includes the AuthorizationStatus response element, which will be always be - // set to Pending if you have selected the asynchronous mode of operation. - - result.NewPaymentStatus = Core.Domain.Payments.PaymentStatus.Pending; - } - - public AuthorizationDetails GetAuthorizationDetails(AmazonPayClient client, string authorizationId, out AmazonPayApiData data) - { - data = new AmazonPayApiData(); - - AuthorizationDetails details = null; - var request = new GetAuthorizationDetailsRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonAuthorizationId = authorizationId; - - var response = client.Service.GetAuthorizationDetails(request); - - if (response.IsSetGetAuthorizationDetailsResult()) - { - var result = response.GetAuthorizationDetailsResult; - - if (result != null && result.IsSetAuthorizationDetails()) - details = result.AuthorizationDetails; - } - - try - { - data.MessageType = "GetAuthorizationDetails"; - - if (response.IsSetResponseMetadata() && response.ResponseMetadata.IsSetRequestId()) - data.MessageId = response.ResponseMetadata.RequestId; - - if (details != null) - { - if (details.IsSetAmazonAuthorizationId()) - data.AuthorizationId = details.AmazonAuthorizationId; - - if (details.IsSetAuthorizationReferenceId()) - data.ReferenceId = details.AuthorizationReferenceId; - - if (details.IsSetIdList() && details.IdList.IsSetmember()) - data.CaptureId = (details.IdList.member != null && details.IdList.member.Count > 0 ? details.IdList.member[0] : null); - - if (details.IsSetAuthorizationFee()) - data.Fee = new AmazonPayApiPrice(details.AuthorizationFee.Amount, details.AuthorizationFee.CurrencyCode); - - if (details.IsSetAuthorizationAmount()) - data.AuthorizedAmount = new AmazonPayApiPrice(details.AuthorizationAmount.Amount, details.AuthorizationAmount.CurrencyCode); - - if (details.IsSetCapturedAmount()) - data.CapturedAmount = new AmazonPayApiPrice(details.CapturedAmount.Amount, details.CapturedAmount.CurrencyCode); - - if (details.IsSetCaptureNow()) - data.CaptureNow = details.CaptureNow; - - if (details.IsSetCreationTimestamp()) - data.Creation = details.CreationTimestamp; - - if (details.IsSetExpirationTimestamp()) - data.Expiration = details.ExpirationTimestamp; - - if (details.IsSetAuthorizationStatus()) - { - data.ReasonCode = details.AuthorizationStatus.ReasonCode; - data.ReasonDescription = details.AuthorizationStatus.ReasonDescription; - data.State = details.AuthorizationStatus.State.ToString(); - data.StateLastUpdate = details.AuthorizationStatus.LastUpdateTimestamp; - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - return details; - } - - public void Capture(AmazonPayClient client, CapturePaymentRequest capture, CapturePaymentResult result) - { - result.NewPaymentStatus = capture.Order.PaymentStatus; - - var request = new CaptureRequest(); - var store = _services.StoreService.GetStoreById(capture.Order.StoreId); - - request.SellerId = client.Settings.SellerId; - request.AmazonAuthorizationId = capture.Order.AuthorizationTransactionId; - request.CaptureReferenceId = GetRandomId("Capture"); - //request.SellerCaptureNote = client.Settings.SellerNoteCapture.Truncate(255); - - request.CaptureAmount = new Price - { - Amount = capture.Order.OrderTotal.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = store.PrimaryStoreCurrency.CurrencyCode - }; - - var response = client.Service.Capture(request); - - if (response != null && response.IsSetCaptureResult() && response.CaptureResult.IsSetCaptureDetails()) - { - var details = response.CaptureResult.CaptureDetails; - - result.CaptureTransactionId = details.AmazonCaptureId; - - if (details.IsSetCaptureStatus() && details.CaptureStatus.IsSetState()) - { - result.CaptureTransactionResult = details.CaptureStatus.State.ToString().Grow(details.CaptureStatus.ReasonCode, " "); - - if (details.CaptureStatus.State == PaymentStatus.COMPLETED) - result.NewPaymentStatus = Core.Domain.Payments.PaymentStatus.Paid; - } - } - } - - public CaptureDetails GetCaptureDetails(AmazonPayClient client, string captureId, out AmazonPayApiData data) - { - data = new AmazonPayApiData(); - - CaptureDetails details = null; - var request = new GetCaptureDetailsRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonCaptureId = captureId; - - var response = client.Service.GetCaptureDetails(request); - - if (response != null && response.IsSetGetCaptureDetailsResult()) - { - var result = response.GetCaptureDetailsResult; - if (result != null && result.IsSetCaptureDetails()) - details = result.CaptureDetails; - } - - try - { - data.MessageType = "GetCaptureDetails"; - - if (response.IsSetResponseMetadata() && response.ResponseMetadata.IsSetRequestId()) - data.MessageId = response.ResponseMetadata.RequestId; - - if (details != null) - { - if (details.IsSetAmazonCaptureId()) - data.CaptureId = details.AmazonCaptureId; - - if (details.IsSetCaptureReferenceId()) - data.ReferenceId = details.CaptureReferenceId; - - if (details.IsSetCaptureFee()) - data.Fee = new AmazonPayApiPrice(details.CaptureFee.Amount, details.CaptureFee.CurrencyCode); - - if (details.IsSetCaptureAmount()) - data.CapturedAmount = new AmazonPayApiPrice(details.CaptureAmount.Amount, details.CaptureAmount.CurrencyCode); - - if (details.IsSetRefundedAmount()) - data.RefundedAmount = new AmazonPayApiPrice(details.RefundedAmount.Amount, details.RefundedAmount.CurrencyCode); - - if (details.IsSetCreationTimestamp()) - data.Creation = details.CreationTimestamp; - - if (details.IsSetCaptureStatus()) - { - data.ReasonCode = details.CaptureStatus.ReasonCode; - data.ReasonDescription = details.CaptureStatus.ReasonDescription; - data.State = details.CaptureStatus.State.ToString(); - data.StateLastUpdate = details.CaptureStatus.LastUpdateTimestamp; - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - return details; - } - - public string Refund(AmazonPayClient client, RefundPaymentRequest refund, RefundPaymentResult result) - { - result.NewPaymentStatus = refund.Order.PaymentStatus; - - string amazonRefundId = null; - var store = _services.StoreService.GetStoreById(refund.Order.StoreId); - - var request = new RefundRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonCaptureId = refund.Order.CaptureTransactionId; - request.RefundReferenceId = GetRandomId("Refund"); - //request.SellerRefundNote = client.Settings.SellerNoteRefund.Truncate(255); - - request.RefundAmount = new Price - { - Amount = refund.AmountToRefund.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = store.PrimaryStoreCurrency.CurrencyCode - }; - - var response = client.Service.Refund(request); - - if (response != null && response.IsSetRefundResult() && response.RefundResult.IsSetRefundDetails()) - { - var details = response.RefundResult.RefundDetails; - - amazonRefundId = details.AmazonRefundId; - - if (details.IsSetRefundStatus() && details.RefundStatus.IsSetState()) - { - if (refund.IsPartialRefund) - result.NewPaymentStatus = Core.Domain.Payments.PaymentStatus.PartiallyRefunded; - else - result.NewPaymentStatus = Core.Domain.Payments.PaymentStatus.Refunded; - } - } - return amazonRefundId; - } - - public RefundDetails GetRefundDetails(AmazonPayClient client, string refundId, out AmazonPayApiData data) - { - data = new AmazonPayApiData(); - - RefundDetails details = null; - var request = new GetRefundDetailsRequest(); - request.SellerId = client.Settings.SellerId; - request.AmazonRefundId = refundId; - - var response = client.Service.GetRefundDetails(request); - - if (response != null && response.IsSetGetRefundDetailsResult()) - { - var result = response.GetRefundDetailsResult; - if (result != null && result.IsSetRefundDetails()) - details = result.RefundDetails; - } - - try - { - data.MessageType = "GetRefundDetails"; - - if (response.IsSetResponseMetadata() && response.ResponseMetadata.IsSetRequestId()) - data.MessageId = response.ResponseMetadata.RequestId; - - if (details != null) - { - if (details.IsSetAmazonRefundId()) - data.RefundId = details.AmazonRefundId; - - if (details.IsSetRefundReferenceId()) - data.ReferenceId = details.RefundReferenceId; - - if (details.IsSetFeeRefunded()) - data.Fee = new AmazonPayApiPrice(details.FeeRefunded.Amount, details.FeeRefunded.CurrencyCode); - - if (details.IsSetRefundAmount()) - data.RefundedAmount = new AmazonPayApiPrice(details.RefundAmount.Amount, details.RefundAmount.CurrencyCode); - - if (details.IsSetCreationTimestamp()) - data.Creation = details.CreationTimestamp; - - if (details.IsSetRefundStatus()) - { - data.ReasonCode = details.RefundStatus.ReasonCode; - data.ReasonDescription = details.RefundStatus.ReasonDescription; - data.State = details.RefundStatus.State.ToString(); - data.StateLastUpdate = details.RefundStatus.LastUpdateTimestamp; - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - return details; - } - - public string ToInfoString(AmazonPayApiData data) - { - var sb = new StringBuilder(); - - try - { - string[] strings = _services.Localization.GetResource("Plugins.Payments.AmazonPay.MessageStrings").SplitSafe(";"); - - string state = data.State.Grow(data.ReasonCode, " "); - if (data.ReasonDescription.HasValue()) - state = "{0} ({1})".FormatWith(state, data.ReasonDescription); - - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.MessageTyp), data.MessageType.NaIfEmpty())); - - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.State), state)); - - var stateDate = _dateTimeHelper.ConvertToUserTime(data.StateLastUpdate, DateTimeKind.Utc); - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.StateUpdate), stateDate.ToString())); - - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.MessageId), data.MessageId.NaIfEmpty())); - - if (data.AuthorizationId.HasValue()) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.AuthorizationID), data.AuthorizationId)); - - if (data.CaptureId.HasValue()) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.CaptureID), data.CaptureId)); - - if (data.RefundId.HasValue()) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.RefundID), data.RefundId)); - - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.ReferenceID), data.ReferenceId.NaIfEmpty())); - - if (data.Fee != null && data.Fee.Amount != 0.0) - { - bool isSigned = (data.MessageType.IsCaseInsensitiveEqual("RefundNotification") || data.MessageType.IsCaseInsensitiveEqual("GetRefundDetails")); - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.Fee), (isSigned ? "-" : "") + data.Fee.ToString())); - } - - if (data.AuthorizedAmount != null && data.AuthorizedAmount.Amount != 0.0) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.AuthorizedAmount), data.AuthorizedAmount.ToString())); - - if (data.CapturedAmount != null && data.CapturedAmount.Amount != 0.0) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.CapturedAmount), data.CapturedAmount.ToString())); - - if (data.RefundedAmount != null && data.RefundedAmount.Amount != 0.0) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.RefundedAmount), data.RefundedAmount.ToString())); - - if (data.CaptureNow.HasValue) - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.CaptureNow), data.CaptureNow.Value.ToString())); - - var creationDate = _dateTimeHelper.ConvertToUserTime(data.Creation, DateTimeKind.Utc); - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.Creation), creationDate.ToString())); - - if (data.Expiration.HasValue) - { - var expirationDate = _dateTimeHelper.ConvertToUserTime(data.Expiration.Value, DateTimeKind.Utc); - sb.AppendLine("{0}: {1}".FormatWith(strings.SafeGet((int)AmazonPayMessage.Expiration), expirationDate.ToString())); - } - } - catch (Exception exc) - { - exc.Dump(); - } - return sb.ToString(); - } - - public AmazonPayApiData ParseNotification(HttpRequestBase request) - { - string json = null; - - using (var reader = new StreamReader(request.InputStream)) - { - json = reader.ReadToEnd(); - } - - var parser = new OffAmazonPaymentsNotifications.NotificationsParser(); - var message = parser.ParseRawMessage(request.Headers, json); - - var data = new AmazonPayApiData() - { - MessageType = message.NotificationType.ToString(), - MessageId = ((OffAmazonPaymentsNotifications.IpnNotificationMetadata)message.NotificationMetadata).NotificationReferenceId - }; - - if (message.NotificationType == OffAmazonPaymentsNotifications.NotificationType.AuthorizationNotification) - { - var details = ((OffAmazonPaymentsNotifications.AuthorizationNotification)message).AuthorizationDetails; - - data.AuthorizationId = details.AmazonAuthorizationId; - data.CaptureId = details.IdList.SafeGet(0); - data.ReferenceId = details.AuthorizationReferenceId; - data.CaptureNow = details.CaptureNow; - data.Creation = details.CreationTimestamp; - - if (details.AuthorizationFee != null) - data.Fee = new AmazonPayApiPrice(details.AuthorizationFee.Amount, details.AuthorizationFee.CurrencyCode); - - if (details.AuthorizationAmount != null) - data.AuthorizedAmount = new AmazonPayApiPrice(details.AuthorizationAmount.Amount, details.AuthorizationAmount.CurrencyCode); - - if (details.CapturedAmount != null) - data.CapturedAmount = new AmazonPayApiPrice(details.CapturedAmount.Amount, details.CapturedAmount.CurrencyCode); - - if (details.ExpirationTimestampSpecified) - data.Expiration = details.ExpirationTimestamp; - - if (details.AuthorizationStatus != null) - { - data.ReasonCode = details.AuthorizationStatus.ReasonCode; - data.ReasonDescription = details.AuthorizationStatus.ReasonDescription; - data.State = details.AuthorizationStatus.State; - data.StateLastUpdate = details.AuthorizationStatus.LastUpdateTimestamp; - } - } - else if (message.NotificationType == OffAmazonPaymentsNotifications.NotificationType.CaptureNotification) - { - var details = ((OffAmazonPaymentsNotifications.CaptureNotification)message).CaptureDetails; - - data.CaptureId = details.AmazonCaptureId; - data.ReferenceId = details.CaptureReferenceId; - data.Creation = details.CreationTimestamp; - - if (details.CaptureFee != null) - data.Fee = new AmazonPayApiPrice(details.CaptureFee.Amount, details.CaptureFee.CurrencyCode); - - if (details.CaptureAmount != null) - data.CapturedAmount = new AmazonPayApiPrice(details.CaptureAmount.Amount, details.CaptureAmount.CurrencyCode); - - if (details.RefundedAmount != null) - data.RefundedAmount = new AmazonPayApiPrice(details.RefundedAmount.Amount, details.RefundedAmount.CurrencyCode); - - if (details.CaptureStatus != null) - { - data.ReasonCode = details.CaptureStatus.ReasonCode; - data.ReasonDescription = details.CaptureStatus.ReasonDescription; - data.State = details.CaptureStatus.State; - data.StateLastUpdate = details.CaptureStatus.LastUpdateTimestamp; - } - } - else if (message.NotificationType == OffAmazonPaymentsNotifications.NotificationType.RefundNotification) - { - var details = ((OffAmazonPaymentsNotifications.RefundNotification)message).RefundDetails; - - data.RefundId = details.AmazonRefundId; - data.ReferenceId = details.RefundReferenceId; - data.Creation = details.CreationTimestamp; - - if (details.FeeRefunded != null) - data.Fee = new AmazonPayApiPrice(details.FeeRefunded.Amount, details.FeeRefunded.CurrencyCode); - - if (details.RefundAmount != null) - data.RefundedAmount = new AmazonPayApiPrice(details.RefundAmount.Amount, details.RefundAmount.CurrencyCode); - - if (details.RefundStatus != null) - { - data.ReasonCode = details.RefundStatus.ReasonCode; - data.ReasonDescription = details.RefundStatus.ReasonDescription; - data.State = details.RefundStatus.State; - data.StateLastUpdate = details.RefundStatus.LastUpdateTimestamp; - } - } - - return data; - } - } - - - public class AmazonPayClient - { - public AmazonPayClient(AmazonPaySettings settings) - { - string appVersion = "1.0"; - - try - { - appVersion = EngineContext.Current.Resolve().GetPluginDescriptorBySystemName(AmazonPayCore.SystemName).Version.ToString(); - } - catch (Exception) { } - - var config = new OffAmazonPaymentsServiceConfig() - { - ServiceURL = (settings.UseSandbox ? AmazonPayCore.UrlApiEuSandbox : AmazonPayCore.UrlApiEuProduction) - }; - - config.SetUserAgent(AmazonPayCore.AppName, appVersion); - - Settings = settings; - Service = new OffAmazonPaymentsServiceClient(AmazonPayCore.AppName, appVersion, settings.AccessKey, settings.SecretKey, config); - } - - public IOffAmazonPaymentsService Service { get; private set; } - public AmazonPaySettings Settings { get; private set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayCore.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayCore.cs deleted file mode 100644 index da9480d3db..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayCore.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using SmartStore.AmazonPay.Api; -using SmartStore.AmazonPay.Settings; -using SmartStore.Core.Domain.Orders; - -namespace SmartStore.AmazonPay.Services -{ - public static class AmazonPayCore - { - public static string PlatformId { get { return "A3OJ83WFYM72IY"; } } - public static string AppName { get { return "SmartStore.Net " + SystemName; } } - public static string SystemName { get { return "SmartStore.AmazonPay"; } } - public static string AmazonPayCheckoutStateKey { get { return SystemName + ".CheckoutState"; } } - public static string AmazonPayOrderAttributeKey { get { return SystemName + ".OrderAttribute"; } } - public static string AmazonPayRefundIdKey { get { return SystemName + ".RefundId"; } } - public static string DataPollingTaskType { get { return "SmartStore.AmazonPay.DataPollingTask, SmartStore.AmazonPay"; } } - - public static string UrlApiEuSandbox { get { return "https://mws-eu.amazonservices.com/OffAmazonPayments_Sandbox/2013-01-01/"; } } - public static string UrlApiEuProduction { get { return "https://mws-eu.amazonservices.com/OffAmazonPayments/2013-01-01/"; } } - - public static string UrlWidgetSandbox { get { return "https://static-eu.payments-amazon.com/OffAmazonPayments/{0}/sandbox/js/Widgets.js"; } } - public static string UrlWidgetProduction { get { return "https://static-eu.payments-amazon.com/OffAmazonPayments/{0}/js/Widgets.js"; } } - - public static string UrlButtonSandbox { get { return "https://payments-sandbox.amazon.{0}/gp/widgets/button"; } } - public static string UrlButtonProduction { get { return "https://payments.amazon.{0}/gp/widgets/button"; } } - - public static string UrlIpnSchema { get { return "https://amazonpayments.s3.amazonaws.com/documents/payments_ipn.xsd"; } } - } - - - public class AmazonPayCheckoutState - { - public string OrderReferenceId { get; set; } - } - - - public class AmazonPayActionState - { - public Guid OrderGuid { get; set; } - public List Errors { get; set; } - } - - - [Serializable] - public class AmazonPayOrderAttribute - { - public string OrderReferenceId { get; set; } - public bool IsBillingAddressApplied { get; set; } - } - - - public class PollingLoopData - { - public PollingLoopData(int orderId) - { - OrderId = orderId; - } - - public int OrderId { get; private set; } - public Order Order { get; set; } - public AmazonPaySettings Settings { get; set; } - public AmazonPayClient Client { get; set; } - } - - - public class AmazonPayApiData - { - public string MessageType { get; set; } - public string MessageId { get; set; } - public string AuthorizationId { get; set; } - public string CaptureId { get; set; } - public string RefundId { get; set; } - public string ReferenceId { get; set; } - - public string ReasonCode { get; set; } - public string ReasonDescription { get; set; } - public string State { get; set; } - public DateTime StateLastUpdate { get; set; } - - public AmazonPayApiPrice Fee { get; set; } - public AmazonPayApiPrice AuthorizedAmount { get; set; } - public AmazonPayApiPrice CapturedAmount { get; set; } - public AmazonPayApiPrice RefundedAmount { get; set; } - - public bool? CaptureNow { get; set; } - public DateTime Creation { get; set; } - public DateTime? Expiration { get; set; } - - public string AnyAmazonId - { - get - { - if (CaptureId.HasValue()) - return CaptureId; - if (AuthorizationId.HasValue()) - return AuthorizationId; - return RefundId; - } - } - } - - - public class AmazonPayApiPrice - { - public AmazonPayApiPrice() - { - } - public AmazonPayApiPrice(double amount, string currenycCode) - { - Amount = amount; - CurrencyCode = currenycCode; - } - public AmazonPayApiPrice(string amount, string currenycCode) - { - double d; - if (amount.HasValue() && double.TryParse(amount, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) - Amount = d; - - CurrencyCode = currenycCode; - } - - public double Amount { get; set; } - public string CurrencyCode { get; set; } - - public override string ToString() - { - string str = Amount.ToString("0.00", CultureInfo.InvariantCulture); - return str.Grow(CurrencyCode, " "); - } - } - - - public enum AmazonPayRequestType : int - { - None = 0, - ShoppingCart, - Address, - Payment, - OrderReviewData, - ShippingMethod, - MiniShoppingCart, - LoginHandler - } - - public enum AmazonPayTransactionType : int - { - None = 0, - Authorize, - AuthorizeAndCapture - } - - public enum AmazonPaySaveDataType : int - { - None = 0, - OnlyIfEmpty, - Always - } - - public enum AmazonPayDataFetchingType : int - { - None = 0, - Ipn, - Polling - } - - public enum AmazonPayResultType : int - { - None = 0, - PluginView, - Redirect, - Unauthorized - } - - public enum AmazonPayOrderNote : int - { - FunctionExecuted = 0, - Answer, - BillingAddressApplied, - AmazonMessageProcessed, - BillingAddressCountryNotAllowed - } - - public enum AmazonPayMessage : int - { - MessageTyp = 0, - MessageId, - AuthorizationID, - CaptureID, - RefundID, - ReferenceID, - State, - StateUpdate, - Fee, - AuthorizedAmount, - CapturedAmount, - RefundedAmount, - CaptureNow, - Creation, - Expiration - } -} diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayEnums.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayEnums.cs new file mode 100644 index 0000000000..4e9144bc9a --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayEnums.cs @@ -0,0 +1,78 @@ +namespace SmartStore.AmazonPay.Services +{ + public enum AmazonPayRequestType + { + None = 0, + ShoppingCart, + Address, + PaymentMethod, + OrderReviewData, + ShippingMethod, + MiniShoppingCart, + + /// + /// Amazon Pay button clicked + /// + PayButtonHandler, + + /// + /// Display authentication button on login page + /// + AuthenticationPublicInfo + } + + public enum AmazonPayTransactionType + { + None = 0, + Authorize, + AuthorizeAndCapture + } + + public enum AmazonPayAuthorizeMethod + { + Omnichronous = 0, + Asynchronous, + Synchronous + } + + public enum AmazonPaySaveDataType + { + None = 0, + OnlyIfEmpty, + Always + } + + public enum AmazonPayDataFetchingType + { + None = 0, + Ipn, + Polling + } + + public enum AmazonPayResultType + { + None = 0, + PluginView, + Redirect, + Unauthorized + } + + public enum AmazonPayMessage + { + MessageTyp = 0, + MessageId, + AuthorizationID, + CaptureID, + RefundID, + ReferenceID, + State, + StateUpdate, + Fee, + AuthorizedAmount, + CapturedAmount, + RefundedAmount, + CaptureNow, + Creation, + Expiration + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs index 396fac7865..a24c0646f7 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs @@ -4,96 +4,102 @@ using System.Linq; using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Web; using System.Web.Mvc; -using System.Xml.Serialization; +using AmazonPay; +using AmazonPay.Responses; +using AmazonPay.StandardPaymentRequests; using Autofac; -using OffAmazonPaymentsService; -using SmartStore.AmazonPay.Api; -using SmartStore.AmazonPay.Extensions; +using Newtonsoft.Json.Linq; using SmartStore.AmazonPay.Models; -using SmartStore.AmazonPay.Settings; +using SmartStore.AmazonPay.Services.Internal; +using SmartStore.Core; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Localization; using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; using SmartStore.Services; -using SmartStore.Services.Catalog; +using SmartStore.Services.Authentication.External; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; -using SmartStore.Services.Messages; +using SmartStore.Services.Helpers; +using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Tasks; +using SmartStore.Web; +using SmartStore.Web.Framework; namespace SmartStore.AmazonPay.Services { - public class AmazonPayService : IAmazonPayService + public partial class AmazonPayService : IAmazonPayService { - private readonly IAmazonPayApi _api; private readonly HttpContextBase _httpContext; + private readonly IRepository _orderRepository; private readonly ICommonServices _services; private readonly IPaymentService _paymentService; private readonly IGenericAttributeService _genericAttributeService; - private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly ICustomerService _customerService; - private readonly IPriceFormatter _priceFormatter; - private readonly OrderSettings _orderSettings; - private readonly RewardPointsSettings _rewardPointsSettings; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; private readonly IOrderService _orderService; - private readonly IRepository _orderRepository; private readonly IOrderProcessingService _orderProcessingService; - private readonly IScheduleTaskService _scheduleTaskService; - private readonly IWorkflowMessageService _workflowMessageService; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IPluginFinder _pluginFinder; + private readonly Lazy _authorizer; + private readonly AddressSettings _addressSettings; + private readonly OrderSettings _orderSettings; + private readonly CompanyInformationSettings _companyInformationSettings; public AmazonPayService( - IAmazonPayApi api, HttpContextBase httpContext, + IRepository orderRepository, ICommonServices services, IPaymentService paymentService, IGenericAttributeService genericAttributeService, - IOrderTotalCalculationService orderTotalCalculationService, ICurrencyService currencyService, - CurrencySettings currencySettings, ICustomerService customerService, - IPriceFormatter priceFormatter, - OrderSettings orderSettings, - RewardPointsSettings rewardPointsSettings, + ICountryService countryService, + IStateProvinceService stateProvinceService, IOrderService orderService, - IRepository orderRepository, IOrderProcessingService orderProcessingService, - IScheduleTaskService scheduleTaskService, - IWorkflowMessageService workflowMessageService) + IOrderTotalCalculationService orderTotalCalculationService, + IDateTimeHelper dateTimeHelper, + IPluginFinder pluginFinder, + Lazy authorizer, + AddressSettings addressSettings, + OrderSettings orderSettings, + CompanyInformationSettings companyInformationSettings) { - _api = api; _httpContext = httpContext; + _orderRepository = orderRepository; _services = services; _paymentService = paymentService; _genericAttributeService = genericAttributeService; - _orderTotalCalculationService = orderTotalCalculationService; _currencyService = currencyService; - _currencySettings = currencySettings; _customerService = customerService; - _priceFormatter = priceFormatter; - _orderSettings = orderSettings; - _rewardPointsSettings = rewardPointsSettings; + _countryService = countryService; + _stateProvinceService = stateProvinceService; _orderService = orderService; - _orderRepository = orderRepository; _orderProcessingService = orderProcessingService; - _scheduleTaskService = scheduleTaskService; - _workflowMessageService = workflowMessageService; + _orderTotalCalculationService = orderTotalCalculationService; + + _dateTimeHelper = dateTimeHelper; + _pluginFinder = pluginFinder; + _authorizer = authorizer; + _addressSettings = addressSettings; + _orderSettings = orderSettings; + _companyInformationSettings = companyInformationSettings; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -102,174 +108,84 @@ public AmazonPayService( public Localizer T { get; set; } public ILogger Logger { get; set; } - private string GetPluginUrl(string action, bool useSsl = false) - { - string pluginUrl = "{0}Plugins/SmartStore.AmazonPay/AmazonPay/{1}".FormatWith(_services.WebHelper.GetStoreLocation(useSsl), action); - return pluginUrl; - } - - //private decimal? GetOrderTotal() - //{ - // decimal orderTotalDiscountAmountBase = decimal.Zero; - // Discount orderTotalAppliedDiscount = null; - // List appliedGiftCards = null; - // int redeemedRewardPoints = 0; - // decimal redeemedRewardPointsAmount = decimal.Zero; - - // var cart = _services.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _services.StoreContext.CurrentStore.Id); - - // decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, - // out orderTotalDiscountAmountBase, out orderTotalAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); - - // if (shoppingCartTotalBase.HasValue) // shipping method needs to be selected here! - // { - // decimal shoppingCartTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTotalBase.Value, _services.WorkContext.WorkingCurrency); - - // return shoppingCartTotal; - // } - // return null; - //} - - private void SerializeOrderAttribute(AmazonPayOrderAttribute attribute, Order order) - { - if (attribute != null) - { - var sb = new StringBuilder(); - using (var writer = new StringWriter(sb)) - { - var serializer = new XmlSerializer(typeof(AmazonPayOrderAttribute)); - serializer.Serialize(writer, attribute); - - _genericAttributeService.SaveAttribute(order, AmazonPayCore.AmazonPayOrderAttributeKey, sb.ToString(), order.StoreId); - } - } - } - private AmazonPayOrderAttribute DeserializeOrderAttribute(Order order) + public void SetupConfiguration(ConfigurationModel model) { - var serialized = order.GetAttribute(AmazonPayCore.AmazonPayOrderAttributeKey, _genericAttributeService, order.StoreId); - - if (!serialized.HasValue()) - { - var attribute = new AmazonPayOrderAttribute(); - - // legacy < v.1.14 - attribute.OrderReferenceId = order.GetAttribute(AmazonPayCore.SystemName + ".OrderReferenceId", order.StoreId); + var store = _services.StoreContext.CurrentStore; + var language = _services.WorkContext.WorkingLanguage; + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(AmazonPayPlugin.SystemName); + var allStores = _services.StoreService.GetAllStores(); + var urlHelper = new UrlHelper(_httpContext.Request.RequestContext); - return attribute; - } + model.IpnUrl = GetPluginUrl("IPNHandler", true); + model.ConfigGroups = T("Plugins.Payments.AmazonPay.ConfigGroups").Text.SplitSafe(";"); + model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; - using (var reader = new StringReader(serialized)) + model.RegisterUrl = "https://payments-eu.amazon.com/register"; + model.SoftwareVersion = SmartStoreVersion.CurrentFullVersion; + if (descriptor != null) { - var serializer = new XmlSerializer(typeof(AmazonPayOrderAttribute)); - return (AmazonPayOrderAttribute)serializer.Deserialize(reader); + model.PluginVersion = descriptor.Version.ToString(); } - } - - public void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null) - { - try - { - if (exception != null) - { - shortMessage = exception.Message; - fullMessage = exception.ToString(); - exception.Dump(); - } - - if (shortMessage.HasValue()) + model.LeadCode = LeadCode; + model.PlatformId = PlatformId; + // Not implemented. Not available for europe at the moment. + model.PublicKey = string.Empty; + model.KeyShareUrl = GetPluginUrl("ShareKey", store.SslEnabled); + model.LanguageLocale = language.UniqueSeoCode.ToAmazonLanguageCode('_'); + model.MerchantStoreDescription = store.Name.Truncate(2048); + model.MerchantPrivacyNoticeUrl = WebHelper.GetAbsoluteUrl(urlHelper.Topic("privacyinfo"), _httpContext.Request, protocol: store.SslEnabled ? "https" : "http"); + model.MerchantSandboxIpnUrl = model.IpnUrl; + model.MerchantProductionIpnUrl = model.IpnUrl; + + model.MerchantLoginDomains = new HashSet(StringComparer.OrdinalIgnoreCase); + model.MerchantLoginRedirectUrls = new HashSet(StringComparer.OrdinalIgnoreCase); + model.CurrentMerchantLoginDomains = new HashSet(StringComparer.OrdinalIgnoreCase); + model.CurrentMerchantLoginRedirectUrls = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var entity in allStores) + { + // SSL required! + var shopUrl = entity.SslEnabled ? entity.SecureUrl : entity.Url; + if (shopUrl.HasValue()) { - Logger.InsertLog(LogLevel.Error, shortMessage, fullMessage.EmptyNull()); - - if (notify) - _services.Notifier.Error(new LocalizedString(shortMessage)); - } - } - catch (Exception) { } + try + { + // Only protocol and domain name. + var uri = new Uri(shopUrl); + var loginDomain = uri.GetLeftPart(UriPartial.Scheme | UriPartial.Authority).EmptyNull().TrimEnd('/'); + model.MerchantLoginDomains.Add(loginDomain); - if (errors != null && shortMessage.HasValue()) - errors.Add(shortMessage); - } - public void LogAmazonError(OffAmazonPaymentsServiceException exception, bool notify = false, IList errors = null) - { - try - { - string shortMessage, fullMessage; + if (entity.Id == store.Id) + { + model.CurrentMerchantLoginDomains.Add(loginDomain); + } + } + catch { } - if (exception.GetErrorStrings(out shortMessage, out fullMessage)) - { - Logger.InsertLog(LogLevel.Error, shortMessage, fullMessage); + var payHandlerUrl = shopUrl.EnsureEndsWith("/") + "Plugins/SmartStore.AmazonPay/AmazonPayShoppingCart/PayButtonHandler"; + var authHandlerUrl = shopUrl.EnsureEndsWith("/") + "Plugins/SmartStore.AmazonPay/AmazonPay/AuthenticationButtonHandler"; - if (notify) - _services.Notifier.Error(new LocalizedString(shortMessage)); + model.MerchantLoginRedirectUrls.Add(payHandlerUrl); + model.MerchantLoginRedirectUrls.Add(authHandlerUrl); - if (errors != null) - errors.Add(shortMessage); + if (entity.Id == store.Id) + { + model.CurrentMerchantLoginRedirectUrls.Add(payHandlerUrl); + model.CurrentMerchantLoginRedirectUrls.Add(authHandlerUrl); + } } } - catch (Exception) { } - } - - private bool IsActive(int storeId, bool logInactive = false) - { - bool isActive = _paymentService.IsPaymentMethodActive(AmazonPayCore.SystemName, storeId); - if (!isActive && logInactive) + if (_companyInformationSettings.CountryId != 0) { - LogError(null, T("Plugins.Payments.AmazonPay.PaymentMethodNotActive", _services.StoreContext.CurrentStore.Name)); - } - return isActive; - } - - public void AddOrderNote(AmazonPaySettings settings, Order order, AmazonPayOrderNote note, string anyString = null, bool isIpn = false) - { - try - { - if (!settings.AddOrderNotes || order == null) - return; - - var sb = new StringBuilder(); - - string[] orderNoteStrings = T("Plugins.Payments.AmazonPay.OrderNoteStrings").Text.SplitSafe(";"); - string faviconUrl = "{0}Plugins/{1}/Content/images/favicon.png".FormatWith(_services.WebHelper.GetStoreLocation(false), AmazonPayCore.SystemName); - - sb.AppendFormat("", faviconUrl); - - if (anyString.HasValue()) + var merchantCountry = _countryService.GetCountryById(_companyInformationSettings.CountryId); + if (merchantCountry != null) { - anyString = orderNoteStrings.SafeGet((int)note).FormatWith(anyString); + model.MerchantCountry = merchantCountry.GetLocalized(x => x.Name, language, false, false); } - else - { - anyString = orderNoteStrings.SafeGet((int)note); - anyString = anyString.Replace("{0}", ""); - } - - if (anyString.HasValue()) - { - sb.AppendFormat("{0}", anyString); - } - - if (isIpn) - order.HasNewPaymentNotification = true; - - order.OrderNotes.Add(new OrderNote - { - Note = sb.ToString(), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - - _orderService.UpdateOrder(order); - } - catch (Exception exc) - { - LogError(exc); } - } - public void SetupConfiguration(ConfigurationModel model) - { - model.DataFetchings = new List() + model.DataFetchings = new List { new SelectListItem { @@ -290,7 +206,7 @@ public void SetupConfiguration(ConfigurationModel model) } }; - model.TransactionTypes = new List() + model.TransactionTypes = new List { new SelectListItem { @@ -306,7 +222,9 @@ public void SetupConfiguration(ConfigurationModel model) } }; - model.SaveEmailAndPhones = new List() + model.AuthorizeMethods = model.AuthorizeMethod.ToSelectList(); + + model.SaveEmailAndPhones = new List { new SelectListItem { @@ -326,40 +244,9 @@ public void SetupConfiguration(ConfigurationModel model) Value = ((int)AmazonPaySaveDataType.Always).ToString() } }; - - model.IpnUrl = GetPluginUrl("IPNHandler", _services.StoreContext.CurrentStore.SslEnabled); - - model.ConfigGroups = T("Plugins.Payments.AmazonPay.ConfigGroups").Text.SplitSafe(";"); - - var task = _scheduleTaskService.GetTaskByType(AmazonPayCore.DataPollingTaskType); - - if (task == null) - model.PollingTaskMinutes = 30; - else - model.PollingTaskMinutes = 30; // (task.Seconds / 60); } - public string GetWidgetUrl() - { - try - { - var store = _services.StoreContext.CurrentStore; - - if (IsActive(store.Id)) - { - var settings = _services.Settings.LoadSetting(store.Id); - if (settings.SellerId.HasValue()) - return settings.GetWidgetUrl(); - } - } - catch (Exception exc) - { - LogError(exc); - } - return ""; - } - - public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDataDictionary tempData, string orderReferenceId = null) + public AmazonPayViewModel CreateViewModel(AmazonPayRequestType type, TempDataDictionary tempData) { var model = new AmazonPayViewModel(); model.Type = type; @@ -368,69 +255,78 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa { var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; + var language = _services.WorkContext.WorkingLanguage; var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + var storeLocation = _services.WebHelper.GetStoreLocation(store.SslEnabled); + var settings = _services.Settings.LoadSetting(store.Id); + + model.ButtonHandlerUrl = $"{storeLocation}Plugins/SmartStore.AmazonPay/AmazonPayShoppingCart/PayButtonHandler"; + model.LanguageCode = language.UniqueSeoCode.ToAmazonLanguageCode(); - if (type == AmazonPayRequestType.LoginHandler) + if (type == AmazonPayRequestType.PayButtonHandler) { - if (string.IsNullOrWhiteSpace(orderReferenceId)) + if (cart.Count <= 0 || !IsPaymentMethodActive(store.Id)) { - LogError(null, T("Plugins.Payments.AmazonPay.MissingOrderReferenceId"), null, true); model.Result = AmazonPayResultType.Redirect; return model; } - if (cart.Count <= 0 || !IsActive(store.Id)) + if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) { - model.Result = AmazonPayResultType.Redirect; + model.Result = AmazonPayResultType.Unauthorized; return model; } - if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) + var accessToken = _httpContext.Request.QueryString["access_token"]; + if (accessToken.IsEmpty()) { - model.Result = AmazonPayResultType.Unauthorized; + var msg = T("Plugins.Payments.AmazonPay.MissingAddressConsentToken"); + Logger.Error(null, msg); + _services.Notifier.Error(new LocalizedString(msg)); + + model.Result = AmazonPayResultType.Redirect; return model; } + // Create session state object. var checkoutState = _httpContext.GetCheckoutState(); - if (checkoutState == null) { - Logger.InsertLog(LogLevel.Warning, "Checkout state is null in AmazonPayService.ValidateAndInitiateCheckout!"); + Logger.Warn("Checkout state is null in AmazonPayService.ValidateAndInitiateCheckout!"); model.Result = AmazonPayResultType.Redirect; return model; } - var state = new AmazonPayCheckoutState() - { - OrderReferenceId = orderReferenceId - }; - - if (checkoutState.CustomProperties.ContainsKey(AmazonPayCore.AmazonPayCheckoutStateKey)) - checkoutState.CustomProperties[AmazonPayCore.AmazonPayCheckoutStateKey] = state; - else - checkoutState.CustomProperties.Add(AmazonPayCore.AmazonPayCheckoutStateKey, state); - - //_httpContext.Session.SafeSet(AmazonPayCore.AmazonPayCheckoutStateKey, state); + checkoutState.CustomProperties[AmazonPayPlugin.SystemName + ".CheckoutState"] = new AmazonPayCheckoutState { AccessToken = accessToken }; model.RedirectAction = "Index"; model.RedirectController = "Checkout"; model.Result = AmazonPayResultType.Redirect; return model; } + else if (type == AmazonPayRequestType.AuthenticationPublicInfo) + { + if (settings.SellerId.IsEmpty() || settings.AccessKey.IsEmpty() || settings.SecretKey.IsEmpty()) + { + return null; + } + + model.ButtonHandlerUrl = $"{storeLocation}Plugins/SmartStore.AmazonPay/AmazonPay/AuthenticationButtonHandler"; + + // Do not append returnUrl to button handler URL. Handler URLs must be whitelisted in Amazon Seller Central. + _httpContext.Session["AmazonAuthReturnUrl"] = _httpContext.Request.QueryString["returnUrl"]; + } else if (type == AmazonPayRequestType.ShoppingCart || type == AmazonPayRequestType.MiniShoppingCart) { - if (cart.Count <= 0 || !IsActive(store.Id)) + if (cart.Count <= 0 || !IsPaymentMethodActive(store.Id)) { model.Result = AmazonPayResultType.None; return model; } - - string storeLocation = _services.WebHelper.GetStoreLocation(store.SslEnabled); - model.LoginHandlerUrl = "{0}Plugins/SmartStore.AmazonPay/AmazonPayShoppingCart/LoginHandler".FormatWith(storeLocation); } else { - if (!_httpContext.HasAmazonPayState() || cart.Count <= 0) + if (cart.Count <= 0) { model.Result = AmazonPayResultType.Redirect; return model; @@ -443,32 +339,67 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa } var state = _httpContext.GetAmazonPayState(_services.Localization); - model.OrderReferenceId = state.OrderReferenceId; - //model.IsOrderConfirmed = state.IsOrderConfirmed; - } + model.AddressConsentToken = state.AccessToken; + model.IsConfirmed = state.IsConfirmed; + model.FormData = state.FormData; + model.SubmitForm = state.SubmitForm; - var currency = store.PrimaryStoreCurrency; - var settings = _services.Settings.LoadSetting(store.Id); + state.SubmitForm = false; + + if (type == AmazonPayRequestType.ShippingMethod || type == AmazonPayRequestType.PaymentMethod) + { + if (state.OrderReferenceId.IsEmpty() || state.AccessToken.IsEmpty()) + { + model.Result = AmazonPayResultType.Redirect; + return model; + } + } + } model.SellerId = settings.SellerId; - model.ClientId = settings.AccessKey; + model.ClientId = settings.ClientId; model.IsShippable = cart.RequiresShipping(); model.IsRecurring = cart.IsRecurring(); - model.WidgetUrl = settings.GetWidgetUrl(); - model.ButtonUrl = settings.GetButtonUrl(type); - model.AddressWidgetWidth = Math.Max(settings.AddressWidgetWidth, 200); - model.AddressWidgetHeight = Math.Max(settings.AddressWidgetHeight, 228); - model.PaymentWidgetWidth = Math.Max(settings.PaymentWidgetWidth, 200); - model.PaymentWidgetHeight = Math.Max(settings.PaymentWidgetHeight, 228); + model.WidgetUrl = GetWidgetUrl(settings); - if (type == AmazonPayRequestType.MiniShoppingCart) + if (type == AmazonPayRequestType.MiniShoppingCart || type == AmazonPayRequestType.ShoppingCart) { - if (!settings.ShowButtonInMiniShoppingCart) + if (type == AmazonPayRequestType.MiniShoppingCart && !settings.ShowButtonInMiniShoppingCart) + { + model.Result = AmazonPayResultType.None; + return model; + } + if (settings.ShowPayButtonForAdminOnly && !customer.IsAdmin()) { model.Result = AmazonPayResultType.None; return model; } + + // AmazonPay review: The setting for payment button type has been removed. + model.ButtonType = "PwA"; + model.ButtonColor = settings.PayButtonColor; + model.ButtonSize = settings.PayButtonSize; + + var failureReason = _httpContext.Session["AmazonPayFailedPaymentReason"] as string; + if (failureReason.IsCaseInsensitiveEqual("AmazonRejected")) + { + model.Logout = true; + _services.Notifier.Error(new LocalizedString(T("Plugins.Payments.AmazonPay.AuthorizationSoftDeclineMessage"))); + } + else if (failureReason.IsCaseInsensitiveEqual("AuthenticationStatusFailure")) + { + model.Logout = true; + _services.Notifier.Error(new LocalizedString(T("Plugins.Payments.AmazonPay.AuthenticationStatusFailureMessage"))); + } + + _httpContext.Session.SafeRemove("AmazonPayFailedPaymentReason"); + } + else if (type == AmazonPayRequestType.AuthenticationPublicInfo) + { + model.ButtonType = settings.AuthButtonType; + model.ButtonColor = settings.AuthButtonColor; + model.ButtonSize = settings.AuthButtonSize; } else if (type == AmazonPayRequestType.Address) { @@ -480,10 +411,12 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa return model; } - var shippingToCountryNotAllowed = tempData[AmazonPayCore.SystemName + "ShippingToCountryNotAllowed"]; + var shippingToCountryNotAllowed = tempData[AmazonPayPlugin.SystemName + "ShippingToCountryNotAllowed"]; if (shippingToCountryNotAllowed != null && true == (bool)shippingToCountryNotAllowed) + { model.Warning = T("Plugins.Payments.AmazonPay.ShippingToCountryNotAllowed"); + } } else if (type == AmazonPayRequestType.ShippingMethod) { @@ -491,51 +424,116 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa if (model.IsShippable) { - var client = new AmazonPayClient(settings); - var details = _api.GetOrderReferenceDetails(client, model.OrderReferenceId); - - if (_api.FindAndApplyAddress(details, customer, model.IsShippable, true)) + var client = CreateClient(settings); + var getOrderRequest = new GetOrderReferenceDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(model.OrderReferenceId) + .WithAccessToken(model.AddressConsentToken); + + var getOrderResponse = client.GetOrderReferenceDetails(getOrderRequest); + if (getOrderResponse.GetSuccess()) { + // Billing address not available here. getOrderResponse.GetBillingAddressDetails() is null. + //if (FindAndApplyAddress(getOrderResponse, customer, model.IsShippable, true)) + var countryAllowsShipping = true; + var countryAllowsBilling = true; + + var address = CreateAddress( + getOrderResponse.GetEmail(), + getOrderResponse.GetBuyerShippingName(), + getOrderResponse.GetAddressLine1(), + getOrderResponse.GetAddressLine2(), + getOrderResponse.GetAddressLine3(), + getOrderResponse.GetCity(), + getOrderResponse.GetPostalCode(), + getOrderResponse.GetPhone(), + getOrderResponse.GetCountryCode(), + getOrderResponse.GetStateOrRegion(), + getOrderResponse.GetCounty(), + getOrderResponse.GetDistrict(), + out countryAllowsShipping, + out countryAllowsBilling); + + if (model.IsShippable && !countryAllowsShipping) + { + tempData[AmazonPayPlugin.SystemName + "ShippingToCountryNotAllowed"] = true; + model.RedirectAction = "ShippingAddress"; + model.RedirectController = "Checkout"; + model.Result = AmazonPayResultType.Redirect; + return model; + } + + if (address.Email.IsEmpty()) + { + address.Email = customer.Email; + } + + var existingAddress = customer.Addresses.ToList().FindAddress(address, true); + if (existingAddress == null) + { + customer.Addresses.Add(address); + customer.ShippingAddress = model.IsShippable ? address : null; + } + else + { + customer.ShippingAddress = model.IsShippable ? existingAddress : null; + } + _customerService.UpdateCustomer(customer); model.Result = AmazonPayResultType.None; return model; } else { - tempData[AmazonPayCore.SystemName + "ShippingToCountryNotAllowed"] = true; - model.RedirectAction = "ShippingAddress"; - model.RedirectController = "Checkout"; - model.Result = AmazonPayResultType.Redirect; - return model; + LogError(getOrderRequest, getOrderResponse); } } } - else if (type == AmazonPayRequestType.Payment) + else if (type == AmazonPayRequestType.PaymentMethod) { - if (_rewardPointsSettings.Enabled && !model.IsRecurring) - { - int rewardPointsBalance = customer.GetRewardPointsBalance(); - decimal rewardPointsAmountBase = _orderTotalCalculationService.ConvertRewardPointsToAmount(rewardPointsBalance); - decimal rewardPointsAmount = _currencyService.ConvertFromPrimaryStoreCurrency(rewardPointsAmountBase, currency); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, AmazonPayPlugin.SystemName, store.Id); + + var paymentFailure = _httpContext.Session["AmazonPayFailedPaymentReason"] as string; - if (rewardPointsAmount > decimal.Zero) + if (paymentFailure.IsCaseInsensitiveEqual("InvalidPaymentMethod")) + { + _services.Notifier.Warning(new LocalizedString(T("Payment.PayingFailed"))); + } + else if (paymentFailure.IsCaseInsensitiveEqual("PaymentMethodNotAllowed")) + { + _services.Notifier.Warning(new LocalizedString(T("Plugins.Payments.AmazonPay.AuthorizationSoftDeclineMessage"))); + } + else if (paymentFailure.IsCaseInsensitiveEqual("AuthenticationStatusAbandoned")) + { + _services.Notifier.Warning(new LocalizedString(T("Plugins.Payments.AmazonPay.AuthenticationStatusAbandonedMessage"))); + } + else + { + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); + if (shoppingCartTotalBase.HasValue) { - model.DisplayRewardPoints = true; - model.RewardPointsAmount = _priceFormatter.FormatPrice(rewardPointsAmount, true, false); - model.RewardPointsBalance = rewardPointsBalance; + var client = CreateClient(settings); + var setOrderRequest = new SetOrderReferenceDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(model.OrderReferenceId) + .WithPlatformId(PlatformId) + .WithAmount(shoppingCartTotalBase.Value) + .WithCurrencyCode(ConvertCurrency(store.PrimaryStoreCurrency.CurrencyCode)) + .WithStoreName(store.Name); + + var setOrderResponse = client.SetOrderReferenceDetails(setOrderRequest); + if (!setOrderResponse.GetSuccess()) + { + LogError(setOrderRequest, setOrderResponse); + } } } - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, AmazonPayCore.SystemName, store.Id); - - var client = new AmazonPayClient(settings); - var unused = _api.SetOrderReferenceDetails(client, model.OrderReferenceId, store.PrimaryStoreCurrency.CurrencyCode, cart); - - // this is ugly... + // This is ugly... var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; if (paymentRequest == null) { - _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); + _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest { OrderGuid = Guid.NewGuid() }; } } else if (type == AmazonPayRequestType.OrderReviewData) @@ -543,104 +541,37 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa if (model.IsShippable) { var shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, store.Id); - if (shippingOption != null) - model.ShippingMethod = shippingOption.Name; + if (shippingOption != null) + { + model.ShippingMethod = shippingOption.Name; + } } - } - } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc, notify: true); - } - catch (Exception exc) - { - LogError(exc, notify: true); - } - return model; - } - public void ApplyRewardPoints(bool useRewardPoints) - { - if (_rewardPointsSettings.Enabled) - { - _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, useRewardPoints, _services.StoreContext.CurrentStore.Id); - } - } - - private string GetAuthorizationState(AmazonPayClient client, string authorizationId) - { - try - { - if (authorizationId.HasValue()) - { - AmazonPayApiData data; - - if (_api.GetAuthorizationDetails(client, authorizationId, out data) != null) - return data.State; + if (customer.BillingAddress != null) + { + model.BillingAddress.PrepareModel(customer.BillingAddress, false, _addressSettings); + } } } - catch (OffAmazonPaymentsServiceException exc) + catch (Exception ex) { - LogAmazonError(exc); + Logger.Error(ex); + _services.Notifier.Error(new LocalizedString(ex.Message)); } - catch (Exception exc) - { - LogError(exc); - } - return null; - } - - private void CloseOrderReference(AmazonPaySettings settings, Order order) - { - // You can still perform captures against any open authorizations, but you cannot create any new authorizations on the - // Order Reference object. You can still execute refunds against the Order Reference object. - - try - { - var client = new AmazonPayClient(settings); - var orderAttribute = DeserializeOrderAttribute(order); - - _api.CloseOrderReference(client, orderAttribute.OrderReferenceId); - } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc); - } - catch (Exception exc) - { - LogError(exc); - } + return model; } - private void ProcessAuthorizationResult(AmazonPayClient client, Order order, AmazonPayApiData data, OffAmazonPaymentsService.Model.AuthorizationDetails details) + private void ProcessAuthorizationResult(AmazonPaySettings settings, Order order, AmazonPayData data) { - string formattedAddress; var orderAttribute = DeserializeOrderAttribute(order); - if (!orderAttribute.IsBillingAddressApplied) - { - if (_api.FulfillBillingAddress(client.Settings, order, details, out formattedAddress)) - { - AddOrderNote(client.Settings, order, AmazonPayOrderNote.BillingAddressApplied, formattedAddress); - - orderAttribute.IsBillingAddressApplied = true; - SerializeOrderAttribute(orderAttribute, order); - } - else if (formattedAddress.HasValue()) - { - AddOrderNote(client.Settings, order, AmazonPayOrderNote.BillingAddressCountryNotAllowed, formattedAddress); - - orderAttribute.IsBillingAddressApplied = true; - SerializeOrderAttribute(orderAttribute, order); - } - } - if (data.State.IsCaseInsensitiveEqual("Pending")) + { return; + } - string newResult = data.State.Grow(data.ReasonCode, " "); + var newResult = data.State.Grow(data.ReasonCode, " "); if (_orderProcessingService.CanMarkOrderAsAuthorized(order)) { @@ -649,7 +580,8 @@ private void ProcessAuthorizationResult(AmazonPayClient client, Order order, Ama if (data.State.IsCaseInsensitiveEqual("Closed") && data.ReasonCode.IsCaseInsensitiveEqual("OrderReferenceCanceled") && _orderProcessingService.CanVoidOffline(order)) { - _orderProcessingService.VoidOffline(order); // cancelation at amazon seller central + // Cancelation at amazon seller central. + _orderProcessingService.VoidOffline(order); } else if (data.State.IsCaseInsensitiveEqual("Declined") && _orderProcessingService.CanVoidOffline(order)) { @@ -661,31 +593,47 @@ private void ProcessAuthorizationResult(AmazonPayClient client, Order order, Ama order.AuthorizationTransactionResult = newResult; if (order.CaptureTransactionId.IsEmpty() && data.CaptureId.HasValue()) - order.CaptureTransactionId = data.CaptureId; // captured at amazon seller central + { + // Captured at amazon seller central. + order.CaptureTransactionId = data.CaptureId; + } _orderService.UpdateOrder(order); - AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data), true); + AddOrderNote(settings, order, ToInfoString(data), true); } } - private void ProcessCaptureResult(AmazonPayClient client, Order order, AmazonPayApiData data) + + private void ProcessCaptureResult(Client client, AmazonPaySettings settings, Order order, AmazonPayData data) { if (data.State.IsCaseInsensitiveEqual("Pending")) + { return; + } - string newResult = data.State.Grow(data.ReasonCode, " "); + var newResult = data.State.Grow(data.ReasonCode, " "); if (data.State.IsCaseInsensitiveEqual("Completed") && _orderProcessingService.CanMarkOrderAsPaid(order)) { _orderProcessingService.MarkOrderAsPaid(order); - - CloseOrderReference(client.Settings, order); } else if (data.State.IsCaseInsensitiveEqual("Declined") && _orderProcessingService.CanVoidOffline(order)) { - if (!GetAuthorizationState(client, order.AuthorizationTransactionId).IsCaseInsensitiveEqual("Open")) + var authDetailsRequest = new GetAuthorizationDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonAuthorizationId(order.AuthorizationTransactionId); + + var authDetailsResponse = client.GetAuthorizationDetails(authDetailsRequest); + if (authDetailsResponse.GetSuccess()) + { + if (authDetailsResponse.GetAuthorizationState().IsCaseInsensitiveEqual("Open")) + { + _orderProcessingService.VoidOffline(order); + } + } + else { - _orderProcessingService.VoidOffline(order); + LogError(authDetailsRequest, authDetailsResponse); } } @@ -694,20 +642,24 @@ private void ProcessCaptureResult(AmazonPayClient client, Order order, AmazonPay order.CaptureTransactionResult = newResult; _orderService.UpdateOrder(order); - AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data), true); + AddOrderNote(settings, order, ToInfoString(data), true); } } - private void ProcessRefundResult(AmazonPayClient client, Order order, AmazonPayApiData data) + + private void ProcessRefundResult(Client client, AmazonPaySettings settings, Order order, AmazonPayData data) { if (data.State.IsCaseInsensitiveEqual("Pending")) + { return; + } - if (data.RefundedAmount != null && data.RefundedAmount.Amount != 0.0) // totally refunded amount + if (data.RefundedAmount != null && data.RefundedAmount.Amount != decimal.Zero) { - // we could only process it once cause otherwise order.RefundedAmount would getting wrong. + // Totally refunded amount. + // We could only process it once cause otherwise order.RefundedAmount would getting wrong. if (order.RefundedAmount == decimal.Zero) { - decimal refundAmount = Convert.ToDecimal(data.RefundedAmount.Amount); + decimal refundAmount = data.RefundedAmount.Amount; decimal receivable = order.OrderTotal - refundAmount; if (receivable <= decimal.Zero) @@ -716,8 +668,10 @@ private void ProcessRefundResult(AmazonPayClient client, Order order, AmazonPayA { _orderProcessingService.RefundOffline(order); - if (client.Settings.DataFetching == AmazonPayDataFetchingType.Polling) - AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data), true); + if (settings.DataFetching == AmazonPayDataFetchingType.Polling) + { + AddOrderNote(settings, order, ToInfoString(data), true); + } } } else @@ -726,15 +680,19 @@ private void ProcessRefundResult(AmazonPayClient client, Order order, AmazonPayA { _orderProcessingService.PartiallyRefundOffline(order, refundAmount); - if (client.Settings.DataFetching == AmazonPayDataFetchingType.Polling) - AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data), true); + if (settings.DataFetching == AmazonPayDataFetchingType.Polling) + { + AddOrderNote(settings, order, ToInfoString(data), true); + } } } } } - if (client.Settings.DataFetching == AmazonPayDataFetchingType.Ipn) - AddOrderNote(client.Settings, order, AmazonPayOrderNote.AmazonMessageProcessed, _api.ToInfoString(data), true); + if (settings.DataFetching == AmazonPayDataFetchingType.Ipn) + { + AddOrderNote(settings, order, ToInfoString(data), true); + } } private void PollingLoop(PollingLoopData data, Func poll) @@ -747,14 +705,14 @@ private void PollingLoop(PollingLoopData data, Func poll) for (int i = 0; i < 99 && (DateTime.Now.TimeOfDay.Milliseconds - startTime.Milliseconds) <= loopMillSec; ++i) { - // inside the loop cause other instances are also updating the order + // Inside the loop cause other instances are also updating the order. data.Order = _orderService.GetOrderById(data.OrderId); if (data.Settings == null) data.Settings = _services.Settings.LoadSetting(data.Order.StoreId); if (data.Client == null) - data.Client = new AmazonPayClient(data.Settings); + data.Client = CreateClient(data.Settings); if (!poll()) break; @@ -762,20 +720,14 @@ private void PollingLoop(PollingLoopData data, Func poll) Thread.Sleep(sleepMillSec); } } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc); - } - catch (Exception exc) + catch (Exception exception) { - LogError(exc); + Logger.Error(exception); } } private void EarlyPolling(int orderId, AmazonPaySettings settings) { - // the Authorization object moves to the Open state after remaining in the Pending state for 30 seconds. - - AmazonPayApiData data; + // The Authorization object moves to the Open state after remaining in the Pending state for 30 seconds. var d = new PollingLoopData(orderId); d.Settings = settings; @@ -784,13 +736,21 @@ private void EarlyPolling(int orderId, AmazonPaySettings settings) if (d.Order.AuthorizationTransactionId.IsEmpty()) return false; - var details = _api.GetAuthorizationDetails(d.Client, d.Order.AuthorizationTransactionId, out data); + var authDetailsRequest = new GetAuthorizationDetailsRequest() + .WithMerchantId(d.Settings.SellerId) + .WithAmazonAuthorizationId(d.Order.AuthorizationTransactionId); - if (!data.State.IsCaseInsensitiveEqual("pending")) + var authDetailsResponse = d.Client.GetAuthorizationDetails(authDetailsRequest); + if (!authDetailsResponse.GetSuccess()) + return false; + + var details = GetDetails(authDetailsResponse); + if (!details.State.IsCaseInsensitiveEqual("pending")) { - ProcessAuthorizationResult(d.Client, d.Order, data, details); + ProcessAuthorizationResult(d.Settings, d.Order, details); return false; } + return true; }); @@ -800,50 +760,46 @@ private void EarlyPolling(int orderId, AmazonPaySettings settings) if (d.Order.CaptureTransactionId.IsEmpty()) return false; - _api.GetCaptureDetails(d.Client, d.Order.CaptureTransactionId, out data); + var captureDetailsRequest = new GetCaptureDetailsRequest() + .WithMerchantId(d.Settings.SellerId) + .WithAmazonCaptureId(d.Order.CaptureTransactionId); - ProcessCaptureResult(d.Client, d.Order, data); + var captureDetailsResponse = d.Client.GetCaptureDetails(captureDetailsRequest); + if (!captureDetailsResponse.GetSuccess()) + return false; + + var details = GetDetails(captureDetailsResponse); + ProcessCaptureResult(d.Client, d.Settings, d.Order, details); - return data.State.IsCaseInsensitiveEqual("pending"); + return details.State.IsCaseInsensitiveEqual("pending"); }); } - private Order FindOrder(AmazonPayApiData data) + public void CloseOrderReference(AmazonPaySettings settings, Order order) { - Order order = null; - string errorId = null; - - if (data.MessageType.IsCaseInsensitiveEqual("AuthorizationNotification")) - { - if ((order = _orderService.GetOrderByPaymentAuthorization(AmazonPayCore.SystemName, data.AuthorizationId)) == null) - errorId = "AuthorizationId {0}".FormatWith(data.AuthorizationId); - } - else if (data.MessageType.IsCaseInsensitiveEqual("CaptureNotification")) - { - if ((order = _orderService.GetOrderByPaymentCapture(AmazonPayCore.SystemName, data.CaptureId)) == null) - order = _orderRepository.GetOrderByAmazonId(data.AnyAmazonId); - - if (order == null) - errorId = "CaptureId {0}".FormatWith(data.CaptureId); - } - else if (data.MessageType.IsCaseInsensitiveEqual("RefundNotification")) + // You can still perform captures against any open authorizations, but you cannot create any new authorizations on the + // Order Reference object. You can still execute refunds against the Order Reference object. + var orderAttribute = DeserializeOrderAttribute(order); + if (!orderAttribute.OrderReferenceClosed && orderAttribute.OrderReferenceId.HasValue()) { - var attribute = _genericAttributeService.GetAttributes(AmazonPayCore.AmazonPayRefundIdKey, "Order") - .Where(x => x.Value == data.RefundId) - .FirstOrDefault(); - - if (attribute == null || (order = _orderService.GetOrderById(attribute.EntityId)) == null) - order = _orderRepository.GetOrderByAmazonId(data.AnyAmazonId); + var client = CreateClient(settings); + var closeRequest = new CloseOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(orderAttribute.OrderReferenceId); - if (order == null) - errorId = "RefundId {0}".FormatWith(data.RefundId); + var closeResponse = client.CloseOrderReference(closeRequest); + if (closeResponse.GetSuccess()) + { + orderAttribute.OrderReferenceClosed = true; + SerializeOrderAttribute(orderAttribute, order); + } + else + { + LogError(closeRequest, closeResponse, true); + } } - - if (errorId.HasValue()) - Logger.InsertLog(LogLevel.Warning, T("Plugins.Payments.AmazonPay.OrderNotFound", errorId), ""); - - return order; } + public void AddCustomerOrderNoteLoop(AmazonPayActionState state) { try @@ -852,7 +808,7 @@ public void AddCustomerOrderNoteLoop(AmazonPayActionState state) var loopMillSec = 40000; var startTime = DateTime.Now.TimeOfDay; - for (int i = 0; i < 99 && (DateTime.Now.TimeOfDay.Milliseconds - startTime.Milliseconds) <= loopMillSec; ++i) + for (var i = 0; i < 99 && (DateTime.Now.TimeOfDay.Milliseconds - startTime.Milliseconds) <= loopMillSec; ++i) { var order = _orderService.GetOrderByGuid(state.OrderGuid); if (order != null) @@ -862,10 +818,12 @@ public void AddCustomerOrderNoteLoop(AmazonPayActionState state) if (state.Errors != null) { foreach (var error in state.Errors) + { sb.AppendFormat("

                                {0}

                                ", error); + } } - var orderNote = new OrderNote() + var orderNote = new OrderNote { DisplayToCustomer = true, Note = sb.ToString(), @@ -874,137 +832,372 @@ public void AddCustomerOrderNoteLoop(AmazonPayActionState state) order.OrderNotes.Add(orderNote); _orderService.UpdateOrder(order); - - _workflowMessageService.SendNewOrderNoteAddedCustomerNotification(orderNote, _services.WorkContext.WorkingLanguage.Id); + + _services.MessageFactory.SendNewOrderNoteAddedCustomerNotification(orderNote, _services.WorkContext.WorkingLanguage.Id); break; } Thread.Sleep(sleepMillSec); } } - catch (Exception exc) + catch (Exception ex) { - LogError(exc); + Logger.Error(ex); } } - public PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest request) + public void GetBillingAddress() { - // fulfill the Amazon checkout - var result = new PreProcessPaymentResult(); - - try - { - var orderGuid = request.OrderGuid.ToString(); - var store = _services.StoreService.GetStoreById(request.StoreId); - var customer = _customerService.GetCustomerById(request.CustomerId); - var currency = store.PrimaryStoreCurrency; - var settings = _services.Settings.LoadSetting(store.Id); - var state = _httpContext.GetAmazonPayState(_services.Localization); - var client = new AmazonPayClient(settings); - - if (!IsActive(store.Id, true)) + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + var settings = _services.Settings.LoadSetting(store.Id); + var state = _httpContext.GetAmazonPayState(_services.Localization); + var client = CreateClient(settings); + + var getOrderRequest = new GetOrderReferenceDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(state.OrderReferenceId) + .WithAccessToken(state.AccessToken); + + var getOrderResponse = client.GetOrderReferenceDetails(getOrderRequest); + if (getOrderResponse.GetSuccess()) + { + var details = getOrderResponse.GetBillingAddressDetails(); + if (details != null) { - //_httpContext.ResetCheckoutState(); - - result.AddError(T("Plugins.Payments.AmazonPay.PaymentMethodNotActive", store.Name)); - return result; - } - - var preConfirmDetails = _api.SetOrderReferenceDetails(client, state.OrderReferenceId, request.OrderTotal, currency.CurrencyCode, orderGuid, store.Name); - - _api.GetConstraints(preConfirmDetails, result.Errors); - - if (!result.Success) - return result; - - _api.ConfirmOrderReference(client, state.OrderReferenceId); - - // address and payment cannot be changed if order is in open state, amazon widgets then might show an error. - //state.IsOrderConfirmed = true; - - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); - var isShippable = cart.RequiresShipping(); - - // note: billing address is only available after authorization is in a non-pending and non-declined state. - var details = _api.GetOrderReferenceDetails(client, state.OrderReferenceId); + var countryAllowsShipping = true; + var countryAllowsBilling = true; + var email = getOrderResponse.GetEmail(); + + var address = CreateAddress( + email, + details.GetName(), + details.GetAddressLine1(), + details.GetAddressLine2(), + details.GetAddressLine3(), + details.GetCity(), + details.GetPostalCode(), + details.GetPhone(), + details.GetCountryCode(), + details.GetStateOrRegion(), + details.GetCounty(), + details.GetDistrict(), + out countryAllowsShipping, + out countryAllowsBilling); + + // We must ignore countryAllowsBilling because the customer cannot choose another billing address in Amazon checkout. + //if (!countryAllowsBilling) + // return; + + var existingAddress = customer.Addresses.ToList().FindAddress(address, true); + if (existingAddress == null) + { + customer.Addresses.Add(address); + customer.BillingAddress = address; + } + else + { + customer.BillingAddress = existingAddress; + } - _api.FindAndApplyAddress(details, customer, isShippable, false); + if (settings.CanSaveEmailAndPhone(customer.Email)) + { + customer.Email = email; + } + _customerService.UpdateCustomer(customer); - if (details.IsSetBuyer() && details.Buyer.IsSetEmail() && settings.CanSaveEmailAndPhone(customer.Email)) - { - customer.Email = details.Buyer.Email; + if (settings.CanSaveEmailAndPhone(customer.GetAttribute(SystemCustomerAttributeNames.Phone, store.Id))) + { + var phone = details.GetPhone(); + if (phone.IsEmpty()) + { + phone = getOrderResponse.GetPhone(); + } + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, phone); + } } - - _customerService.UpdateCustomer(customer); - - if (details.IsSetBuyer() && details.Buyer.IsSetPhone() && settings.CanSaveEmailAndPhone(customer.GetAttribute(SystemCustomerAttributeNames.Phone, store.Id))) + else { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, details.Buyer.Phone); + // No billing address at Amazon? We cannot proceed. } } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc, errors: result.Errors); - } - catch (Exception exc) + else { - LogError(exc, errors: result.Errors); + LogError(getOrderRequest, getOrderResponse); } - - return result; } + public bool SetOrderReferenceDetails( + Client client, + AmazonPaySettings settings, + AmazonPayCheckoutState state, + string sellerNote, + out string errorMessage) + { + errorMessage = null; + + if (client == null) + { + client = CreateClient(settings); + } + + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + decimal? cartTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart); + + var setOrderRequest = new SetOrderReferenceDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(state.OrderReferenceId) + .WithPlatformId(PlatformId) + .WithAmount(cartTotal) + .WithCurrencyCode(ConvertCurrency(store.PrimaryStoreCurrency.CurrencyCode)) + .WithStoreName(store.Name); + + var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; + if (paymentRequest != null) + { + setOrderRequest = setOrderRequest.WithSellerOrderId(paymentRequest.OrderGuid.ToString()); + } + + if (sellerNote != null) + { + setOrderRequest = setOrderRequest.WithSellerNote(sellerNote); + } + + var setOrderResponse = client.SetOrderReferenceDetails(setOrderRequest); + if (!setOrderResponse.GetSuccess()) + { + errorMessage = LogError(setOrderRequest, setOrderResponse); + return false; + } + + if (setOrderResponse.GetHasConstraint()) + { + var ids = setOrderResponse.GetConstraintIdList(); + var descriptions = setOrderResponse.GetDescriptionList(); + + foreach (var id in ids) + { + var idx = ids.IndexOf(id); + if (idx < descriptions.Count) + { + errorMessage = $"{descriptions[idx]} ({id})"; + Logger.Error(errorMessage); + } + + if (id.IsCaseInsensitiveEqual("PaymentMethodNotAllowed")) + { + // Must be redirected to checkout payment page. + return false; + } + } + } + + return true; + } + + public bool ConfirmOrderReference() + { + try + { + var store = _services.StoreContext.CurrentStore; + var settings = _services.Settings.LoadSetting(store.Id); + var state = _httpContext.GetAmazonPayState(_services.Localization); + var client = CreateClient(settings); + + // Only call SetOrderReferenceDetailsRequest if it's in draft state, otherwise it returns InvalidOrderReferenceStatus error. + if (!state.DetailsSet && !SetOrderReferenceDetails(client, settings, state, null, out var _)) + { + return false; + } + + state.DetailsSet = true; + + var confirmationResultUrl = _services.WebHelper.GetStoreLocation() + "Plugins/SmartStore.AmazonPay/AmazonPayCheckout/ConfirmationResult"; + + // Confirm order. This already generates the payment object at Amazon. + // Can be called multiple times, for example to move the order reference object from suspended to open state. + var confirmRequest = new ConfirmOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(state.OrderReferenceId) + .WithSuccessUrl(confirmationResultUrl); + + var confirmResponse = client.ConfirmOrderReference(confirmRequest); + if (confirmResponse.GetSuccess()) + { + state.IsConfirmed = true; + return true; + } + else + { + LogError(confirmRequest, confirmResponse); + return false; + } + } + catch (Exception ex) + { + Logger.Error(ex); + return false; + } + } + public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest request) { - // initiate Amazon payment. We do not add errors to request.Errors cause of asynchronous processing. var result = new ProcessPaymentResult(); - var errors = new List(); - bool informCustomerAboutErrors = false; - bool informCustomerAddErrors = false; + var orderNoteErrors = new List(); + var informCustomerAboutErrors = false; + var informCustomerAddErrors = false; + var isSynchronous = false; + string error = null; + + result.NewPaymentStatus = PaymentStatus.Pending; + + _httpContext.Session.SafeRemove("AmazonPayFailedPaymentReason"); + _httpContext.Session.SafeRemove("AmazonPayCheckoutCompletedNote"); try { - var orderGuid = request.OrderGuid.ToString(); var store = _services.StoreService.GetStoreById(request.StoreId); - var currency = store.PrimaryStoreCurrency; var settings = _services.Settings.LoadSetting(store.Id); + var captureNow = settings.TransactionType == AmazonPayTransactionType.AuthorizeAndCapture; var state = _httpContext.GetAmazonPayState(_services.Localization); - var client = new AmazonPayClient(settings); + var client = CreateClient(settings); + AuthorizeRequest authRequest = null; + AuthorizeResponse authResponse = null; informCustomerAboutErrors = settings.InformCustomerAboutErrors; informCustomerAddErrors = settings.InformCustomerAddErrors; - _api.Authorize(client, result, errors, state.OrderReferenceId, request.OrderTotal, currency.CurrencyCode, orderGuid); + // Authorize. + if (settings.AuthorizeMethod == AmazonPayAuthorizeMethod.Omnichronous) + { + // First try synchronously. + authResponse = AuthorizePayment(settings, state, store, request, client, true, out authRequest); + + if (authResponse.GetAuthorizationState().IsCaseInsensitiveEqual("Declined") && + authResponse.GetReasonCode().IsCaseInsensitiveEqual("TransactionTimedOut")) + { + // Second try asynchronously. + // Transaction is always in pending state after return. + authResponse = AuthorizePayment(settings, state, store, request, client, false, out authRequest); + } + else + { + isSynchronous = true; + } + } + else + { + isSynchronous = settings.AuthorizeMethod == AmazonPayAuthorizeMethod.Synchronous; + authResponse = AuthorizePayment(settings, state, store, request, client, isSynchronous, out authRequest); + } + + // Process authorization response. + if (authResponse.GetSuccess()) + { + var reason = authResponse.GetReasonCode(); + + result.AuthorizationTransactionId = authResponse.GetAuthorizationId(); + result.AuthorizationTransactionCode = authResponse.GetAuthorizationReferenceId(); + result.AuthorizationTransactionResult = authResponse.GetAuthorizationState(); + + if (captureNow) + { + var idList = authResponse.GetCaptureIdList(); + if (idList.Any()) + { + result.CaptureTransactionId = idList.First(); + } + } + + if (isSynchronous) + { + if (result.AuthorizationTransactionResult.IsCaseInsensitiveEqual("Open")) + { + result.NewPaymentStatus = PaymentStatus.Authorized; + } + else if (result.AuthorizationTransactionResult.IsCaseInsensitiveEqual("Closed")) + { + if (captureNow && reason.IsCaseInsensitiveEqual("MaxCapturesProcessed")) + { + result.NewPaymentStatus = PaymentStatus.Paid; + } + } + } + else + { + _httpContext.Session["AmazonPayCheckoutCompletedNote"] = T("Plugins.Payments.AmazonPay.AsyncPaymentAuthrizationNote").Text; + } + + if (reason.IsCaseInsensitiveEqual("InvalidPaymentMethod") || reason.IsCaseInsensitiveEqual("AmazonRejected") || + reason.IsCaseInsensitiveEqual("ProcessingFailure") || reason.IsCaseInsensitiveEqual("TransactionTimedOut") || + reason.IsCaseInsensitiveEqual("TransactionTimeout")) + { + error = authResponse.GetReasonDescription(); + error = error.HasValue() ? $"{reason}: {error}" : reason; + + if (reason.IsCaseInsensitiveEqual("AmazonRejected")) + { + // Must be logged out and redirected to shopping cart. + _httpContext.Session["AmazonPayFailedPaymentReason"] = reason; + _httpContext.Response.RedirectToRoute("ShoppingCart"); + } + else if (reason.IsCaseInsensitiveEqual("InvalidPaymentMethod")) + { + // Must be redirected to checkout payment page. + _httpContext.Session["AmazonPayFailedPaymentReason"] = reason; + + // Review: confirmation required to get order reference object from suspended to open state again. + state.IsConfirmed = false; + state.FormData = null; + + var urlHelper = new UrlHelper(_httpContext.Request.RequestContext); + _httpContext.Response.Redirect(urlHelper.Action("PaymentMethod", "Checkout", new { area = "" })); + } + } + } + else + { + error = LogError(authRequest, authResponse); + } } - catch (OffAmazonPaymentsServiceException exc) + catch (Exception ex) { - LogAmazonError(exc, errors: errors); + Logger.Error(ex); + error = ex.Message; } - catch (Exception exc) + + if (error.HasValue()) { - LogError(exc, errors: errors); + if (isSynchronous) + { + result.AddError(error); + } + else + { + orderNoteErrors.Add(error); + } } - if (informCustomerAboutErrors && errors != null && errors.Count > 0) + // Customer needs to be informed of an Amazon error here. Hooking OrderPlaced.CustomerNotification won't work + // cause of asynchronous processing. Solution: we add a customer order note that is also send as an email. + if (informCustomerAboutErrors && orderNoteErrors.Any()) { - // customer needs to be informed of an amazon error here. hooking OrderPlaced.CustomerNotification won't work - // cause of asynchronous processing. solution: we add a customer order note that is also send as an email. - - var state = new AmazonPayActionState() { OrderGuid = request.OrderGuid }; + var state = new AmazonPayActionState { OrderGuid = request.OrderGuid }; if (informCustomerAddErrors) { state.Errors = new List(); - state.Errors.AddRange(errors); + state.Errors.AddRange(orderNoteErrors); } - AsyncRunner.Run((container, ct, o) => + AsyncRunner.Run((c, ct, o) => { var obj = o as AmazonPayActionState; - container.Resolve().AddCustomerOrderNoteLoop(obj); + c.Resolve().AddCustomerOrderNoteLoop(obj); }, - state, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + state); } return result; @@ -1028,35 +1221,49 @@ public void PostProcessPayment(PostProcessPaymentRequest request) // settings, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); // } //} - //catch (OffAmazonPaymentsServiceException exc) - //{ - // LogAmazonError(exc); - //} //catch (Exception exc) //{ - // LogError(exc); + // Logger.Error(exc); //} try { var state = _httpContext.GetAmazonPayState(_services.Localization); - - var orderAttribute = new AmazonPayOrderAttribute() + var orderAttribute = new AmazonPayOrderAttribute { OrderReferenceId = state.OrderReferenceId }; + if (request.Order.PaymentStatus == PaymentStatus.Paid) + { + var settings = _services.Settings.LoadSetting(request.Order.StoreId); + var client = CreateClient(settings); + var closeRequest = new CloseOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(orderAttribute.OrderReferenceId); + + var closeResponse = client.CloseOrderReference(closeRequest); + if (closeResponse.GetSuccess()) + { + orderAttribute.OrderReferenceClosed = true; + } + else + { + LogError(closeRequest, closeResponse, true); + } + } + SerializeOrderAttribute(orderAttribute, request.Order); } - catch (Exception exc) + catch (Exception exception) { - LogError(exc); + Logger.Error(exception); } } public CapturePaymentResult Capture(CapturePaymentRequest request) { - var result = new CapturePaymentResult() + var result = new CapturePaymentResult { NewPaymentStatus = request.Order.PaymentStatus }; @@ -1064,24 +1271,47 @@ public CapturePaymentResult Capture(CapturePaymentRequest request) try { var settings = _services.Settings.LoadSetting(request.Order.StoreId); - var client = new AmazonPayClient(settings); + var store = _services.StoreService.GetStoreById(request.Order.StoreId); + var client = CreateClient(settings); + + var captureRequest = new CaptureRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonAuthorizationId(request.Order.AuthorizationTransactionId) + .WithCaptureReferenceId(GetRandomId("Capture")) + .WithCurrencyCode(ConvertCurrency(store.PrimaryStoreCurrency.CurrencyCode)) + .WithAmount(request.Order.OrderTotal); + + var captureResponse = client.Capture(captureRequest); + if (captureResponse.GetSuccess()) + { + var state = captureResponse.GetCaptureState(); - _api.Capture(client, request, result); - } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc, errors: result.Errors); + result.CaptureTransactionId = captureResponse.GetCaptureId(); + result.CaptureTransactionResult = state.Grow(captureResponse.GetReasonCode(), " "); + + if (state.IsCaseInsensitiveEqual("completed")) + { + result.NewPaymentStatus = PaymentStatus.Paid; + } + } + else + { + var message = LogError(captureRequest, captureResponse); + result.AddError(message); + } } - catch (Exception exc) + catch (Exception exception) { - LogError(exc, errors: result.Errors); + Logger.Error(exception); + result.AddError(exception.Message); } + return result; } public RefundPaymentResult Refund(RefundPaymentRequest request) { - var result = new RefundPaymentResult() + var result = new RefundPaymentResult { NewPaymentStatus = request.Order.PaymentStatus }; @@ -1089,36 +1319,52 @@ public RefundPaymentResult Refund(RefundPaymentRequest request) try { var settings = _services.Settings.LoadSetting(request.Order.StoreId); - var client = new AmazonPayClient(settings); - - string amazonRefundId = _api.Refund(client, request, result); - - if (amazonRefundId.HasValue() && request.Order.Id != 0) + var store = _services.StoreService.GetStoreById(request.Order.StoreId); + var client = CreateClient(settings); + + var refundRequest = new RefundRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonCaptureId(request.Order.CaptureTransactionId) + .WithRefundReferenceId(GetRandomId("Refund")) + .WithCurrencyCode(ConvertCurrency(store.PrimaryStoreCurrency.CurrencyCode)) + .WithAmount(request.AmountToRefund); + + var refundResponse = client.Refund(refundRequest); + if (refundResponse.GetSuccess()) { - _genericAttributeService.InsertAttribute(new GenericAttribute() + result.NewPaymentStatus = request.IsPartialRefund ? PaymentStatus.PartiallyRefunded : PaymentStatus.Refunded; + + var refundId = refundResponse.GetAmazonRefundId(); + if (refundId.HasValue() && request.Order.Id != 0) { - EntityId = request.Order.Id, - KeyGroup = "Order", - Key = AmazonPayCore.AmazonPayRefundIdKey, - Value = amazonRefundId, - StoreId = request.Order.StoreId - }); + _genericAttributeService.InsertAttribute(new GenericAttribute + { + EntityId = request.Order.Id, + KeyGroup = "Order", + Key = AmazonPayPlugin.SystemName + ".RefundId", + Value = refundId, + StoreId = request.Order.StoreId + }); + } + } + else + { + var message = LogError(refundRequest, refundResponse); + result.AddError(message); } } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc, errors: result.Errors); - } - catch (Exception exc) + catch (Exception exception) { - LogError(exc, errors: result.Errors); + Logger.Error(exception); + result.AddError(exception.Message); } + return result; } public VoidPaymentResult Void(VoidPaymentRequest request) { - var result = new VoidPaymentResult() + var result = new VoidPaymentResult { NewPaymentStatus = request.Order.PaymentStatus }; @@ -1144,99 +1390,193 @@ public VoidPaymentResult Void(VoidPaymentRequest request) if (request.Order.PaymentStatus == PaymentStatus.Pending || request.Order.PaymentStatus == PaymentStatus.Authorized) { var settings = _services.Settings.LoadSetting(request.Order.StoreId); - var client = new AmazonPayClient(settings); - var orderAttribute = DeserializeOrderAttribute(request.Order); + var client = CreateClient(settings); - _api.CancelOrderReference(client, orderAttribute.OrderReferenceId); + var cancelRequest = new CancelOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(orderAttribute.OrderReferenceId); + + var cancelResponse = client.CancelOrderReference(cancelRequest); + if (cancelResponse.GetSuccess()) + { + result.NewPaymentStatus = PaymentStatus.Voided; + } + else + { + var message = LogError(cancelRequest, cancelResponse); + result.AddError(message); + } } } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc, errors: result.Errors); - } - catch (Exception exc) + catch (Exception exception) { - LogError(exc, errors: result.Errors); + Logger.Error(exception); + result.AddError(exception.Message); } + return result; } public void ProcessIpn(HttpRequestBase request) { + string json = null; try { - var data = _api.ParseNotification(request); - var order = FindOrder(data); - - if (order == null || !IsActive(order.StoreId)) - return; + using (var reader = new StreamReader(request.InputStream)) + { + json = reader.ReadToEnd(); + } - var client = new AmazonPayClient(_services.Settings.LoadSetting(order.StoreId)); + var parser = new IpnHandler(request.Headers, json); + var type = parser.GetNotificationType(); + AmazonPayData data = null; + Order order = null; + string errorId = null; + var isAuthorize = false; + var isCapture = false; + var isRefund = false; - if (client.Settings.DataFetching != AmazonPayDataFetchingType.Ipn) + if (type.IsCaseInsensitiveEqual("PaymentAuthorize")) + { + isAuthorize = true; + var response = parser.GetAuthorizeResponse(); + data = GetDetails(response); + } + else if (type.IsCaseInsensitiveEqual("PaymentCapture")) + { + isCapture = true; + var response = parser.GetCaptureResponse(); + data = GetDetails(response); + } + else if (type.IsCaseInsensitiveEqual("PaymentRefund")) + { + isRefund = true; + var response = parser.GetRefundResponse(); + data = GetDetails(response); + } + else + { + // Ignore, e.g. OrderReferenceNotification. return; + } - if (data.MessageType.IsCaseInsensitiveEqual("AuthorizationNotification")) + if (data == null) { - ProcessAuthorizationResult(client, order, data, null); + Logger.Error($"No IPN details for notification type {type}"); return; } - else if (data.MessageType.IsCaseInsensitiveEqual("CaptureNotification")) + + data.MessageType = type; + data.MessageId = parser.GetNotificationReferenceId(); + + // Find order. + if (isAuthorize) + { + if ((order = _orderService.GetOrderByPaymentAuthorization(AmazonPayPlugin.SystemName, data.AuthorizationId)) == null) + errorId = $"AuthorizationId {data.AuthorizationId.NaIfEmpty()}"; + } + else if (isCapture) + { + if ((order = _orderService.GetOrderByPaymentCapture(AmazonPayPlugin.SystemName, data.CaptureId)) == null) + order = _orderRepository.GetOrderByAmazonId(data.AnyAmazonId); + + if (order == null) + errorId = $"CaptureId {data.CaptureId.NaIfEmpty()}"; + } + else if (isRefund) + { + var attribute = _genericAttributeService.GetAttributes(AmazonPayPlugin.SystemName + ".RefundId", "Order") + .Where(x => x.Value == data.RefundId) + .FirstOrDefault(); + + if (attribute == null || (order = _orderService.GetOrderById(attribute.EntityId)) == null) + order = _orderRepository.GetOrderByAmazonId(data.AnyAmazonId); + + if (order == null) + errorId = $"RefundId {data.RefundId.NaIfEmpty()}"; + } + + if (errorId.HasValue()) + { + Logger.Warn(T("Plugins.Payments.AmazonPay.OrderNotFound", errorId)); + } + + if (order == null || !IsPaymentMethodActive(order.StoreId)) { - ProcessCaptureResult(client, order, data); return; } - else if (data.MessageType.IsCaseInsensitiveEqual("RefundNotification")) + + var settings = _services.Settings.LoadSetting(order.StoreId); + if (settings.DataFetching != AmazonPayDataFetchingType.Ipn) { - ProcessRefundResult(client, order, data); return; } + + if (isAuthorize) + { + ProcessAuthorizationResult(settings, order, data); + } + else if (isCapture) + { + var client = CreateClient(settings); + ProcessCaptureResult(client, settings, order, data); + } + else if (isRefund) + { + var client = CreateClient(settings); + ProcessRefundResult(client, settings, order, data); + } } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc); - } - catch (Exception exc) + catch (Exception exception) { - LogError(exc); + Logger.Error(exception, json); } } - public void DataPollingTaskProcess() + public void StartDataPolling() { try { - // ignore cancelled and completed (paid and shipped) orders. ignore old orders too. - - var data = new AmazonPayApiData(); - int pollingMaxOrderCreationDays = _services.Settings.GetSettingByKey("AmazonPaySettings.PollingMaxOrderCreationDays", 31); + // Ignore cancelled and completed (paid and shipped) orders. ignore old orders too. + var pollingMaxOrderCreationDays = _services.Settings.GetSettingByKey("AmazonPaySettings.PollingMaxOrderCreationDays", 31); var isTooOld = DateTime.UtcNow.AddDays(-(pollingMaxOrderCreationDays)); var query = from x in _orderRepository.Table - where x.PaymentMethodSystemName == AmazonPayCore.SystemName && x.CreatedOnUtc > isTooOld && + where x.PaymentMethodSystemName == AmazonPayPlugin.SystemName && x.CreatedOnUtc > isTooOld && !x.Deleted && x.OrderStatusId < (int)OrderStatus.Complete && x.PaymentStatusId != (int)PaymentStatus.Voided orderby x.Id descending select x; var orders = query.ToList(); - //"- start polling {0} orders".FormatWith(orders.Count).Dump(); foreach (var order in orders) { try { - var client = new AmazonPayClient(_services.Settings.LoadSetting(order.StoreId)); - - if (client.Settings.DataFetching == AmazonPayDataFetchingType.Polling) + var settings = _services.Settings.LoadSetting(order.StoreId); + if (settings.DataFetching == AmazonPayDataFetchingType.Polling) { + var client = CreateClient(settings); + if (order.AuthorizationTransactionId.HasValue()) { - var details = _api.GetAuthorizationDetails(client, order.AuthorizationTransactionId, out data); + var authDetailsRequest = new GetAuthorizationDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonAuthorizationId(order.AuthorizationTransactionId); - ProcessAuthorizationResult(client, order, data, details); + var authDetailsResponse = client.GetAuthorizationDetails(authDetailsRequest); + if (authDetailsResponse.GetSuccess()) + { + var details = GetDetails(authDetailsResponse); + ProcessAuthorizationResult(settings, order, details); + } + else + { + LogError(authDetailsRequest, authDetailsResponse); + } } if (order.CaptureTransactionId.HasValue()) @@ -1244,65 +1584,143 @@ orderby x.Id descending if (_orderProcessingService.CanMarkOrderAsPaid(order) || _orderProcessingService.CanVoidOffline(order) || _orderProcessingService.CanRefundOffline(order) || _orderProcessingService.CanPartiallyRefundOffline(order, 0.01M)) { - var details = _api.GetCaptureDetails(client, order.CaptureTransactionId, out data); - - ProcessCaptureResult(client, order, data); + var captureDetailsRequest = new GetCaptureDetailsRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonCaptureId(order.CaptureTransactionId); - if (_orderProcessingService.CanRefundOffline(order) || _orderProcessingService.CanPartiallyRefundOffline(order, 0.01M)) + var captureDetailsResponse = client.GetCaptureDetails(captureDetailsRequest); + if (captureDetailsResponse.GetSuccess()) { - // note status polling: we cannot use GetRefundDetails to reflect refund(s) made at Amazon seller central cause we - // do not have any refund-id and there is no api endpoint that serves them. so we only can process CaptureDetails.RefundedAmount. - - ProcessRefundResult(client, order, data); + var details = GetDetails(captureDetailsResponse); + ProcessCaptureResult(client, settings, order, details); + + if (_orderProcessingService.CanRefundOffline(order) || _orderProcessingService.CanPartiallyRefundOffline(order, 0.01M)) + { + // Note status polling: we cannot use GetRefundDetails to reflect refund(s) made at Amazon seller central cause we + // do not have any refund-id and there is no api endpoint that provide them. So we only can process CaptureDetails.RefundedAmount. + ProcessRefundResult(client, settings, order, details); + } + } + else + { + LogError(captureDetailsRequest, captureDetailsResponse); } } } } } - catch (OffAmazonPaymentsServiceException exc) - { - LogAmazonError(exc); - } - catch (Exception exc) + catch (Exception exception) { - LogError(exc); + Logger.Error(exception); } } } - catch (OffAmazonPaymentsServiceException exc) + catch (Exception exception) { - LogAmazonError(exc); - } - catch (Exception exc) - { - LogError(exc); + Logger.Error(exception); } } - public void DataPollingTaskInit() + public void ShareKeys(string payload, int storeId) { - _scheduleTaskService.GetOrAddTask(x => + if (payload.IsEmpty()) { - x.Name = "{0} data polling".FormatWith(AmazonPayCore.SystemName); - x.CronExpression = "*/30 * * * *"; // Every 30 minutes - }); - } + throw new SmartException(T("Plugins.Payments.AmazonPay.MissingPayloadParameter")); + } - public void DataPollingTaskUpdate(bool enabled, int seconds) - { - var task = _scheduleTaskService.GetTaskByType(); - if (task != null) + dynamic json = JObject.Parse(payload); + var settings = _services.Settings.LoadSetting(storeId); + + var encryptedPayload = (string)json.encryptedPayload; + if (encryptedPayload.HasValue()) + { + throw new SmartException(T("Plugins.Payments.AmazonPay.EncryptionNotSupported")); + } + else { - task.Enabled = enabled; - //task.Seconds = seconds; + settings.SellerId = (string)json.merchant_id; + settings.AccessKey = (string)json.access_key; + settings.SecretKey = (string)json.secret_key; + settings.ClientId = (string)json.client_id; + //settings.ClientSecret = (string)json.client_secret; + } - _scheduleTaskService.UpdateTask(task); + using (_services.Settings.BeginScope()) + { + _services.Settings.SaveSetting(settings, x => x.SellerId, storeId, false); + _services.Settings.SaveSetting(settings, x => x.AccessKey, storeId, false); + _services.Settings.SaveSetting(settings, x => x.SecretKey, storeId, false); + _services.Settings.SaveSetting(settings, x => x.ClientId, storeId, false); } } - public void DataPollingTaskDelete() + #region IExternalProviderAuthorizer + + public AuthorizeState Authorize(string returnUrl, bool? verifyResponse = null) { - _scheduleTaskService.TryDeleteTask(); + string error = null; + string email = null; + string name = null; + string userId = null; + var accessToken = _httpContext.Request.QueryString["access_token"]; + + if (accessToken.HasValue()) + { + var settings = _services.Settings.LoadSetting(); + var client = CreateClient(settings); + var jsonString = client.GetUserInfo(accessToken); + if (jsonString.HasValue()) + { + var json = JObject.Parse(jsonString); + + email = json.GetValue("email").ToString(); + name = json.GetValue("name").ToString(); + userId = json.GetValue("user_id").ToString(); + + if (email.IsEmpty() || name.IsEmpty() || userId.IsEmpty()) + { + error = T("Plugins.Payments.AmazonPay.IncompleteProfileDetails") + + $" Email: {email.NaIfEmpty()}, name: {name.NaIfEmpty()}, userId: {userId.NaIfEmpty()}."; + } + } + else + { + error = T("Plugins.Payments.AmazonPay.IncompleteProfileDetails"); + } + } + else + { + error = T("Plugins.Payments.AmazonPay.MissingAccessToken"); + } + + if (error.HasValue()) + { + var state = new AuthorizeState("", OpenAuthenticationStatus.Error); + state.AddError(error); + Logger.Error(error); + return state; + } + + string firstName, lastName; + name.ToFirstAndLastName(out firstName, out lastName); + + var claims = new UserClaims(); + claims.Name = new NameClaims(); + claims.Contact = new ContactClaims(); + claims.Contact.Email = email; + claims.Name.FullName = name; + claims.Name.First = firstName; + claims.Name.Last = lastName; + + var parameters = new AmazonAuthenticationParameters(); + parameters.ExternalIdentifier = userId; + parameters.AddClaim(claims); + + var result = _authorizer.Value.Authorize(parameters); + + return new AuthorizeState(returnUrl, result); } + + #endregion } } diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs new file mode 100644 index 0000000000..baf21a850a --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs @@ -0,0 +1,602 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Serialization; +using AmazonPay; +using AmazonPay.CommonRequests; +using AmazonPay.Responses; +using AmazonPay.StandardPaymentRequests; +using SmartStore.AmazonPay.Services.Internal; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Logging; +using SmartStore.Services.Common; +using SmartStore.Services.Payments; +using SmartStore.Utilities; + +namespace SmartStore.AmazonPay.Services +{ + /// + /// Helper with utilities to keep the AmazonPayService tidy. + /// + public partial class AmazonPayService + { + /// + /// Also named "spId". + /// + internal static string PlatformId => "A3OJ83WFYM72IY"; + + internal static string LeadCode => "SPEXDEAPA-SmartStore.Net-CP-DP"; + + internal static string GetWidgetUrl(AmazonPaySettings settings) + { + switch (settings.Marketplace.EmptyNull().ToLower()) + { + case "us": + return settings.UseSandbox + ? "https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js" + : "https://static-na.payments-amazon.com/OffAmazonPayments/us/js/Widgets.js"; + case "uk": + return settings.UseSandbox + ? "https://static-eu.payments-amazon.com/OffAmazonPayments/gbp/sandbox/lpa/js/Widgets.js" + : "https://static-eu.payments-amazon.com/OffAmazonPayments/gbp/lpa/js/Widgets.js"; + case "jp": + return settings.UseSandbox + ? "https://static-fe.payments-amazon.com/OffAmazonPayments/jp/sandbox/lpa/js/Widgets.js" + : "https://static-fe.payments-amazon.com/OffAmazonPayments/jp/lpa/js/Widgets.js"; + default: + return settings.UseSandbox + ? "https://static-eu.payments-amazon.com/OffAmazonPayments/eur/sandbox/lpa/js/Widgets.js" + : "https://static-eu.payments-amazon.com/OffAmazonPayments/eur/lpa/js/Widgets.js"; + } + } + + private string GetPluginUrl(string action, bool useSsl = false) + { + var pluginUrl = "{0}Plugins/SmartStore.AmazonPay/AmazonPay/{1}".FormatInvariant(_services.WebHelper.GetStoreLocation(useSsl), action); + return pluginUrl; + } + + private void SerializeOrderAttribute(AmazonPayOrderAttribute attribute, Order order) + { + if (attribute != null) + { + var sb = new StringBuilder(); + using (var writer = new StringWriter(sb)) + { + var serializer = new XmlSerializer(typeof(AmazonPayOrderAttribute)); + serializer.Serialize(writer, attribute); + + _genericAttributeService.SaveAttribute(order, AmazonPayPlugin.SystemName + ".OrderAttribute", sb.ToString(), order.StoreId); + } + } + } + + private AmazonPayOrderAttribute DeserializeOrderAttribute(Order order) + { + var serialized = order.GetAttribute(AmazonPayPlugin.SystemName + ".OrderAttribute", _genericAttributeService, order.StoreId); + + if (!serialized.HasValue()) + { + var attribute = new AmazonPayOrderAttribute(); + + // legacy < v.1.14 + attribute.OrderReferenceId = order.GetAttribute(AmazonPayPlugin.SystemName + ".OrderReferenceId", order.StoreId); + + return attribute; + } + + using (var reader = new StringReader(serialized)) + { + var serializer = new XmlSerializer(typeof(AmazonPayOrderAttribute)); + return (AmazonPayOrderAttribute)serializer.Deserialize(reader); + } + } + + private bool IsPaymentMethodActive(int storeId, bool logInactive = false) + { + var isActive = _paymentService.IsPaymentMethodActive(AmazonPayPlugin.SystemName, storeId); + + if (!isActive && logInactive) + { + Logger.Error(null, T("Plugins.Payments.AmazonPay.PaymentMethodNotActive", _services.StoreContext.CurrentStore.Name)); + } + + return isActive; + } + + private void AddOrderNote(AmazonPaySettings settings, Order order, string anyString = null, bool isIpn = false) + { + try + { + if (!settings.AddOrderNotes || order == null || anyString.IsEmpty()) + return; + + var sb = new StringBuilder(); + var faviconUrl = "{0}Plugins/{1}/Content/images/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(), AmazonPayPlugin.SystemName); + + sb.AppendFormat("", faviconUrl); + sb.Append(T("Plugins.Payments.AmazonPay.AmazonDataProcessed")); + sb.Append(":
                                "); + sb.Append(anyString); + + if (isIpn) + { + order.HasNewPaymentNotification = true; + } + + order.OrderNotes.Add(new OrderNote + { + Note = sb.ToString(), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + + _orderService.UpdateOrder(order); + } + catch (Exception exception) + { + Logger.Error(exception); + } + } + + private Regions.currencyCode ConvertCurrency(string currencyCode) + { + switch (currencyCode.EmptyNull().ToLower()) + { + case "usd": + return Regions.currencyCode.USD; + case "gbp": + return Regions.currencyCode.GBP; + case "jpy": + return Regions.currencyCode.JPY; + case "aud": + return Regions.currencyCode.AUD; + case "zar": + return Regions.currencyCode.ZAR; + case "chf": + return Regions.currencyCode.CHF; + case "nok": + return Regions.currencyCode.NOK; + case "dkk": + return Regions.currencyCode.DKK; + case "sek": + return Regions.currencyCode.SEK; + case "nzd": + return Regions.currencyCode.NZD; + case "hkd": + return Regions.currencyCode.HKD; + default: + return Regions.currencyCode.EUR; + } + } + + private Address CreateAddress( + string email, + string buyerName, + string addressLine1, + string addressLine2, + string addressLine3, + string city, + string postalCode, + string phone, + string countryCode, + string stateRegion, + string county, + string destrict, + out bool countryAllowsShipping, + out bool countryAllowsBilling) + { + countryAllowsShipping = countryAllowsBilling = true; + + var address = new Address(); + address.CreatedOnUtc = DateTime.UtcNow; + address.Email = email; + address.ToFirstAndLastName(buyerName); + address.Address1 = addressLine1.EmptyNull().Trim().Truncate(4000); + address.Address2 = addressLine2.EmptyNull().Trim().Truncate(4000); + address.Address2 = address.Address2.Grow(addressLine3.EmptyNull().Trim(), ", ").Truncate(4000); + address.City = county.Grow(destrict, " ").Grow(city, " ").EmptyNull().Trim().Truncate(4000); + address.ZipPostalCode = postalCode.EmptyNull().Trim().Truncate(4000); + address.PhoneNumber = phone.EmptyNull().Trim().Truncate(4000); + + if (countryCode.HasValue()) + { + var country = _countryService.GetCountryByTwoOrThreeLetterIsoCode(countryCode); + if (country != null) + { + address.CountryId = country.Id; + countryAllowsShipping = country.AllowsShipping; + countryAllowsBilling = country.AllowsBilling; + } + } + + if (stateRegion.HasValue()) + { + var stateProvince = _stateProvinceService.GetStateProvinceByAbbreviation(stateRegion); + if (stateProvince != null) + { + address.StateProvinceId = stateProvince.Id; + } + } + + // Normalize. + if (address.Address1.IsEmpty() && address.Address2.HasValue()) + { + address.Address1 = address.Address2; + address.Address2 = null; + } + else if (address.Address1.HasValue() && address.Address1 == address.Address2) + { + address.Address2 = null; + } + + if (address.CountryId == 0) + { + address.CountryId = null; + } + + if (address.StateProvinceId == 0) + { + address.StateProvinceId = null; + } + + return address; + } + + //private void GetAddress(OrderReferenceDetailsResponse details, Address address, out bool countryAllowsShipping, out bool countryAllowsBilling) + //{ + // countryAllowsShipping = countryAllowsBilling = true; + + // address.Email = details.GetEmail(); + // address.ToFirstAndLastName(details.GetBuyerName()); + // address.Address1 = details.GetAddressLine1().EmptyNull().Trim().Truncate(4000); + // address.Address2 = details.GetAddressLine2().EmptyNull().Trim().Truncate(4000); + // address.Address2 = address.Address2.Grow(details.GetAddressLine3().EmptyNull().Trim(), ", ").Truncate(4000); + // address.City = details.GetCity().EmptyNull().Trim().Truncate(4000); + // address.ZipPostalCode = details.GetPostalCode().EmptyNull().Trim().Truncate(4000); + // address.PhoneNumber = details.GetPhone().EmptyNull().Trim().Truncate(4000); + + // var countryCode = details.GetCountryCode(); + // if (countryCode.HasValue()) + // { + // var country = _countryService.GetCountryByTwoOrThreeLetterIsoCode(countryCode); + // if (country != null) + // { + // address.CountryId = country.Id; + // countryAllowsShipping = country.AllowsShipping; + // countryAllowsBilling = country.AllowsBilling; + // } + // } + + // var stateRegion = details.GetStateOrRegion(); + // if (stateRegion.HasValue()) + // { + // var stateProvince = _stateProvinceService.GetStateProvinceByAbbreviation(stateRegion); + // if (stateProvince != null) + // { + // address.StateProvinceId = stateProvince.Id; + // } + // } + + // // Normalize. + // if (address.Address1.IsEmpty() && address.Address2.HasValue()) + // { + // address.Address1 = address.Address2; + // address.Address2 = null; + // } + // else if (address.Address1.HasValue() && address.Address1 == address.Address2) + // { + // address.Address2 = null; + // } + + // if (address.CountryId == 0) + // { + // address.CountryId = null; + // } + + // if (address.StateProvinceId == 0) + // { + // address.StateProvinceId = null; + // } + //} + + //private bool FindAndApplyAddress(OrderReferenceDetailsResponse details, Customer customer, bool isShippable, bool forceToTakeAmazonAddress) + //{ + // // PlaceOrder requires billing address but we don't get one from Amazon here. so use shipping address instead until we get it from amazon. + // var countryAllowsShipping = true; + // var countryAllowsBilling = true; + + // var address = new Address(); + // address.CreatedOnUtc = DateTime.UtcNow; + + // GetAddress(details, address, out countryAllowsShipping, out countryAllowsBilling); + + // if (isShippable && !countryAllowsShipping) + // return false; + + // if (address.Email.IsEmpty()) + // { + // address.Email = customer.Email; + // } + + // if (forceToTakeAmazonAddress) + // { + // // First time to get in touch with an amazon address. + // var existingAddress = customer.Addresses.ToList().FindAddress(address, true); + // if (existingAddress == null) + // { + // customer.Addresses.Add(address); + // customer.BillingAddress = address; + // } + // else + // { + // customer.BillingAddress = existingAddress; + // } + // } + // else + // { + // if (customer.BillingAddress == null) + // { + // customer.Addresses.Add(address); + // customer.BillingAddress = address; + // } + + // GetAddress(details, customer.BillingAddress, out countryAllowsShipping, out countryAllowsBilling); + + // // But now we could have dublicates. + // var newAddressId = customer.BillingAddress.Id; + // var addresses = customer.Addresses.Where(x => x.Id != newAddressId).ToList(); + + // var existingAddress = addresses.FindAddress(customer.BillingAddress, false); + // if (existingAddress != null) + // { + // // Remove the new and take the old one. + // customer.RemoveAddress(customer.BillingAddress); + // customer.BillingAddress = existingAddress; + + // try + // { + // _addressService.DeleteAddress(newAddressId); + // } + // catch (Exception exception) + // { + // exception.Dump(); + // } + // } + // } + + // customer.ShippingAddress = (isShippable ? customer.BillingAddress : null); + + // return true; + //} + + private AmazonPayData GetDetails(AuthorizeResponse response) + { + var data = new AmazonPayData(); + data.MessageType = "GetAuthorizationDetails"; + data.MessageId = response.GetRequestId(); + data.AuthorizationId = response.GetAuthorizationId(); + data.ReferenceId = response.GetAuthorizationReferenceId(); + + var ids = response.GetCaptureIdList(); + if (ids.Any()) + { + data.CaptureId = ids.First(); + } + + data.Fee = new AmazonPayPrice(response.GetAuthorizationFee(), response.GetAuthorizationFeeCurrencyCode()); + data.AuthorizedAmount = new AmazonPayPrice(response.GetAuthorizationAmount(), response.GetAuthorizationAmountCurrencyCode()); + data.CapturedAmount = new AmazonPayPrice(response.GetCapturedAmount(), response.GetCapturedAmountCurrencyCode()); + data.CaptureNow = response.GetCaptureNow(); + data.Creation = response.GetCreationTimestamp(); + data.Expiration = response.GetExpirationTimestamp(); + data.ReasonCode = response.GetReasonCode(); + data.ReasonDescription = response.GetReasonDescription(); + data.State = response.GetAuthorizationState(); + data.StateLastUpdate = response.GetLastUpdateTimestamp(); + + return data; + } + private AmazonPayData GetDetails(CaptureResponse response) + { + var data = new AmazonPayData(); + data.MessageType = "GetCaptureDetails"; + data.MessageId = response.GetRequestId(); + data.CaptureId = response.GetCaptureId(); + data.ReferenceId = response.GetCaptureReferenceId(); + data.Fee = new AmazonPayPrice(response.GetCaptureFee(), response.GetCaptureFeeCurrencyCode()); + data.CapturedAmount = new AmazonPayPrice(response.GetCaptureAmount(), response.GetCaptureAmountCurrencyCode()); + data.Creation = response.GetCreationTimestamp(); + data.ReasonCode = response.GetReasonCode(); + data.ReasonDescription = response.GetReasonDescription(); + data.State = response.GetCaptureState(); + data.StateLastUpdate = response.GetLastUpdatedTimestamp(); + + return data; + } + private AmazonPayData GetDetails(RefundResponse response) + { + var data = new AmazonPayData(); + data.MessageType = "GetRefundDetails"; + data.MessageId = response.GetRequestId(); + data.ReferenceId = response.GetRefundReferenceId(); + data.Creation = response.GetCreationTimestamp(); + data.Fee = new AmazonPayPrice(response.GetRefundFee(), response.GetRefundFeeCurrencyCode()); + data.RefundId = response.GetAmazonRefundId(); + data.RefundedAmount = new AmazonPayPrice(response.GetRefundAmount(), response.GetRefundAmountCurrencyCode()); + data.ReasonCode = response.GetReasonCode(); + data.ReasonDescription = response.GetReasonDescription(); + data.State = response.GetRefundState(); + data.StateLastUpdate = response.GetLastUpdateTimestamp(); + + return data; + } + + private string GetRandomId(string prefix) + { + var str = prefix + CommonHelper.GenerateRandomDigitCode(20); + return str.Truncate(32); + } + + private string LogError(DelegateRequest request, IResponse response, bool isWarning = false) + { + var message = string.Empty; + + if (response != null) + { + var requestMethod = request != null ? request.GetAction() : null; + + message = $"{requestMethod.NaIfEmpty()} --> {response.GetErrorCode().NaIfEmpty()}. {response.GetErrorMessage().NaIfEmpty()}"; + + Logger.Log(isWarning ? LogLevel.Warning : LogLevel.Error, new Exception(response.GetJson()), message, null); + } + + return message; + } + + private string ToInfoString(AmazonPayData data) + { + var sb = new StringBuilder(); + + try + { + var strings = _services.Localization.GetResource("Plugins.Payments.AmazonPay.MessageStrings").SplitSafe(";"); + var state = data.State.Grow(data.ReasonCode, " "); + + if (data.ReasonDescription.HasValue()) + state = $"{state} ({data.ReasonDescription})"; + + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.MessageTyp)}: {data.MessageType.NaIfEmpty()}"); + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.State)}: {state}"); + + var stateDate = _dateTimeHelper.ConvertToUserTime(data.StateLastUpdate, DateTimeKind.Utc); + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.StateUpdate)}: {stateDate.ToString()}"); + + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.MessageId)}: {data.MessageId.NaIfEmpty()}"); + + if (data.AuthorizationId.HasValue()) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.AuthorizationID)}: {data.AuthorizationId}"); + + if (data.CaptureId.HasValue()) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.CaptureID)}: {data.CaptureId}"); + + if (data.RefundId.HasValue()) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.RefundID)}: {data.RefundId}"); + + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.ReferenceID)}: {data.ReferenceId.NaIfEmpty()}"); + + if (data.Fee != null && data.Fee.Amount != decimal.Zero) + { + var signed = data.MessageType.IsCaseInsensitiveEqual("RefundNotification") || data.MessageType.IsCaseInsensitiveEqual("GetRefundDetails") ? "-" : ""; + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.Fee)}: {signed}"); + } + + if (data.AuthorizedAmount != null && data.AuthorizedAmount.Amount != decimal.Zero) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.AuthorizedAmount)}: {data.AuthorizedAmount.ToString()}"); + + if (data.CapturedAmount != null && data.CapturedAmount.Amount != decimal.Zero) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.CapturedAmount)}: {data.CapturedAmount.ToString()}"); + + if (data.RefundedAmount != null && data.RefundedAmount.Amount != decimal.Zero) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.RefundedAmount)}: {data.RefundedAmount.ToString()}"); + + if (data.CaptureNow.HasValue) + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.CaptureNow)}: {data.CaptureNow.Value.ToString()}"); + + var creationDate = _dateTimeHelper.ConvertToUserTime(data.Creation, DateTimeKind.Utc); + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.Creation)}: {creationDate.ToString()}"); + + if (data.Expiration.HasValue) + { + var expirationDate = _dateTimeHelper.ConvertToUserTime(data.Expiration.Value, DateTimeKind.Utc); + sb.AppendLine($"{strings.SafeGet((int)AmazonPayMessage.Expiration)}: {expirationDate.ToString()}"); + } + } + catch (Exception exception) + { + exception.Dump(); + } + + return sb.ToString(); + } + + /// + /// Creates an API client. + /// + /// AmazonPay settings + /// Currency code of primary store currency + /// AmazonPay client + private Client CreateClient(AmazonPaySettings settings) + { + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(AmazonPayPlugin.SystemName); + var appVersion = descriptor != null ? descriptor.Version.ToString() : "1.0"; + + Regions.supportedRegions region; + switch (settings.Marketplace.EmptyNull().ToLower()) + { + case "us": + region = Regions.supportedRegions.us; + break; + case "uk": + region = Regions.supportedRegions.uk; + break; + case "jp": + region = Regions.supportedRegions.jp; + break; + default: + region = Regions.supportedRegions.de; + break; + } + + var config = new Configuration() + .WithAccessKey(settings.AccessKey) + .WithClientId(settings.ClientId) + .WithSecretKey(settings.SecretKey) + .WithSandbox(settings.UseSandbox) + .WithApplicationName("SmartStore.Net " + AmazonPayPlugin.SystemName) + .WithApplicationVersion(appVersion) + .WithRegion(region); + + var client = new Client(config); + return client; + } + + private AuthorizeResponse AuthorizePayment( + AmazonPaySettings settings, + AmazonPayCheckoutState state, + Store store, + ProcessPaymentRequest request, + Client client, + bool synchronously, + out AuthorizeRequest authRequest) + { + authRequest = new AuthorizeRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(state.OrderReferenceId) + .WithAuthorizationReferenceId(GetRandomId("Authorize")) + .WithCaptureNow(settings.TransactionType == AmazonPayTransactionType.AuthorizeAndCapture) + .WithCurrencyCode(ConvertCurrency(store.PrimaryStoreCurrency.CurrencyCode)) + .WithAmount(request.OrderTotal); + + if (synchronously) + { + authRequest = authRequest.WithTransactionTimeout(0); + } + + // See https://pay.amazon.com/de/developer/documentation/lpwa/201956480 + //{"SandboxSimulation": {"State":"Declined", "ReasonCode":"InvalidPaymentMethod", "PaymentMethodUpdateTimeInMins":5}} + //{"SandboxSimulation": {"State":"Declined", "ReasonCode":"AmazonRejected"}} + //if (settings.UseSandbox) + //{ + // var authNote = _services.Settings.GetSettingByKey("SmartStore.AmazonPay.SellerAuthorizationNote"); + // authRequest = authRequest.WithSellerAuthorizationNote(authNote); + //} + + var authResponse = client.Authorize(authRequest); + return authResponse; + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs new file mode 100644 index 0000000000..8836575653 --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using AmazonPay; +using SmartStore.Core.Domain.Orders; + +namespace SmartStore.AmazonPay.Services +{ + [Serializable] + public class AmazonPayCheckoutState + { + public string OrderReferenceId { get; set; } + public string AccessToken { get; set; } + + // Confirmation flow. + public bool DetailsSet { get; set; } + public bool IsConfirmed { get; set; } + public string FormData { get; set; } + public bool SubmitForm { get; set; } + } + + + public class AmazonPayActionState + { + public Guid OrderGuid { get; set; } + public List Errors { get; set; } + } + + + [Serializable] + public class AmazonPayOrderAttribute + { + public string OrderReferenceId { get; set; } + public bool OrderReferenceClosed { get; set; } + } + + + internal class PollingLoopData + { + public PollingLoopData(int orderId) + { + OrderId = orderId; + } + + public int OrderId { get; private set; } + public Order Order { get; set; } + public AmazonPaySettings Settings { get; set; } + public Client Client { get; set; } + } +} diff --git a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs deleted file mode 100644 index 70d72d9aa1..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.Generic; -using System.Web; -using OffAmazonPaymentsService.Model; -using SmartStore.AmazonPay.Services; -using SmartStore.AmazonPay.Settings; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Payments; - -namespace SmartStore.AmazonPay.Api -{ - public partial interface IAmazonPayApi - { - void GetConstraints(OrderReferenceDetails details, IList warnings); - - bool FindAndApplyAddress(OrderReferenceDetails details, Customer customer, bool isShippable, bool forceToTakeAmazonAddress); - bool FulfillBillingAddress(AmazonPaySettings settings, Order order, AuthorizationDetails details, out string formattedAddress); - - OrderReferenceDetails GetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, string addressConsentToken = null); - - OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, decimal? orderTotalAmount, - string currencyCode, string orderGuid = null, string storeName = null); - - OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, string currencyCode, List cart); - - void ConfirmOrderReference(AmazonPayClient client, string orderReferenceId); - - void CancelOrderReference(AmazonPayClient client, string orderReferenceId); - - void CloseOrderReference(AmazonPayClient client, string orderReferenceId); - - void Authorize(AmazonPayClient client, ProcessPaymentResult result, List errors, string orderReferenceId, decimal orderTotalAmount, - string currencyCode, string orderGuid); - - AuthorizationDetails GetAuthorizationDetails(AmazonPayClient client, string authorizationId, out AmazonPayApiData data); - - void Capture(AmazonPayClient client, CapturePaymentRequest capture, CapturePaymentResult result); - - CaptureDetails GetCaptureDetails(AmazonPayClient client, string captureId, out AmazonPayApiData data); - - string Refund(AmazonPayClient client, RefundPaymentRequest refund, RefundPaymentResult result); - - RefundDetails GetRefundDetails(AmazonPayClient client, string refundId, out AmazonPayApiData data); - - string ToInfoString(AmazonPayApiData data); - - AmazonPayApiData ParseNotification(HttpRequestBase request); - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs index 5e14a4d77e..f4adc780e2 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs @@ -1,33 +1,33 @@ -using System; -using System.Web; +using System.Web; using System.Web.Mvc; -using System.Collections.Generic; -using OffAmazonPaymentsService; +using AmazonPay; using SmartStore.AmazonPay.Models; -using SmartStore.AmazonPay.Settings; using SmartStore.Core.Domain.Orders; +using SmartStore.Services.Authentication.External; using SmartStore.Services.Payments; namespace SmartStore.AmazonPay.Services { - public partial interface IAmazonPayService + public partial interface IAmazonPayService : IExternalProviderAuthorizer { - void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null); - void LogAmazonError(OffAmazonPaymentsServiceException exception, bool notify = false, IList errors = null); - - void AddOrderNote(AmazonPaySettings settings, Order order, AmazonPayOrderNote note, string anyString = null, bool isIpn = false); - void SetupConfiguration(ConfigurationModel model); - string GetWidgetUrl(); + AmazonPayViewModel CreateViewModel(AmazonPayRequestType type, TempDataDictionary tempData); - AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDataDictionary tempData, string orderReferenceId = null); - - void ApplyRewardPoints(bool useRewardPoints); + void CloseOrderReference(AmazonPaySettings settings, Order order); void AddCustomerOrderNoteLoop(AmazonPayActionState state); - PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest request); + void GetBillingAddress(); + + bool SetOrderReferenceDetails( + Client client, + AmazonPaySettings settings, + AmazonPayCheckoutState state, + string sellerNote, + out string errorMessage); + + bool ConfirmOrderReference(); ProcessPaymentResult ProcessPayment(ProcessPaymentRequest request); @@ -41,12 +41,8 @@ public partial interface IAmazonPayService void ProcessIpn(HttpRequestBase request); - void DataPollingTaskProcess(); - - void DataPollingTaskInit(); - - void DataPollingTaskUpdate(bool enabled, int seconds); + void StartDataPolling(); - void DataPollingTaskDelete(); + void ShareKeys(string payload, int storeId); } } diff --git a/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayData.cs b/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayData.cs new file mode 100644 index 0000000000..8df3aa6d48 --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayData.cs @@ -0,0 +1,40 @@ +using System; + +namespace SmartStore.AmazonPay.Services.Internal +{ + internal class AmazonPayData + { + public string MessageType { get; set; } + public string MessageId { get; set; } + public string AuthorizationId { get; set; } + public string CaptureId { get; set; } + public string RefundId { get; set; } + public string ReferenceId { get; set; } + + public string ReasonCode { get; set; } + public string ReasonDescription { get; set; } + public string State { get; set; } + public DateTime StateLastUpdate { get; set; } + + public AmazonPayPrice Fee { get; set; } + public AmazonPayPrice AuthorizedAmount { get; set; } + public AmazonPayPrice CapturedAmount { get; set; } + public AmazonPayPrice RefundedAmount { get; set; } + + public bool? CaptureNow { get; set; } + public DateTime Creation { get; set; } + public DateTime? Expiration { get; set; } + + public string AnyAmazonId + { + get + { + if (CaptureId.HasValue()) + return CaptureId; + if (AuthorizationId.HasValue()) + return AuthorizationId; + return RefundId; + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayExtensions.cs b/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayExtensions.cs new file mode 100644 index 0000000000..1eb4378dfa --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayExtensions.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web; +using SmartStore.AmazonPay.Services; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Orders; +using SmartStore.Services.Common; +using SmartStore.Services.Localization; + +namespace SmartStore.AmazonPay +{ + internal static class AmazonPayExtensions + { + internal static void ToFirstAndLastName(this string name, out string firstName, out string lastName) + { + if (!string.IsNullOrWhiteSpace(name)) + { + int index = name.LastIndexOf(' '); + if (index == -1) + { + firstName = ""; + lastName = name; + } + else + { + firstName = name.Substring(0, index); + lastName = name.Substring(index + 1); + } + + firstName = firstName.EmptyNull().Truncate(4000); + lastName = lastName.EmptyNull().Truncate(4000); + } + else + { + firstName = lastName = ""; + } + } + + internal static void ToFirstAndLastName(this Address address, string name) + { + string firstName, lastName; + name.ToFirstAndLastName(out firstName, out lastName); + + address.FirstName = firstName; + address.LastName = lastName; + } + + internal static Address FindAddress(this List
                                addresses, Address address, bool uncompleteToo) + { + var match = addresses.FindAddress(address.FirstName, address.LastName, + address.PhoneNumber, address.Email, address.FaxNumber, address.Company, + address.Address1, address.Address2, + address.City, address.StateProvinceId, address.ZipPostalCode, address.CountryId); + + if (match == null && uncompleteToo) + { + // Compare with ToAddress + match = addresses.FirstOrDefault(x => + x.FirstName == null && x.LastName == null && + x.Address1 == null && x.Address2 == null && + x.City == address.City && x.ZipPostalCode == address.ZipPostalCode && + x.PhoneNumber == null && + x.CountryId == address.CountryId && x.StateProvinceId == address.StateProvinceId + ); + } + + return match; + } + + internal static string ToAmazonLanguageCode(this string twoLetterLanguageCode, char delimiter = '-') + { + switch (twoLetterLanguageCode.EmptyNull().ToLower()) + { + case "en": + return $"en{delimiter}GB"; + case "fr": + return $"fr{delimiter}FR"; + case "it": + return $"it{delimiter}IT"; + case "es": + return $"es{delimiter}ES"; + case "de": + default: + return $"de{delimiter}DE"; + } + } + + internal static bool HasAmazonPayState(this HttpContextBase httpContext) + { + var checkoutState = httpContext.GetCheckoutState(); + var checkoutStateKey = AmazonPayPlugin.SystemName + ".CheckoutState"; + + if (checkoutState != null && checkoutState.CustomProperties.ContainsKey(checkoutStateKey)) + { + var state = checkoutState.CustomProperties[checkoutStateKey] as AmazonPayCheckoutState; + + return state != null && state.AccessToken.HasValue(); + } + + return false; + } + + internal static AmazonPayCheckoutState GetAmazonPayState(this HttpContextBase httpContext, ILocalizationService localizationService) + { + var checkoutState = httpContext.GetCheckoutState(); + + if (checkoutState == null) + throw new SmartException(localizationService.GetResource("Plugins.Payments.AmazonPay.MissingCheckoutSessionState")); + + var state = checkoutState.CustomProperties.Get(AmazonPayPlugin.SystemName + ".CheckoutState") as AmazonPayCheckoutState; + + if (state == null) + throw new SmartException(localizationService.GetResource("Plugins.Payments.AmazonPay.MissingCheckoutSessionState")); + + return state; + } + + internal static Order GetOrderByAmazonId(this IRepository orderRepository, string amazonId) + { + // S02-9777218-8608106 OrderReferenceId + // S02-9777218-8608106-A088344 Auth ID + // S02-9777218-8608106-C088344 Capture ID + + if (amazonId.HasValue()) + { + string amazonOrderReferenceId = amazonId.Substring(0, amazonId.LastIndexOf('-')); + if (amazonOrderReferenceId.HasValue()) + { + var orders = orderRepository.Table + .Where(x => x.PaymentMethodSystemName == AmazonPayPlugin.SystemName && x.AuthorizationTransactionId.StartsWith(amazonOrderReferenceId)) + .ToList(); + + if (orders.Count() == 1) + return orders.FirstOrDefault(); + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayPrice.cs b/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayPrice.cs new file mode 100644 index 0000000000..cfa3fef830 --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Services/Internal/AmazonPayPrice.cs @@ -0,0 +1,22 @@ +using System.Globalization; + +namespace SmartStore.AmazonPay.Services.Internal +{ + internal class AmazonPayPrice + { + public AmazonPayPrice(decimal amount, string currencyCode) + { + Amount = amount; + CurrencyCode = currencyCode; + } + + public decimal Amount { get; private set; } + public string CurrencyCode { get; private set; } + + public override string ToString() + { + var str = Amount.ToString("0.00", CultureInfo.InvariantCulture); + return str.Grow(CurrencyCode, " "); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Settings/AmazonPaySettings.cs b/src/Plugins/SmartStore.AmazonPay/Settings/AmazonPaySettings.cs deleted file mode 100644 index 6acdaf0f18..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Settings/AmazonPaySettings.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Web; -using SmartStore.Core.Configuration; -using SmartStore.AmazonPay.Services; - -namespace SmartStore.AmazonPay.Settings -{ - public class AmazonPaySettings : ISettings - { - public AmazonPaySettings() - { - Marketplace = "de"; - DataFetching = AmazonPayDataFetchingType.Ipn; - TransactionType = AmazonPayTransactionType.Authorize; - SaveEmailAndPhone = AmazonPaySaveDataType.OnlyIfEmpty; - AmazonButtonColor = "orange"; - AmazonButtonSize = "x-large"; - AddressWidgetWidth = PaymentWidgetWidth = 400; - AddressWidgetHeight = PaymentWidgetHeight = 260; - AddOrderNotes = true; - InformCustomerAboutErrors = true; - InformCustomerAddErrors = true; - PollingMaxOrderCreationDays = 31; - } - - public bool UseSandbox { get; set; } - - public string SellerId { get; set; } - public string AccessKey { get; set; } - public string SecretKey { get; set; } - public string Marketplace { get; set; } - - public AmazonPayDataFetchingType DataFetching { get; set; } - public AmazonPayTransactionType TransactionType { get; set; } - - public AmazonPaySaveDataType? SaveEmailAndPhone { get; set; } - public bool ShowButtonInMiniShoppingCart { get; set; } - - public int PollingMaxOrderCreationDays { get; set; } - - public string AmazonButtonColor { get; set; } - public string AmazonButtonSize { get; set; } - - public int AddressWidgetWidth { get; set; } - public int AddressWidgetHeight { get; set; } - - public int PaymentWidgetWidth { get; set; } - public int PaymentWidgetHeight { get; set; } - - public decimal AdditionalFee { get; set; } - public bool AdditionalFeePercentage { get; set; } - - public bool AddOrderNotes { get; set; } - - public bool InformCustomerAboutErrors { get; set; } - public bool InformCustomerAddErrors { get; set; } - - public string GetApiUrl() - { - return (UseSandbox ? AmazonPayCore.UrlApiEuSandbox : AmazonPayCore.UrlApiEuProduction); - } - - public string GetWidgetUrl() - { - if (SellerId.IsEmpty()) - return null; - - string url = (UseSandbox ? AmazonPayCore.UrlWidgetSandbox : AmazonPayCore.UrlWidgetProduction); - url = url.FormatWith(Marketplace ?? "de"); - - return "{0}?sellerId={1}".FormatWith( - url, - HttpUtility.UrlEncode(SellerId) - ); - } - - public string GetButtonUrl(AmazonPayRequestType view) - { - //bool isGerman = _services.WorkContext.WorkingLanguage.UniqueSeoCode.IsCaseInsensitiveEqual("DE"); - string marketplace = Marketplace ?? "de"; - if (marketplace.IsCaseInsensitiveEqual("uk")) - marketplace = "co.uk"; - - string buttonSize = (view == AmazonPayRequestType.MiniShoppingCart ? "medium" : AmazonButtonSize); - - string url = (UseSandbox ? AmazonPayCore.UrlButtonSandbox : AmazonPayCore.UrlButtonProduction); - url = url.FormatWith(marketplace); - - return "{0}?sellerId={1}&size={2}&color={3}".FormatWith( - url, - HttpUtility.UrlEncode(SellerId), - HttpUtility.UrlEncode(buttonSize ?? "x-large"), - HttpUtility.UrlEncode(AmazonButtonColor ?? "orange") - ); - } - - public bool CanSaveEmailAndPhone(string value) - { - return ( - SaveEmailAndPhone == AmazonPaySaveDataType.Always || (SaveEmailAndPhone == AmazonPaySaveDataType.OnlyIfEmpty && value.IsEmpty()) - ); - } - } -} diff --git a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj index 3cbc8fcb83..b2db65457e 100644 --- a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj +++ b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj @@ -1,5 +1,6 @@  - + + @@ -18,7 +19,7 @@ Properties SmartStore.AmazonPay SmartStore.AmazonPay - v4.5.1 + v4.6.1 512 @@ -37,6 +38,9 @@ + + + true @@ -74,24 +78,32 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\AmazonPay.3.5.0\lib\net20\AmazonPay.dll + True - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False + + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll + False + + + ..\..\packages\Common.Logging.2.0.0\lib\2.0\Common.Logging.dll + True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - False - lib\OffAmazonPaymentsNotifications.dll - - - False - lib\OffAmazonPaymentsService.dll + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False + @@ -99,32 +111,32 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - @@ -135,22 +147,27 @@ Properties\AssemblyVersionInfo.cs + + PreserveNewest + - - - + + + - - - + + + + + - + @@ -159,11 +176,15 @@ + + PreserveNewest + PreserveNewest - - + + PreserveNewest + PreserveNewest @@ -189,42 +210,24 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest - + PreserveNewest PreserveNewest - - - - PreserveNewest - - + PreserveNewest + + Always - Always - - PreserveNewest @@ -235,9 +238,6 @@ Designer Always - - Always - @@ -286,8 +286,15 @@ - xcopy /s /y "$(ProjectDir)lib\*.*" "$(TargetDir)*.*" + + + + + Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.Clickatell/AdminMenu.cs b/src/Plugins/SmartStore.Clickatell/AdminMenu.cs index 27ea086787..f336662023 100644 --- a/src/Plugins/SmartStore.Clickatell/AdminMenu.cs +++ b/src/Plugins/SmartStore.Clickatell/AdminMenu.cs @@ -10,7 +10,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) var menuItem = new MenuItem().ToBuilder() .Text("Clickatell SMS Provider") .ResKey("Plugins.FriendlyName.SmartStore.Clickatell") - .Icon("send-o") + .Icon("far fa-paper-plane") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.Clickatell", area = "Admin" }) .ToItem(); diff --git a/src/Plugins/SmartStore.Clickatell/ClickatellSettings.cs b/src/Plugins/SmartStore.Clickatell/ClickatellSettings.cs index 287f57a4e9..84752bee98 100644 --- a/src/Plugins/SmartStore.Clickatell/ClickatellSettings.cs +++ b/src/Plugins/SmartStore.Clickatell/ClickatellSettings.cs @@ -1,5 +1,4 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.Clickatell { @@ -19,15 +18,5 @@ public class ClickatellSettings : ISettings /// Gets or sets the Clickatell API ID ///
public string ApiId { get; set; } - - /// - /// Gets or sets the Clickatell username - /// - public string Username { get; set; } - - /// - /// Gets or sets the Clickatell password - /// - public string Password { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs b/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs index cd7fca5a22..cd183ffb6b 100644 --- a/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs +++ b/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs @@ -1,100 +1,133 @@ using System; -using System.ServiceModel; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; using System.Web.Routing; -using SmartStore.Clickatell.Clickatell; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Localization; namespace SmartStore.Clickatell { - /// - /// Represents the Clickatell SMS provider - /// public class ClickatellSmsProvider : BasePlugin, IConfigurable { - private readonly ILogger _logger; private readonly ClickatellSettings _clickatellSettings; private readonly ILocalizationService _localizationService; + private readonly ILogger _logger; - public ClickatellSmsProvider(ClickatellSettings clickatellSettings, - ILogger logger, - ILocalizationService localizationService) + public ClickatellSmsProvider( + ClickatellSettings clickatellSettings, + ILocalizationService localizationService, + ILogger logger) { - this._clickatellSettings = clickatellSettings; - this._logger = logger; + _clickatellSettings = clickatellSettings; _localizationService = localizationService; - } + _logger = logger; + } - /// - /// Sends SMS - /// - /// Text - public bool SendSms(string text) - { - try - { - using (var svc = new PushServerWSPortTypeClient(new BasicHttpBinding(), new EndpointAddress("http://api.clickatell.com/soap/webservice_vs.php"))) - { - string authRsp = svc.auth(Int32.Parse(_clickatellSettings.ApiId), _clickatellSettings.Username, _clickatellSettings.Password); - - if (!authRsp.ToUpperInvariant().StartsWith("OK")) - { - throw new SmartException(authRsp); - } - - string ssid = authRsp.Substring(4); - string[] sndRsp = svc.sendmsg(ssid, - Int32.Parse(_clickatellSettings.ApiId), _clickatellSettings.Username, - _clickatellSettings.Password, new string[1] { _clickatellSettings.PhoneNumber }, - String.Empty, text, 0, 0, 0, 0, 0, 0, 0, 0, 0, - String.Empty, 0, String.Empty, String.Empty, String.Empty, 0); - - if (!sndRsp[0].ToUpperInvariant().StartsWith("ID")) - { - throw new SmartException(sndRsp[0]); - } - return true; - } - } - catch (Exception ex) - { - _logger.Error(ex.Message, ex); - } - return false; - } + public static string SystemName + { + get + { + return "SmartStore.Clickatell"; + } + } - /// - /// Gets a route for provider configuration - /// - /// Action name - /// Controller name - /// Route values - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; controllerName = "SmsClickatell"; - routeValues = new RouteValueDictionary() { { "area", "SmartStore.Clickatell" } }; + routeValues = new RouteValueDictionary { { "area", SystemName } }; } - /// - /// Install plugin - /// public override void Install() { - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); + _localizationService.ImportPluginResourcesFromXml(PluginDescriptor); base.Install(); } - /// - /// Uninstall plugin - /// public override void Uninstall() { - _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); + _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); base.Uninstall(); } - } + + public void SendSms(string text) + { + if (text.IsEmpty()) + return; + + string error = null; + HttpWebResponse webResponse = null; + + try + { + // https://www.clickatell.com/developers/api-documentation/rest-api-request-parameters/ + var request = (HttpWebRequest)WebRequest.Create("https://platform.clickatell.com/messages"); + request.Method = "POST"; + request.Accept = "application/json"; + request.ContentType = "application/json"; + request.Headers["Authorization"] = _clickatellSettings.ApiId; + + var data = new Dictionary + { + { "content", text }, + { "to", _clickatellSettings.PhoneNumber.SplitSafe(";") } + }; + + var json = JsonConvert.SerializeObject(data); + + // UTF8 is default encoding + var bytes = Encoding.UTF8.GetBytes(json); + request.ContentLength = bytes.Length; + + using (var stream = request.GetRequestStream()) + { + stream.Write(bytes, 0, bytes.Length); + } + + webResponse = request.GetResponse() as HttpWebResponse; + } + catch (WebException wexc) + { + webResponse = wexc.Response as HttpWebResponse; + } + catch (Exception exception) + { + error = exception.ToString(); + } + + if (webResponse != null) + { + using (var reader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8)) + { + var rawResponse = reader.ReadToEnd(); + + if (webResponse.StatusCode == HttpStatusCode.OK || webResponse.StatusCode == HttpStatusCode.Accepted) + { + dynamic response = JObject.Parse(rawResponse); + + error = (string)response.error; + if (error.IsEmpty() && response.messages != null) + error = response.messages[0].error; + } + else + { + error = rawResponse; + } + } + } + + if (error.HasValue()) + { + _logger.Error(error); + throw new SmartException(error); + } + } + } } diff --git a/src/Plugins/SmartStore.Clickatell/Content/branding.png b/src/Plugins/SmartStore.Clickatell/Content/branding.png new file mode 100644 index 0000000000..06e58f757f Binary files /dev/null and b/src/Plugins/SmartStore.Clickatell/Content/branding.png differ diff --git a/src/Plugins/SmartStore.Clickatell/Content/icon.png b/src/Plugins/SmartStore.Clickatell/Content/icon.png new file mode 100644 index 0000000000..322fcd0493 Binary files /dev/null and b/src/Plugins/SmartStore.Clickatell/Content/icon.png differ diff --git a/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs b/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs index 82be6e7725..d9451d5e75 100644 --- a/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs +++ b/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs @@ -1,98 +1,76 @@ using System; using System.Web.Mvc; -using SmartStore.Core.Plugins; using SmartStore.Clickatell.Models; -using SmartStore.Clickatell; -using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; +using SmartStore.ComponentModel; +using SmartStore.Core.Plugins; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; +using SmartStore.Web.Framework.Settings; namespace SmartStore.Clickatell.Controllers { - [AdminAuthorize] public class SmsClickatellController : PluginControllerBase { - private readonly ClickatellSettings _clickatellSettings; - private readonly ISettingService _settingService; - private readonly IPluginFinder _pluginFinder; - private readonly ILocalizationService _localizationService; + private readonly IPluginFinder _pluginFinder; - public SmsClickatellController(ClickatellSettings clickatellSettings, - ISettingService settingService, IPluginFinder pluginFinder, - ILocalizationService localizationService) + public SmsClickatellController(IPluginFinder pluginFinder) { - this._clickatellSettings = clickatellSettings; - this._settingService = settingService; - this._pluginFinder = pluginFinder; - this._localizationService = localizationService; + _pluginFinder = pluginFinder; } - public ActionResult Configure() - { - var model = new SmsClickatellModel(); - model.Enabled = _clickatellSettings.Enabled; - model.PhoneNumber = _clickatellSettings.PhoneNumber; - model.ApiId = _clickatellSettings.ApiId; - model.Username = _clickatellSettings.Username; - model.Password = _clickatellSettings.Password; - return View(model); + [LoadSetting] + public ActionResult Configure(ClickatellSettings settings) + { + var model = new SmsClickatellModel(); + MiniMapper.Map(settings, model); + + return View(model); } - [HttpPost, ActionName("Configure")] - [FormValueRequired("save")] - public ActionResult ConfigurePOST(SmsClickatellModel model) + [HttpPost, SaveSetting, FormValueRequired("save")] + public ActionResult Configure(ClickatellSettings settings, SmsClickatellModel model) { - if (!ModelState.IsValid) - { - return Configure(); - } - - //save settings - _clickatellSettings.Enabled = model.Enabled; - _clickatellSettings.PhoneNumber = model.PhoneNumber; - _clickatellSettings.ApiId = model.ApiId; - _clickatellSettings.Username = model.Username; - _clickatellSettings.Password = model.Password; - _settingService.SaveSetting(_clickatellSettings); + if (!ModelState.IsValid) + { + return Configure(settings); + } - return View(model); - } + MiniMapper.Map(model, settings); + settings.ApiId = model.ApiId.TrimSafe(); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); - [HttpPost, ActionName("Configure")] - [FormValueRequired("test-sms")] + return RedirectToConfiguration(ClickatellSmsProvider.SystemName); + } + + [HttpPost, ActionName("Configure"), FormValueRequired("test-sms")] public ActionResult TestSms(SmsClickatellModel model) { try { - if (String.IsNullOrEmpty(model.TestMessage)) + if (model.TestMessage.IsEmpty()) { - model.TestSmsResult = "Enter test message"; + model.TestSucceeded = false; + model.TestSmsResult = T("Plugins.Sms.Clickatell.EnterMessage"); } else { - var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName("SmartStore.Clickatell"); - if (pluginDescriptor == null) - throw new Exception("Cannot load the plugin"); + var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName(ClickatellSmsProvider.SystemName); var plugin = pluginDescriptor.Instance() as ClickatellSmsProvider; - if (plugin == null) - throw new Exception("Cannot load the plugin"); - if (!plugin.SendSms(model.TestMessage)) - { - model.TestSmsResult = _localizationService.GetResource("Plugins.Sms.Clickatell.TestFailed"); - } - else - { - model.TestSmsResult = _localizationService.GetResource("Plugins.Sms.Clickatell.TestSuccess"); - } + plugin.SendSms(model.TestMessage); + + model.TestSucceeded = true; + model.TestSmsResult = T("Plugins.Sms.Clickatell.TestSuccess"); } } - catch(Exception exc) + catch (Exception exception) { - model.TestSmsResult = exc.ToString(); + model.TestSucceeded = false; + model.TestSmsResult = T("Plugins.Sms.Clickatell.TestFailed"); + model.TestSmsDetailResult = exception.Message; } return View("Configure", model); diff --git a/src/Plugins/SmartStore.Clickatell/Description.txt b/src/Plugins/SmartStore.Clickatell/Description.txt index 177b509843..003d6a572e 100644 --- a/src/Plugins/SmartStore.Clickatell/Description.txt +++ b/src/Plugins/SmartStore.Clickatell/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Clickatell SMS Provider SystemName: SmartStore.Clickatell Group: Mobile -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 1 FileName: SmartStore.Clickatell.dll ResourceRootKey: Plugins.Sms.Clickatell \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml b/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml index 6a88b543c5..56b07d1015 100644 --- a/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml @@ -7,53 +7,50 @@ Um ein Benachrichtigung per SMS zu erhalten wenn eine Bestellung in Ihrem Shop durchgeführt wurde, - müssen folgende Schritte durchgeführt werden. + müssen folgende Schritte durchgeführt werden:

    -
  • Registrieren Sie sich hier für einen Clickatell-Account
  • -
  • Clickatell funktioniert in allen Ländern und bietet 10 kostenlose SMS zum Testen der Benachrichtigungen an
  • -
  • Füllen Sie das folgende Formular mit Ihren Clickatell-Accountdaten und der Telefonnummer zu der die Nachrichten gesendet werden sollen
  • -
  • Klicken Sie auf den 'Speichern'-Button
  • -
  • Wenn nun eine neue Bestellung durchgeführt wird, erhalten Sie automatisch eine SMS
  • +
  • Registrieren Sie sich hier für einen Clickatell-Account.
  • +
  • Legen Sie eine neue Integration über SMS integretation an.
  • +
  • Tragen Sie den dabei erzeugten API Key und die Telefonummer, an die Nachrichten gesendet werden sollen, unten in das Formular ein.
  • +
  • Klicken Sie auf Speichern.
  • +
  • Wenn nun eine neue Bestellung durchgeführt wird, erhalten Sie automatisch eine SMS.
]]> + + Bitte geben Sie eine Nachricht ein. + + + Ein neuer Auftrag mit der Nummer {0} ist eingegangen. + + + Eine SMS über den neuen Auftrag wurde versendet. + - Testnachricht konnte nicht gesendet werden + Testnachricht konnte nicht gesendet werden. - Testnachricht wurde erfolgreich verschickt + Testnachricht wurde erfolgreich verschickt. Aktiv - Aktivieren Sie hier den SMS-Anbieter + Legt fest, ob eine SMS Kurznachricht verschickt werden soll, sobald ein neuer Auftrag eingeht. - API-ID + API Key - Clickatell API-ID - - - Passwort - - - Clickatell Passwort + Clickatell API Key Telefonnummer - Ihre Telefonnummer - - - Nutzername - - - Clickatell Nutzername + Die Telefonnummer, an die SMS Kurznachrichten verschickt werden sollen. Sie können mehrere Nummern durch Semikolon getrennt eingeben. Geben Sie die Nummer bitte im internationalen Format ein, z.B. +49231987654. Text der Nachricht diff --git a/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml b/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml index 4a42186f01..032132b6e8 100644 --- a/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml @@ -5,53 +5,49 @@ To receive an SMS notification when an order is placed from your store you need - to follow a few simple steps, which are shown below:

+

To receive an SMS notification when an order is placed from your store, you need to follow the following steps:

    -
  • Register for a Clickatell account here
  • -
  • Clickatell works with all countries and includes 10 free messages so you can test SMS notifications
  • -
  • Fill in the form below with your Clickatell account details, including the number you want the notification messages to be sent to
  • -
  • Click 'Save' button
  • -
  • Now when you receive a new order, an SMS text message will be sent to the number you enter below automatically
  • +
  • Register for a Clickatell account here.
  • +
  • Create a new integration through SMS integretation.
  • +
  • Fill in the form below with the created API Key, including the number you want the notification messages to be sent to.
  • +
  • Click Save.
  • +
  • Now when you receive a new order, an SMS text message will be sent to the phone number you entered below.
]]>
+ + Please enter a message. + + + New order with number {0} has been placed. + + + A SMS about the new order has been sent. + - Test message sending failed + Test message sending failed. - Test message was sent + Test message was sent. Enabled - Check the box to enable SMS provider + Specifies whether to send an SMS when a new order has been placed. - API ID + API Key - Clickatell API ID - - - Password - - - Clickatell password + Clickatell API Key Phone number - Your phone number - - - Username - - - Clickatell username + The phone number where to send the SMS. You can enter multiple numbers separated by semicolon. Use the international format like +49231987654. Message text diff --git a/src/Plugins/SmartStore.Clickatell/Models/SmsClickatellModel.cs b/src/Plugins/SmartStore.Clickatell/Models/SmsClickatellModel.cs index 8d505b1072..e3588abc53 100644 --- a/src/Plugins/SmartStore.Clickatell/Models/SmsClickatellModel.cs +++ b/src/Plugins/SmartStore.Clickatell/Models/SmsClickatellModel.cs @@ -1,9 +1,9 @@ using SmartStore.Web.Framework; -using System.ComponentModel.DataAnnotations; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.Clickatell.Models { - public class SmsClickatellModel + public class SmsClickatellModel : ModelBase { [SmartResourceDisplayName("Plugins.Sms.Clickatell.Fields.Enabled")] public bool Enabled { get; set; } @@ -14,16 +14,11 @@ public class SmsClickatellModel [SmartResourceDisplayName("Plugins.Sms.Clickatell.Fields.ApiId")] public string ApiId { get; set; } - [SmartResourceDisplayName("Plugins.Sms.Clickatell.Fields.Username")] - public string Username { get; set; } - - [SmartResourceDisplayName("Plugins.Sms.Clickatell.Fields.Password")] - [DataType(DataType.Password)] - public string Password { get; set; } - - [SmartResourceDisplayName("Plugins.Sms.Clickatell.Fields.TestMessage")] public string TestMessage { get; set; } - public string TestSmsResult { get; set; } - } + + public bool TestSucceeded { get; set; } + public string TestSmsResult { get; set; } + public string TestSmsDetailResult { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs b/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs index 3ba9e1742b..8ff1936f22 100644 --- a/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs +++ b/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs @@ -1,69 +1,60 @@ -using System; -using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Orders; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; +using SmartStore.Services; using SmartStore.Services.Orders; -using SmartStore.Core; -using SmartStore.Services.Configuration; namespace SmartStore.Clickatell { - public class OrderPlacedEventConsumer : IConsumer + public class OrderPlacedEventConsumer : IConsumer { - private readonly ClickatellSettings _clickatellSettings; + private readonly ICommonServices _services; + private readonly ClickatellSettings _clickatellSettings; private readonly IPluginFinder _pluginFinder; private readonly IOrderService _orderService; - private readonly IStoreContext _storeContext; - private readonly ISettingService _settingService; - public OrderPlacedEventConsumer(ClickatellSettings clickatellSettings, + public OrderPlacedEventConsumer( + ICommonServices services, + ClickatellSettings clickatellSettings, IPluginFinder pluginFinder, - IOrderService orderService, - IStoreContext storeContext, - ISettingService settingService) + IOrderService orderService) { - this._clickatellSettings = clickatellSettings; - this._pluginFinder = pluginFinder; - this._orderService = orderService; - this._storeContext = storeContext; - this._settingService = settingService; - } + _services = services; + _clickatellSettings = clickatellSettings; + _pluginFinder = pluginFinder; + _orderService = orderService; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } - /// - /// Handles the event. - /// - /// The event message. - public void HandleEvent(OrderPlacedEvent eventMessage) + public void HandleEvent(OrderPlacedEvent eventMessage) { - //is enabled? if (!_clickatellSettings.Enabled) return; - //is plugin installed? - var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName("SmartStore.Clickatell"); - if (pluginDescriptor == null) + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(ClickatellSmsProvider.SystemName); + if (descriptor == null) return; - if (!(_storeContext.CurrentStore.Id == 0 || - _settingService.GetSettingByKey(pluginDescriptor.GetSettingKey("LimitedToStores")).ToIntArrayContains(_storeContext.CurrentStore.Id, true))) + var storeId = _services.StoreContext.CurrentStore.Id; + + if (!(storeId == 0 || _services.Settings.GetSettingByKey(descriptor.GetSettingKey("LimitedToStores")).ToIntArrayContains(storeId, true))) return; - var plugin = pluginDescriptor.Instance() as ClickatellSmsProvider; + var plugin = descriptor.Instance() as ClickatellSmsProvider; if (plugin == null) return; - var order = eventMessage.Order; - //send SMS - if (plugin.SendSms(String.Format("New order '{0}' has been placed.", order.GetOrderNumber()))) - { - order.OrderNotes.Add(new OrderNote() - { - Note = "\"Order placed\" SMS alert (to store owner) has been sent", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } + try + { + plugin.SendSms(T("Plugins.Sms.Clickatell.OrderPlacedMessage", eventMessage.Order.GetOrderNumber())); + + _orderService.AddOrderNote(eventMessage.Order, T("Plugins.Sms.Clickatell.SmsSentNote")); + } + catch { } } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/RouteProvider.cs b/src/Plugins/SmartStore.Clickatell/RouteProvider.cs index 956b559e91..1cc25597ed 100644 --- a/src/Plugins/SmartStore.Clickatell/RouteProvider.cs +++ b/src/Plugins/SmartStore.Clickatell/RouteProvider.cs @@ -4,7 +4,7 @@ namespace SmartStore.Clickatell { - public partial class RouteProvider : IRouteProvider + public partial class RouteProvider : IRouteProvider { public void RegisterRoutes(RouteCollection routes) { @@ -13,8 +13,9 @@ public void RegisterRoutes(RouteCollection routes) new { controller = "SmsClickatell", action = "Configure" }, new[] { "SmartStore.Clickatell.Controllers" } ) - .DataTokens["area"] = "SmartStore.Clickatell"; + .DataTokens["area"] = ClickatellSmsProvider.SystemName; } + public int Priority { get diff --git a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/Reference.cs b/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/Reference.cs deleted file mode 100644 index fe2b19478f..0000000000 --- a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/Reference.cs +++ /dev/null @@ -1,357 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.34209 -// -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. -// -//------------------------------------------------------------------------------ - -namespace SmartStore.Clickatell.Clickatell { - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] - [System.ServiceModel.ServiceContractAttribute(Namespace="http://api.clickatell.com/soap/webservice", ConfigurationName="Clickatell.PushServerWSPortType")] - internal interface PushServerWSPortType { - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/auth", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string auth(int api_id, string user, string password); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/ping", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string ping(string session_id); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/sendmsg", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] sendmsg( - string session_id, - int api_id, - string user, - string password, - string[] to, - string from, - string text, - int concat, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int unicode, - string msg_type, - string udh, - string data, - int validity); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/querymsg", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] querymsg(string session_id, int api_id, string user, string password, string apimsgid, string climsgid); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/delmsg", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] delmsg(string session_id, string api_id, string user, string password, string apimsgid, string climsgid); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/getbalance", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string getbalance(string session_id, int api_id, string user, string password); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/routeCoverage", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string routeCoverage(string session_id, int api_id, string user, string password, string msisdn); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/si_push", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] si_push( - string session_id, - int api_id, - string user, - string password, - string[] to, - string si_id, - string si_url, - string si_text, - string si_created, - string si_expires, - string si_action, - string from, - int concat, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int validity); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/ind_push", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] ind_push( - string session_id, - int api_id, - string user, - string password, - string[] to, - string mms_subject, - string mms_class, - string mms_expire, - string mms_from, - string mms_url, - string from, - int concat, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int validity); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/token_pay", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string token_pay(string session_id, int api_id, string user, string password, string token); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/startbatch", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] startbatch( - string session_id, - int api_id, - string user, - string password, - string from, - int concat, - string template, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int unicode, - string msg_type, - string udh, - string data, - int validity); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/senditem", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] senditem(string session_id, int api_id, string user, string password, string batch_id, string[] to, string field1, string field2, string field3, string field4); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/quicksend", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] quicksend(string session_id, int api_id, string user, string password, string batch_id, string[] to); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/endbatch", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] endbatch(string session_id, int api_id, string user, string password, string batch_id); - - [System.ServiceModel.OperationContractAttribute(Action="http://api.clickatell.com/soap/webservice_vs.php/getmsgcharge", ReplyAction="*")] - [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)] - [return: System.ServiceModel.MessageParameterAttribute(Name="return")] - string[] getmsgcharge(string session_id, int api_id, string user, string password, string apimsgid); - } - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] - internal interface PushServerWSPortTypeChannel : SmartStore.Clickatell.Clickatell.PushServerWSPortType, System.ServiceModel.IClientChannel { - } - - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] - internal partial class PushServerWSPortTypeClient : System.ServiceModel.ClientBase, SmartStore.Clickatell.Clickatell.PushServerWSPortType { - - public PushServerWSPortTypeClient() { - } - - public PushServerWSPortTypeClient(string endpointConfigurationName) : - base(endpointConfigurationName) { - } - - public PushServerWSPortTypeClient(string endpointConfigurationName, string remoteAddress) : - base(endpointConfigurationName, remoteAddress) { - } - - public PushServerWSPortTypeClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : - base(endpointConfigurationName, remoteAddress) { - } - - public PushServerWSPortTypeClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : - base(binding, remoteAddress) { - } - - public string auth(int api_id, string user, string password) { - return base.Channel.auth(api_id, user, password); - } - - public string ping(string session_id) { - return base.Channel.ping(session_id); - } - - public string[] sendmsg( - string session_id, - int api_id, - string user, - string password, - string[] to, - string from, - string text, - int concat, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int unicode, - string msg_type, - string udh, - string data, - int validity) { - return base.Channel.sendmsg(session_id, api_id, user, password, to, from, text, concat, deliv_ack, msg_callback, deliv_time, max_credits, req_feat, queue, escalate, mo, cliMsgId, unicode, msg_type, udh, data, validity); - } - - public string[] querymsg(string session_id, int api_id, string user, string password, string apimsgid, string climsgid) { - return base.Channel.querymsg(session_id, api_id, user, password, apimsgid, climsgid); - } - - public string[] delmsg(string session_id, string api_id, string user, string password, string apimsgid, string climsgid) { - return base.Channel.delmsg(session_id, api_id, user, password, apimsgid, climsgid); - } - - public string getbalance(string session_id, int api_id, string user, string password) { - return base.Channel.getbalance(session_id, api_id, user, password); - } - - public string routeCoverage(string session_id, int api_id, string user, string password, string msisdn) { - return base.Channel.routeCoverage(session_id, api_id, user, password, msisdn); - } - - public string[] si_push( - string session_id, - int api_id, - string user, - string password, - string[] to, - string si_id, - string si_url, - string si_text, - string si_created, - string si_expires, - string si_action, - string from, - int concat, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int validity) { - return base.Channel.si_push(session_id, api_id, user, password, to, si_id, si_url, si_text, si_created, si_expires, si_action, from, concat, deliv_ack, msg_callback, deliv_time, max_credits, req_feat, queue, escalate, mo, cliMsgId, validity); - } - - public string[] ind_push( - string session_id, - int api_id, - string user, - string password, - string[] to, - string mms_subject, - string mms_class, - string mms_expire, - string mms_from, - string mms_url, - string from, - int concat, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int validity) { - return base.Channel.ind_push(session_id, api_id, user, password, to, mms_subject, mms_class, mms_expire, mms_from, mms_url, from, concat, deliv_ack, msg_callback, deliv_time, max_credits, req_feat, queue, escalate, mo, cliMsgId, validity); - } - - public string token_pay(string session_id, int api_id, string user, string password, string token) { - return base.Channel.token_pay(session_id, api_id, user, password, token); - } - - public string[] startbatch( - string session_id, - int api_id, - string user, - string password, - string from, - int concat, - string template, - int deliv_ack, - int msg_callback, - int deliv_time, - float max_credits, - int req_feat, - int queue, - int escalate, - int mo, - string cliMsgId, - int unicode, - string msg_type, - string udh, - string data, - int validity) { - return base.Channel.startbatch(session_id, api_id, user, password, from, concat, template, deliv_ack, msg_callback, deliv_time, max_credits, req_feat, queue, escalate, mo, cliMsgId, unicode, msg_type, udh, data, validity); - } - - public string[] senditem(string session_id, int api_id, string user, string password, string batch_id, string[] to, string field1, string field2, string field3, string field4) { - return base.Channel.senditem(session_id, api_id, user, password, batch_id, to, field1, field2, field3, field4); - } - - public string[] quicksend(string session_id, int api_id, string user, string password, string batch_id, string[] to) { - return base.Channel.quicksend(session_id, api_id, user, password, batch_id, to); - } - - public string[] endbatch(string session_id, int api_id, string user, string password, string batch_id) { - return base.Channel.endbatch(session_id, api_id, user, password, batch_id); - } - - public string[] getmsgcharge(string session_id, int api_id, string user, string password, string apimsgid) { - return base.Channel.getmsgcharge(session_id, api_id, user, password, apimsgid); - } - } -} diff --git a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/Reference.svcmap b/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/Reference.svcmap deleted file mode 100644 index 8be5b42fe4..0000000000 --- a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/Reference.svcmap +++ /dev/null @@ -1,30 +0,0 @@ - - - - false - true - - false - true - false - - - true - Auto - true - true - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/configuration.svcinfo b/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/configuration.svcinfo deleted file mode 100644 index 1de01519f2..0000000000 --- a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/configuration.svcinfo +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/configuration91.svcinfo b/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/configuration91.svcinfo deleted file mode 100644 index 0bfeb2c6fc..0000000000 --- a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/configuration91.svcinfo +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - PushServerWSBinding - - - 00:01:00 - - - 00:01:00 - - - 00:10:00 - - - 00:01:00 - - - False - - - False - - - StrongWildcard - - - 65536 - - - 524288 - - - 65536 - - - Text - - - - - - System.ServiceModel.Configuration.XmlDictionaryReaderQuotasElement - - - 32 - - - 8192 - - - 16384 - - - 4096 - - - 16384 - - - System.ServiceModel.Configuration.BasicHttpSecurityElement - - - None - - - System.ServiceModel.Configuration.HttpTransportSecurityElement - - - None - - - None - - - System.Security.Authentication.ExtendedProtection.Configuration.ExtendedProtectionPolicyElement - - - Never - - - TransportSelected - - - (Collection) - - - - - - System.ServiceModel.Configuration.BasicHttpMessageSecurityElement - - - UserName - - - Default - - - System.Text.UTF8Encoding - - - Buffered - - - True - - - - - - - - - http://api.clickatell.com/soap/webservice_vs.php - - - - - - basicHttpBinding - - - PushServerWSBinding - - - Clickatell.PushServerWSPortType - - - System.ServiceModel.Configuration.AddressHeaderCollectionElement - - - <Header /> - - - System.ServiceModel.Configuration.IdentityElement - - - System.ServiceModel.Configuration.UserPrincipalNameElement - - - - - - System.ServiceModel.Configuration.ServicePrincipalNameElement - - - - - - System.ServiceModel.Configuration.DnsElement - - - - - - System.ServiceModel.Configuration.RsaElement - - - - - - System.ServiceModel.Configuration.CertificateElement - - - - - - System.ServiceModel.Configuration.CertificateReferenceElement - - - My - - - LocalMachine - - - FindBySubjectDistinguishedName - - - - - - False - - - PushServerWSPort - - - - - - - - - - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/webservice_vs.wsdl b/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/webservice_vs.wsdl deleted file mode 100644 index e5b432174f..0000000000 --- a/src/Plugins/SmartStore.Clickatell/Service References/Clickatell/webservice_vs.wsdl +++ /dev/null @@ -1,440 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/SmartStore.Clickatell.csproj b/src/Plugins/SmartStore.Clickatell/SmartStore.Clickatell.csproj index f7e9ee7a8e..decee9a5f7 100644 --- a/src/Plugins/SmartStore.Clickatell/SmartStore.Clickatell.csproj +++ b/src/Plugins/SmartStore.Clickatell/SmartStore.Clickatell.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.Clickatell SmartStore.Clickatell - v4.5.1 + v4.6.1 512 @@ -42,6 +43,8 @@ + + true @@ -83,6 +86,11 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False + + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False @@ -94,32 +102,32 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - @@ -137,11 +145,6 @@ - - True - True - Reference.svcmap - @@ -158,12 +161,11 @@ SmartStore.Web.Framework - - - - + + PreserveNewest + PreserveNewest @@ -174,26 +176,11 @@ PreserveNewest - - - - - - - - - - - - WCF Proxy Generator - Reference.cs - - PreserveNewest - + PreserveNewest diff --git a/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml b/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml index 03b16cb275..09992e2c48 100644 --- a/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml +++ b/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml @@ -4,20 +4,40 @@ Layout = ""; } -
- - @Html.Raw(@T("Plugins.Sms.Clickatell.AdminInstruction")) +
+
+
+
+
+ @Html.Raw(@T("Plugins.Sms.Clickatell.AdminInstruction")) + +
+
+
+ + Clickatell + +
+
+ +@{ Html.RenderAction("StoreScopeConfiguration", "Setting", new { area = "Admin" }); } + +@Html.ValidationSummary(false) + @using (Html.BeginForm()) { - +
@@ -26,7 +46,7 @@ @Html.SmartLabelFor(model => model.PhoneNumber) @@ -35,38 +55,10 @@ @Html.SmartLabelFor(model => model.ApiId) - - - - - - - - - - - - + @if (Model.TestSmsResult.HasValue()) + { + + + + + } -
@Html.SmartLabelFor(model => model.Enabled) - @Html.EditorFor(model => model.Enabled) + @Html.SettingEditorFor(model => model.Enabled) @Html.ValidationMessageFor(model => model.Enabled)
- @Html.EditorFor(model => model.PhoneNumber) + @Html.SettingEditorFor(model => model.PhoneNumber) @Html.ValidationMessageFor(model => model.PhoneNumber)
- @Html.EditorFor(model => model.ApiId) + @Html.SettingEditorFor(model => model.ApiId) @Html.ValidationMessageFor(model => model.ApiId)
- @Html.SmartLabelFor(model => model.Username) - - @Html.EditorFor(model => model.Username) - @Html.ValidationMessageFor(model => model.Username) -
- @Html.SmartLabelFor(model => model.Password) - - @Html.EditorFor(model => model.Password) - @Html.ValidationMessageFor(model => model.Password) -
-   - - -
@@ -83,23 +75,31 @@ @Html.ValidationMessageFor(model => model.TestMessage)
+   + +
+ @Model.TestSmsResult @Model.TestSmsDetailResult + +
+
  -
- - if (!String.IsNullOrEmpty(Model.TestSmsResult)) - { -
- - @Model.TestSmsResult -
- } -} \ No newline at end of file + +} + diff --git a/src/Plugins/SmartStore.Clickatell/Views/Web.config b/src/Plugins/SmartStore.Clickatell/Views/Web.config index 31b7dee187..03fa30c983 100644 --- a/src/Plugins/SmartStore.Clickatell/Views/Web.config +++ b/src/Plugins/SmartStore.Clickatell/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.Clickatell/icon.png b/src/Plugins/SmartStore.Clickatell/icon.png deleted file mode 100644 index 66adb4e415..0000000000 Binary files a/src/Plugins/SmartStore.Clickatell/icon.png and /dev/null differ diff --git a/src/Plugins/SmartStore.Clickatell/packages.config b/src/Plugins/SmartStore.Clickatell/packages.config index 4bc06fc1a7..c716e80c1a 100644 --- a/src/Plugins/SmartStore.Clickatell/packages.config +++ b/src/Plugins/SmartStore.Clickatell/packages.config @@ -1,7 +1,8 @@  - - - + + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/web.config b/src/Plugins/SmartStore.Clickatell/web.config index ba87d0f098..522d79a602 100644 --- a/src/Plugins/SmartStore.Clickatell/web.config +++ b/src/Plugins/SmartStore.Clickatell/web.config @@ -1,116 +1,148 @@ - + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.DevTools/AdminMenu.cs b/src/Plugins/SmartStore.DevTools/AdminMenu.cs index 25ae68e381..8110443480 100644 --- a/src/Plugins/SmartStore.DevTools/AdminMenu.cs +++ b/src/Plugins/SmartStore.DevTools/AdminMenu.cs @@ -9,7 +9,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) { var menuItem = new MenuItem().ToBuilder() .Text("Developer Tools") - .Icon("code") + .Icon("far fa-terminal") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.DevTools", area = "Admin" }) .ToItem(); @@ -18,11 +18,23 @@ protected override void BuildMenuCore(TreeNode pluginsNode) // uncomment to add to admin menu (see plugin sub-menu) //var backendExtensionItem = new MenuItem().ToBuilder() // .Text("Backend extension") - // .Icon("area-chart") + // .Icon("chart-area") // .Action("BackendExtension", "DevTools", new { area = "SmartStore.DevTools" }) // .ToItem(); - //pluginsNode.Append(backendExtensionItem); + + // uncomment to add a sub-menu (see plugin sub-menu) + //var subMenu = new MenuItem().ToBuilder() + // .Text("Sub Menu") + // .Action("BackendExtension", "DevTools", new { area = "SmartStore.DevTools" }) + // .ToItem(); + //var subMenuNode = pluginsNode.Append(subMenu); + + //var subMenuItem = new MenuItem().ToBuilder() + // .Text("Sub Menu Item 1") + // .Action("BackendExtension", "DevTools", new { area = "SmartStore.DevTools" }) + // .ToItem(); + //subMenuNode.Append(subMenuItem); } } } diff --git a/src/Plugins/SmartStore.DevTools/Blocks/SampleBlock.cs b/src/Plugins/SmartStore.DevTools/Blocks/SampleBlock.cs new file mode 100644 index 0000000000..a089af9c47 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Blocks/SampleBlock.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using FluentValidation; +using FluentValidation.Attributes; +using Newtonsoft.Json; +using SmartStore.Services.Cms.Blocks; +using SmartStore.Web.Framework; + +namespace SmartStore.DevTools.Blocks +{ + /// + /// The block handler is the controller which is responsible for loading, instantiating, storing and rendering block types. + /// The 'BlockHandlerBase' abstract class already implements all relevant parts. + /// You can, however, overwrite any method to fulfill your custom needs. + /// + [Block("sample", Icon = "far fa-terminal", FriendlyName = "Sample", DisplayOrder = 50, IsInternal = true)] // REMOVE IsInternal = true to display the block in the page builder + public class SampleBlockHandler : BlockHandlerBase + { + public override SampleBlock Load(IBlockEntity entity, StoryViewMode viewMode) + { + var block = base.Load(entity, viewMode); + + // By default 'BlockHandlerBase' stores block instance data as JSON in the 'Model' field of the 'PageStoryBlock' table. + // You can, however, store data anywhere you like and override loading behaviour in this method. + + if (viewMode == StoryViewMode.Edit) + { + // You can prepare your model especially for the edit mode of the block, + // e.g. add some SelectListItems for dropdownmenus which you will only need in the edit mode of the block + block.MyProperties.Add(new SelectListItem { Text = "Item1", Value = "1" }); + } + else if (viewMode == StoryViewMode.GridEdit) + { + // Manipulate properties especially for the grid edit mode e.g. turn of any animation which could distract the user from editing the grid. + //block.Autoplay = false; + } + + return block; + } + + public override void Save(SampleBlock block, IBlockEntity entity) + { + // By default 'BlockHandlerBase' stores block instance data as JSON in the 'Model' field of the 'PageStoryBlock' table. + // You can, however, store data anywhere you like and override persistance behaviour in this method. + + base.Save(block, entity); + } + + /// + /// By default block templates (Edit & Public) will be searched in '{Area}\Views\Story\BlockTemplates\{BlockSystemName}' or '{Area}\Views\Shared\BlockTemplates\{BlockSystemName}', + /// while {Area} represents your plugin folder. + /// The public action can address a deviating route by overwriting RenderCore & GetRoute. + /// You can override this behaviour by e.g. calling a child action in your plugin controller instead of directly rendering a view. + /// For this to take effect you have to override both methods 'RenderCore()' and 'GetRoute()' + /// + //protected override void RenderCore(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper, TextWriter textWriter) + //{ + // if (templates.First() == "Edit") + // { + // base.RenderCore(element, templates, htmlHelper, textWriter); + // } + // else + // { + // base.RenderByChildAction(element, templates, htmlHelper, textWriter); + // } + //} + + //protected override RouteInfo GetRoute(IBlockContainer element, string template) + //{ + // var block = (SampleBlock)element.Block; + // return new RouteInfo("SampleBlock", "DevTools", new + // { + // block = element.Block, + // area = "SmartStore.DevTools" + // }); + //} + } + + /// + /// Any type that implements the 'IBlock' interface acts as a model. + /// + [Validator(typeof(SampleBlockValidator))] + public class SampleBlock : IBlock + { + [SmartResourceDisplayName("Plugins.SmartStore.DevTools.Block.Property")] + public string Property { get; set; } + + // By default a block instance will be converted to JSON and stored in the 'Model' field of the 'PageStoryBlock' table. + // If your block type contains some special properties - e.g. volatile data for the edit mode - and you don't want them to be persisted, add the [JsonIgnore] attribute to your property. + [JsonIgnore] + public IList MyProperties { get; set; } + } + + /// + /// This is the validator which is used to validate the user input while editing your block + /// + public partial class SampleBlockValidator : AbstractValidator + { + public SampleBlockValidator() + { + RuleFor(x => x.Property).NotEmpty(); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs index a0813cc36b..28827a7307 100644 --- a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs +++ b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs @@ -1,57 +1,48 @@ using System.Web.Mvc; +using SmartStore.DevTools.Blocks; using SmartStore.DevTools.Models; using SmartStore.Services; +using SmartStore.Services.Cms.Blocks; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; +using SmartStore.Web.Framework.Theming; namespace SmartStore.DevTools.Controllers { - - public class DevToolsController : SmartController + public class DevToolsController : SmartController { - private readonly ICommonServices _services; - - public DevToolsController(ICommonServices services) - { - _services = services; - } - - [AdminAuthorize, ChildActionOnly] - public ActionResult Configure() - { - // load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); - - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, settings, storeScope, _services.Settings); + private readonly ICommonServices _services; - return View(settings); - } - - [HttpPost, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(ProfilerSettings model, FormCollection form) - { - if (!ModelState.IsValid) - return Configure(); - - ModelState.Clear(); + public DevToolsController(ICommonServices services) + { + _services = services; + } - // load settings for a chosen store scope - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + [LoadSetting, ChildActionOnly] + public ActionResult Configure(ProfilerSettings settings) + { + return View(settings); + } - storeDependingSettingHelper.UpdateSettings(model /*settings*/, form, storeScope, _services.Settings); + [SaveSetting(false), HttpPost, ChildActionOnly, ActionName("Configure")] + public ActionResult ConfigurePost(ProfilerSettings settings) + { + return RedirectToConfiguration("SmartStore.DevTools"); + } - return Configure(); - } + public ActionResult MiniProfiler() + { + return View(); + } - public ActionResult MiniProfiler() - { - return View(); - } + public ActionResult MachineName() + { + ViewBag.EnvironmentIdentifier = _services.ApplicationEnvironment.EnvironmentIdentifier; + return View(); + } + public ActionResult WidgetZone(string widgetZone) { var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); @@ -67,7 +58,14 @@ public ActionResult WidgetZone(string widgetZone) return new EmptyResult(); } - [AdminAuthorize] + [ChildActionOnly] + public ActionResult SampleBlock(SampleBlock block) + { + // Do something here with your block instance and return a result that should be rendered by the Page Builder. + return View(block); + } + + [AdminAuthorize, AdminThemed] public ActionResult BackendExtension() { var model = new BackendExtensionModel @@ -77,5 +75,23 @@ public ActionResult BackendExtension() return View(model); } + + [AdminAuthorize] + public ActionResult ProductEditTab(int productId, FormCollection form) + { + var model = new BackendExtensionModel + { + Welcome = "Hello world!" + }; + + var result = PartialView(model); + result.ViewData.TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "CustomProperties[DevTools]" }; + return result; + } + + public ActionResult MyDemoWidget() + { + return Content("Hello world! This is a sample widget created for demonstration purposes by Dev-Tools plugin."); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs index e381fbabc9..761f7820a2 100644 --- a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs @@ -3,8 +3,10 @@ using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Core.Logging; using SmartStore.DevTools.Filters; using SmartStore.DevTools.Services; +using SmartStore.Web.Controllers; using SmartStore.Web.Framework.Controllers; namespace SmartStore.DevTools @@ -13,19 +15,28 @@ public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { - builder.RegisterType().As().InstancePerRequest(); - - if (isActiveModule && DataSettings.DatabaseIsInstalled()) + if (isActiveModule) { - // intercept ALL public store controller actions - builder.RegisterType().AsActionFilterFor(); - builder.RegisterType().AsActionFilterFor(); + builder.RegisterType().As().InstancePerRequest(); + + if (DataSettings.DatabaseIsInstalled()) + { + // intercept ALL public store controller actions + builder.RegisterType().AsActionFilterFor(); + builder.RegisterType().AsActionFilterFor(); + builder.RegisterType().AsResultFilterFor(); - //// intercept CatalogController's Product action - //builder.RegisterType().AsResultFilterFor(x => x.Product(default(int), default(string))).InstancePerRequest(); - //builder.RegisterType().AsActionFilterFor().InstancePerRequest(); - //// intercept CheckoutController's Index action (to hijack the checkout or payment workflow) - //builder.RegisterType().AsActionFilterFor(x => x.Index()).InstancePerRequest(); + // Add an action to product detail offer actions + //builder.RegisterType() + // .AsActionFilterFor(x => x.ProductDetails(default(int), default(string), null)) + // .InstancePerRequest(); + + //// intercept CatalogController's Product action + //builder.RegisterType().AsResultFilterFor(x => x.Product(default(int), default(string))).InstancePerRequest(); + //builder.RegisterType().AsActionFilterFor().InstancePerRequest(); + //// intercept CheckoutController's Index action (to hijack the checkout or payment workflow) + //builder.RegisterType().AsActionFilterFor(x => x.Index()).InstancePerRequest(); + } } } diff --git a/src/Plugins/SmartStore.DevTools/Description.txt b/src/Plugins/SmartStore.DevTools/Description.txt index b169d8b16a..54399e9fcf 100644 --- a/src/Plugins/SmartStore.DevTools/Description.txt +++ b/src/Plugins/SmartStore.DevTools/Description.txt @@ -1,8 +1,8 @@ FriendlyName: SmartStore.NET Developer Tools (MiniProfiler and other goodies) SystemName: SmartStore.DevTools Group: Developer -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 1 FileName: SmartStore.DevTools.dll ResourceRootKey: Plugins.Developer.DevTools \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs b/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs index 10e0592a42..6001b8b582 100644 --- a/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs +++ b/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs @@ -1,26 +1,50 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web.Routing; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Services.Common; +using SmartStore.Data; +using SmartStore.Data.Setup; using SmartStore.Services.Configuration; +using SmartStore.Core.Caching; namespace SmartStore.DevTools { - public class DevToolsPlugin : BasePlugin, IConfigurable - { + [DisplayOrder(10)] + [SystemName("Widgets.DevToolsDemo")] + [FriendlyName("Dev-Tools Demo Widget")] + public class DevToolsPlugin : BasePlugin, IConfigurable //, IWidget + { private readonly ISettingService _settingService; + private readonly ICacheableRouteRegistrar _cacheableRouteRegistrar; - public DevToolsPlugin(ISettingService settingService) + public DevToolsPlugin(ISettingService settingService, + ICacheableRouteRegistrar cacheAbleRouteRegistrar) { - this._settingService = settingService; + _settingService = settingService; + _cacheableRouteRegistrar = cacheAbleRouteRegistrar; + this.Logger = NullLogger.Instance; } public ILogger Logger { get; set; } - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + //public IList GetWidgetZones() => new List { "home_page_top" }; + + //public void GetDisplayWidgetRoute(string widgetZone, object model, int storeId, out string actionName, out string controllerName, out RouteValueDictionary routeValues) + //{ + // actionName = "MyDemoWidget"; + // controllerName = "DevTools"; + + // routeValues = new RouteValueDictionary + // { + // { "Namespaces", "SmartStore.DevTools.Controllers" }, + // { "area", "SmartStore.DevTools" } + // }; + //} + + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; controllerName = "DevTools"; @@ -29,9 +53,13 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa public override void Install() { + // Example for how to add a route to the output cache + //_cacheableRouteRegistrar.RegisterCacheableRoute("SmartStore.DevTools/DevTools/PublicInfo"); + _settingService.SaveSetting(new ProfilerSettings()); base.Install(); - Logger.Information(string.Format("Plugin installed: SystemName: {0}, Version: {1}, Description: '{2}'", PluginDescriptor.SystemName, PluginDescriptor.Version, PluginDescriptor.FriendlyName)); + + Logger.Info(string.Format("Plugin installed: SystemName: {0}, Version: {1}, Description: '{2}'", PluginDescriptor.SystemName, PluginDescriptor.Version, PluginDescriptor.FriendlyName)); } /// @@ -39,8 +67,39 @@ public override void Install() /// public override void Uninstall() { + // Example for how to remove a route from the output cache + //_cacheableRouteRegistrar.RemoveCacheableRoute("SmartStore.DevTools/DevTools/PublicInfo"); + _settingService.DeleteSetting(); base.Uninstall(); } - } + + private static bool? _hasPendingMigrations; + internal static bool HasPendingMigrations() + { + bool result = true; + + if (_hasPendingMigrations == null) + { + try + { + var migrator = new DbSeedingMigrator(); + result = migrator.GetPendingMigrations().Any(); + + if (result == false) + { + // Don't check again + _hasPendingMigrations = false; + } + } + catch { } + } + else + { + result = _hasPendingMigrations.Value; + } + + return result; + } + } } diff --git a/src/Plugins/SmartStore.DevTools/Events/CustomTab.cs b/src/Plugins/SmartStore.DevTools/Events/CustomTab.cs new file mode 100644 index 0000000000..cb5f929564 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Events/CustomTab.cs @@ -0,0 +1,57 @@ +using SmartStore.Core.Events; +using SmartStore.DevTools.Models; +using SmartStore.Services; +using SmartStore.Web.Framework.Events; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.DevTools.Events +{ + public class CustomTab : IConsumer + { + private readonly ICommonServices _services; + + public CustomTab(ICommonServices services) + { + _services = services; + } + + public void HandleEvent(TabStripCreated eventMessage) + { + // add a form to product detail configuration + if (eventMessage.TabStripName == "product-edit") + { + var productId = ((TabbableModel)eventMessage.Model).Id; + + // add in a predefined tab "Plugins" which serves as container for plugins to obtain data + + //eventMessage.AddWidget(new RouteInfo( + // "ProductEditTab", + // "DevTools", + // new { area = "SmartStore.DevTools", productId = productId } + //)); + + // add in an own tab + + //eventMessage.ItemFactory.Add().Text("Dev Tools") + // .Name("tab-dt") + // .Icon("fa fa-code fa-lg fa-fw") + // .LinkHtmlAttributes(new { data_tab_name = "DevTools" }) + // .Route("SmartStore.DevTools", new { action = "ProductEditTab", productId = productId }) + // .Ajax(); + + } + } + + public void HandleEvent(ModelBoundEvent eventMessage) + { + if (!eventMessage.BoundModel.CustomProperties.ContainsKey("DevTools")) + return; + + var model = eventMessage.BoundModel.CustomProperties["DevTools"] as BackendExtensionModel; + if (model == null) + return; + + // Do something with the model now: e.g. store it ;-) + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Examples/OutputCacheInvalidationObserver.cs b/src/Plugins/SmartStore.DevTools/Examples/OutputCacheInvalidationObserver.cs new file mode 100644 index 0000000000..9c5bcda2a3 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Examples/OutputCacheInvalidationObserver.cs @@ -0,0 +1,89 @@ +using SmartStore.Core; +using SmartStore.Core.Caching; +using SmartStore.Core.Infrastructure; +using SmartStore.Services; +using SmartStore.Services.Catalog; +using SmartStore.Services.Topics; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.DevTools.Examples +{ + public static class OutputCacheInvalidationObserver + { + public static void Execute() + { + // Register a handler which returns unique string tags for your custom entity. + // This is required if you ever call IDisplayControl.Announce(entity) for your entity, + // otherwise the system is not able to generate tags. + // Tags can be obtained via IDisplayControl.GetCacheControlTagsFor(entity). + // Invalidation by tags is done via IOutputCacheProvider.InvalidateByTag(string[] tags). + // InvalidateByTag() will remove any page from the output cache storage in which your custom entity is somehow displayed. + DisplayControl.RegisterHandlerFor(typeof(MyRecord), (x, c) => new[] { "mr" + x.Id }); + + // Register an output cache observe handler + var observer = EngineContext.Current.Resolve(); + observer.ObserveEntity(Observe); + } + + private static void Observe(ObserveEntityContext ctx) + { + // If your plugin renders dynamic content to the frontend which won't change often once it is configured, you should add a corresponding route + // to the ouput cache to add the rendered output of the plugin to the cache. For more information on this see the Install/Uninstall method of this plugin + + // In the example below we assume the plugin has own entities which depend on system entities like Product, Category or Topic. + // In this case you must invalidate the cache items for the system entities as soon as the corresponding plugin entity is changed by the shop admin + // in order to remove the cached output of the affected pages in the frontend and thus be rebuilt + + var myRecord = ctx?.Entity as MyRecord; + + if (myRecord != null) + { + IEnumerable tags = null; + + var outputCacheProvider = ctx.OutputCacheProvider; + + // We assume the domain record from the plugin stores information about the type of the entity in EntityName and the corresponding Id in EntityId + var entityname = myRecord.EntityName; + + // Collect the tags for the entities which must be invalidated due to changes of the plugin domain record + switch (entityname.ToLower()) + { + case "product": + var product = ctx.ServiceContainer.Resolve().GetProductById(myRecord.EntityId); + if (product != null) tags = ctx.DisplayControl.GetCacheControlTagsFor(product); + break; + case "category": + var category = ctx.ServiceContainer.Resolve().GetCategoryById(myRecord.EntityId); + if (category != null) tags = ctx.DisplayControl.GetCacheControlTagsFor(category); + break; + case "topic": + var topic = ctx.ServiceContainer.Resolve().GetTopicById(myRecord.EntityId); + if (topic != null) tags = ctx.DisplayControl.GetCacheControlTagsFor(topic); + break; + } + + // Invalidate cache items by the collected tags + if (tags != null && tags.Any()) + { + outputCacheProvider.InvalidateByTag(tags.ToArray()); + ctx.Handled = true; + } + } + } + + internal class MyRecord : BaseEntity + { + public int EntityId + { + get { return 0; } + } + + public string EntityName + { + get { return String.Empty; } + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs new file mode 100644 index 0000000000..c71e82d5fe --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs @@ -0,0 +1,56 @@ +using System; +using System.Web.Mvc; +using SmartStore.Services; +using SmartStore.Services.Customers; +using SmartStore.Web.Framework.UI; + +namespace SmartStore.DevTools.Filters +{ + public class MachineNameFilter : IResultFilter + { + private readonly ICommonServices _services; + private readonly Lazy _widgetProvider; + private readonly ProfilerSettings _profilerSettings; + + public MachineNameFilter( + ICommonServices services, + Lazy widgetProvider, + ProfilerSettings profilerSettings) + { + _services = services; + _widgetProvider = widgetProvider; + _profilerSettings = profilerSettings; + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + if (!_profilerSettings.DisplayMachineName) + return; + + if (filterContext.IsChildAction) + return; + + var result = filterContext.Result; + + // should only run on a full view rendering result or HTML ContentResult + if (!result.IsHtmlViewResult()) + return; + + if (!_services.WorkContext.CurrentCustomer.IsAdmin() && !filterContext.HttpContext.Request.IsLocal) + { + return; + } + + _widgetProvider.Value.RegisterAction( + new[] { "body_end_html_tag_before", "admin_content_after" }, + "MachineName", + "DevTools", + new { area = "SmartStore.DevTools" }); + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + } + + } +} diff --git a/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs index 2971dabe08..5822618bbd 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/ProfilerFilter.cs @@ -1,90 +1,80 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Web; using System.Web.Mvc; -using SmartStore.Core.Logging; -using SmartStore.Core.Localization; -using SmartStore.DevTools.Services; -using SmartStore.Core; using SmartStore.Services; using SmartStore.Services.Customers; using SmartStore.Web.Framework.UI; +using SmartStore.Services.Common; namespace SmartStore.DevTools.Filters { public class ProfilerFilter : IActionFilter, IResultFilter { - private readonly Lazy _profiler; private readonly ICommonServices _services; private readonly Lazy _widgetProvider; - private readonly ProfilerSettings _profilerSettings; + private readonly Lazy _profilerSettings; + private readonly Lazy _mobileDeviceHelper; public ProfilerFilter( - Lazy profiler, ICommonServices services, - Lazy widgetProvider, - ProfilerSettings profilerSettings) + Lazy widgetProvider, + Lazy profilerSettings, + Lazy mobileDeviceHelper) { - this._profiler = profiler; - this._services = services; - this._widgetProvider = widgetProvider; - this._profilerSettings = profilerSettings; + _services = services; + _widgetProvider = widgetProvider; + _profilerSettings = profilerSettings; + _mobileDeviceHelper = mobileDeviceHelper; } public void OnActionExecuting(ActionExecutingContext filterContext) { - if (!_profilerSettings.EnableMiniProfilerInPublicStore) + if (!_profilerSettings.Value.EnableMiniProfilerInPublicStore) return; var tokens = filterContext.RouteData.DataTokens; string area = tokens.ContainsKey("area") && !string.IsNullOrEmpty(tokens["area"].ToString()) ? string.Concat(tokens["area"], ".") : string.Empty; + string controller = string.Concat(filterContext.Controller.ToString().Split('.').Last(), "."); string action = filterContext.ActionDescriptor.ActionName; - this._profiler.Value.StepStart("ActionFilter", "Action: " + area + controller + action); + + _services.Chronometer.StepStart("ActionFilter", "Action: " + area + controller + action); } public void OnActionExecuted(ActionExecutedContext filterContext) { - if (!_profilerSettings.EnableMiniProfilerInPublicStore) + if (!_profilerSettings.Value.EnableMiniProfilerInPublicStore) return; - if (!(filterContext.Result is ViewResultBase)) - { - this._profiler.Value.StepStop("ActionFilter"); - } + if (!filterContext.Result.IsHtmlViewResult()) + _services.Chronometer.StepStop("ActionFilter"); } public void OnResultExecuting(ResultExecutingContext filterContext) { - if (!_profilerSettings.EnableMiniProfilerInPublicStore) + if (!_profilerSettings.Value.EnableMiniProfilerInPublicStore) return; // should only run on a full view rendering result - var result = filterContext.Result as ViewResultBase; - if (result == null) - { + if (!filterContext.Result.IsHtmlViewResult()) return; - } if (!this.ShouldProfile(filterContext.HttpContext)) - { return; - } - var viewName = result.ViewName; + var viewResult = filterContext.Result as ViewResultBase; + + string viewName = viewResult?.ViewName; if (viewName.IsEmpty()) { string action = (filterContext.RouteData.Values["action"] as string).EmptyNull(); viewName = action; } - this._profiler.Value.StepStart("ResultFilter", string.Format("{0}: {1}", result is PartialViewResult ? "Partial" : "View", viewName)); + _services.Chronometer.StepStart("ResultFilter", string.Format("{0}: {1}", viewResult is PartialViewResult ? "Partial" : "View", viewName)); if (!filterContext.IsChildAction) { @@ -98,33 +88,29 @@ public void OnResultExecuting(ResultExecutingContext filterContext) public void OnResultExecuted(ResultExecutedContext filterContext) { - if (!_profilerSettings.EnableMiniProfilerInPublicStore) + if (!_profilerSettings.Value.EnableMiniProfilerInPublicStore) return; - + // should only run on a full view rendering result - if (!(filterContext.Result is ViewResultBase)) - { + if (!filterContext.Result.IsHtmlViewResult()) return; - } if (!this.ShouldProfile(filterContext.HttpContext)) - { return; - } - this._profiler.Value.StepStop("ResultFilter"); - this._profiler.Value.StepStop("ActionFilter"); + _services.Chronometer.StepStop("ResultFilter"); + _services.Chronometer.StepStop("ActionFilter"); } private bool ShouldProfile(HttpContextBase ctx) { + if (_mobileDeviceHelper.Value.IsMobileDevice()) + return false; + if (!_services.WorkContext.CurrentCustomer.IsAdmin()) - { return ctx.Request.IsLocal; - } return true; } - } } diff --git a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleActionFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleActionFilter.cs index e1cb06dc72..3c010c46fd 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleActionFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleActionFilter.cs @@ -24,7 +24,7 @@ public void OnActionExecuting(ActionExecutingContext filterContext) { Debug.WriteLine("Executing: {0} - {1}".FormatInvariant(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName)); _notifier.Information("Yeah, my plugin action filter works. NICE!"); - // Do somethid meaningful here ;-) + // Do something meaningful here ;-) } public void OnActionExecuted(ActionExecutedContext filterContext) diff --git a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs new file mode 100644 index 0000000000..fb4307fc1a --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Logging; +using SmartStore.Core.Localization; +using SmartStore.Web.Models.Catalog; +using SmartStore.Services; + +namespace SmartStore.DevTools.Filters +{ + public class SampleProductDetailActionFilter : IActionFilter + { + private readonly ICommonServices _services; + private readonly UrlHelper _urlHelper; + + public SampleProductDetailActionFilter(ICommonServices services, UrlHelper urlHelper) + { + _services = services; + _urlHelper = urlHelper; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + var result = filterContext.Result as ViewResultBase; + if (result == null) + { + // The controller action didn't return a view result + // => no need to continue any further + return; + } + + var model = result.Model as ProductDetailsModel; + if (model == null) + { + // there's no model or the model was not of the expected type + // => no need to continue any further + return; + } + + // modify some property value + model.ActionItems["dev"] = new ProductDetailsModel.ActionItemModel + { + Key = "dev", + Title = _services.Localization.GetResource("Dev"), + Tooltip = _services.Localization.GetResource("Dev.Hint"), + CssClass = "action-dev x-ajax-cart-link", + IconCssClass = "icm icm-code", + //Href = _urlHelper.Action("MyOwnAction", "MyPlugin", new { id = model.Id }) + Href = "https://www.smartstore.com" + }; ; + } + } +} diff --git a/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs index 253fdd0b60..92b04c94b6 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/WidgetZoneFilter.cs @@ -15,6 +15,7 @@ using SmartStore.Web.Framework.UI; using SmartStore.Utilities; using System.IO; +using SmartStore.Core.Caching; namespace SmartStore.DevTools.Filters { @@ -23,34 +24,40 @@ public class WidgetZoneFilter : IActionFilter, IResultFilter private readonly ICommonServices _services; private readonly Lazy _widgetProvider; private readonly ProfilerSettings _profilerSettings; + private readonly IDisplayControl _displayControl; - public WidgetZoneFilter( + public WidgetZoneFilter( ICommonServices services, Lazy widgetProvider, - ProfilerSettings profilerSettings) + ProfilerSettings profilerSettings, + IDisplayControl displayControl) { - this._services = services; - this._widgetProvider = widgetProvider; - this._profilerSettings = profilerSettings; + _services = services; + _widgetProvider = widgetProvider; + _profilerSettings = profilerSettings; + _displayControl = displayControl; } public void OnActionExecuting(ActionExecutingContext filterContext) { + if (filterContext.IsChildAction) + return; + if (!_profilerSettings.DisplayWidgetZones) return; + + _displayControl.MarkRequestAsUncacheable(); } public void OnActionExecuted(ActionExecutedContext filterContext) { - if (!_profilerSettings.DisplayWidgetZones) - return; } public void OnResultExecuting(ResultExecutingContext filterContext) { if (!_profilerSettings.DisplayWidgetZones) return; - + // should only run on a full view rendering result var result = filterContext.Result as ViewResultBase; if (result == null) @@ -71,7 +78,7 @@ public void OnResultExecuting(ResultExecutingContext filterContext) "DevTools", new { area = "SmartStore.DevTools" }); } - + var viewName = result.ViewName; if (viewName.IsEmpty()) @@ -87,26 +94,17 @@ public void OnResultExecuting(ResultExecutingContext filterContext) { ViewName = "~/Plugins/SmartStore.DevTools/Views/DevTools/WidgetZone.cshtml", }; - filterContext.RouteData.Values.Add("widgetZone", model.WidgetZone); + + if(filterContext.RouteData.Values["widgetZone"] == null) + { + filterContext.RouteData.Values.Add("widgetZone", model.WidgetZone); + } } } } public void OnResultExecuted(ResultExecutedContext filterContext) { - if (!_profilerSettings.DisplayWidgetZones) - return; - - // should only run on a full view rendering result - if (!(filterContext.Result is ViewResultBase)) - { - return; - } - - if (!this.ShouldRender(filterContext.HttpContext)) - { - return; - } } private bool ShouldRender(HttpContextBase ctx) diff --git a/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml b/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml new file mode 100644 index 0000000000..67cf6df4a5 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml @@ -0,0 +1,24 @@ + + + Dev-Tools Demo Widget + + + + Mini-Profiler für Frontend aktivieren + + + Aktiviert den Mini-Profiler für das Frontend. Mit dem Profiler können Sie die Requests einer Seite, inklusive Datenbankzugriffen analysieren. Die Anzeige ist auf angemeldete Administratoren beschränkt. + + + Widget-Zonen darstellen + + + Injiziert Tooltips in jede Widget-Zone, die den Namen der jeweiligen Widget-Zone enthalten. Die Anzeige ist auf angemeldete Administratoren beschränkt. + + + Rechnernamen anzeigen + + + Stellt den Rechnernamen unterhalb des Footers dar. Mit dieser Funktion können Sie in einer verteilten Anwendung den gerade genutzten Rechner ermitteln. + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml b/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml new file mode 100644 index 0000000000..0ed80f9443 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml @@ -0,0 +1,24 @@ + + + Dev-Tools Demo Widget + + + + Enable Mini-Profiler in public store + + + Activates the Mini-Profiler for the Frontend. With its help you can analyse requests of a page, including database requests. The display of the profiler is limited to administrators. + + + Display widget zones + + + Injects tooltips into every widget zone, which includes the name of the current widget zone. The display of these tooltips is limited to admintrators. + + + Display machine name + + + Displays the current machine name below the footer. With this function you can determine the current machine within a distributed application. + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs b/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs index 2db540028f..7de7bbe5e1 100644 --- a/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs +++ b/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs @@ -5,5 +5,7 @@ namespace SmartStore.DevTools.Models public class BackendExtensionModel : ModelBase { public string Welcome { get; set; } - } + + public int ProductId { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs index c2e585d0eb..bf1ac188cf 100644 --- a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs +++ b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs @@ -6,21 +6,25 @@ namespace SmartStore.DevTools { - public class ProfilerHttpModule : IHttpModule { private const string MP_KEY = "sm.miniprofiler.started"; public void Init(HttpApplication context) { - context.BeginRequest += OnBeginRequest; + if (DevToolsPlugin.HasPendingMigrations()) + { + return; + } + + context.AcquireRequestState += OnAcquireRequestState; context.EndRequest += OnEndRequest; } - public static void OnBeginRequest(object sender, EventArgs e) + private static void OnAcquireRequestState(object sender, EventArgs e) { var app = (HttpApplication)sender; - if (ShouldProfile(app)) + if (!MiniProfilerStarted(app) && ShouldProfile(app)) { MiniProfiler.Start(); if (app.Context != null && app.Context.Items != null) @@ -30,18 +34,23 @@ public static void OnBeginRequest(object sender, EventArgs e) } } - public static void OnEndRequest(object sender, EventArgs e) + private static void OnEndRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; - if (app.Context != null && app.Context.Items != null && app.Context.Items.Contains(MP_KEY)) + if (MiniProfilerStarted(app)) { MiniProfiler.Stop(); } } + private static bool MiniProfilerStarted(HttpApplication app) + { + return app?.Context?.Items != null && app.Context.Items.Contains(MP_KEY); + } + private static bool ShouldProfile(HttpApplication app) { - if (app.Context == null || app.Context.Request == null) + if (app?.Context?.Request == null) return false; if (!DataSettings.DatabaseIsInstalled()) @@ -50,23 +59,30 @@ private static bool ShouldProfile(HttpApplication app) } var url = app.Context.Request.AppRelativeCurrentExecutionFilePath; - if (url.StartsWith("~/admin", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/mini-profiler", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/bundles", StringComparison.InvariantCultureIgnoreCase)) + if (url.StartsWith("~/admin", StringComparison.InvariantCultureIgnoreCase) + || url.StartsWith("~/mini-profiler", StringComparison.InvariantCultureIgnoreCase) + || url.StartsWith("~/bundles", StringComparison.InvariantCultureIgnoreCase) + || url.StartsWith("~/plugin/", StringComparison.InvariantCultureIgnoreCase) + || url.StartsWith("~/taskscheduler", StringComparison.InvariantCultureIgnoreCase)) { return false; } - ProfilerSettings settings; - if (!EngineContext.Current.ContainerManager.TryResolve(null, out settings)) - { - return false; - } + ProfilerSettings settings = null; - if (!settings.EnableMiniProfilerInPublicStore) + if (EngineContext.Current.IsFullyInitialized) { - return false; + try + { + settings = EngineContext.Current.Resolve(); + } + catch + { + return true; + } } - return true; + return settings == null ? true : settings.EnableMiniProfilerInPublicStore; } public void Dispose() @@ -74,5 +90,4 @@ public void Dispose() // nothing to dispose } } - } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Services/IProfilerService.cs b/src/Plugins/SmartStore.DevTools/Services/IProfilerService.cs deleted file mode 100644 index bf4a87f497..0000000000 --- a/src/Plugins/SmartStore.DevTools/Services/IProfilerService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; - -namespace SmartStore.DevTools.Services -{ - public interface IProfilerService - { - void StepStart(string key, string message); - void StepStop(string key); - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Services/MiniProfilerChronometer.cs b/src/Plugins/SmartStore.DevTools/Services/MiniProfilerChronometer.cs new file mode 100644 index 0000000000..153adf61cd --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Services/MiniProfilerChronometer.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using SmartStore.Core.Logging; +using StackExchange.Profiling; + +namespace SmartStore.DevTools.Services +{ + public class MiniProfilerChronometer : IChronometer + { + private readonly Dictionary> _steps = new Dictionary>(); + private MiniProfiler _profiler; + + public MiniProfilerChronometer() + { + _profiler = MiniProfiler.Current; + } + + protected MiniProfiler Profiler + { + get + { + return _profiler ?? (_profiler = MiniProfiler.Current); + } + } + + public void StepStart(string key, string message) + { + if (this.Profiler == null) + { + return; + } + + var stack = _steps.Get(key); + if (stack == null) + { + _steps[key] = stack = new Stack(); + } + + var step = Profiler.Step(message); + stack.Push(step); + } + + public void StepStop(string key) + { + if (_steps.ContainsKey(key) && _steps[key].Count > 0) + { + var step = _steps[key].Pop(); + step.Dispose(); + if (_steps[key].Count == 0) + { + _steps.Remove(key); + } + } + } + + private void StopAll() + { + // Dispose any orphaned steps + foreach (var stack in _steps.Values) + { + stack.Each(x => x.Dispose()); + } + + _steps.Clear(); + } + + public void Dispose() + { + this.StopAll(); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs b/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs deleted file mode 100644 index e490e592f2..0000000000 --- a/src/Plugins/SmartStore.DevTools/Services/ProfilerService.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using StackExchange.Profiling; - -namespace SmartStore.DevTools.Services -{ - public class ProfilerService : IProfilerService, IDisposable - { - private readonly ConcurrentDictionary> steps = new ConcurrentDictionary>(); - private MiniProfiler _profiler; - - public ProfilerService() - { - this._profiler = MiniProfiler.Current; - } - - protected MiniProfiler Profiler - { - get - { - return this._profiler ?? (this._profiler = MiniProfiler.Current); - } - } - - public void StepStart(string key, string message) - { - if (this.Profiler == null) - { - return; - } - - var stack = this.steps.GetOrAdd(key, k => new ConcurrentStack()); - var step = this.Profiler.Step(message); - stack.Push(step); - } - - public void StepStop(string key) - { - if (this.Profiler == null) - { - return; - } - - IDisposable step; - if (this.steps.ContainsKey(key)) - { - if (this.steps[key].TryPop(out step)) - { - step.Dispose(); - } - } - } - - private void StopAll() - { - // Dispose any orphaned steps - foreach (var stack in this.steps.Values) - { - IDisposable step; - while (stack.TryPop(out step)) - { - step.Dispose(); - } - } - } - - public void Dispose() - { - this.StopAll(); - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs b/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs index 88da20c658..43d45d8d90 100644 --- a/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs +++ b/src/Plugins/SmartStore.DevTools/Settings/ProfilerSettings.cs @@ -3,14 +3,19 @@ using System.Linq; using System.Web; using SmartStore.Core.Configuration; +using SmartStore.Web.Framework; namespace SmartStore.DevTools { public class ProfilerSettings : ISettings { - public bool EnableMiniProfilerInPublicStore { get; set; } + [SmartResourceDisplayName("Plugins.Developer.DevTools.EnableMiniProfilerInPublicStore")] + public bool EnableMiniProfilerInPublicStore { get; set; } + [SmartResourceDisplayName("Plugins.Developer.DevTools.DisplayWidgetZones")] public bool DisplayWidgetZones { get; set; } + [SmartResourceDisplayName("Plugins.Developer.DevTools.DisplayMachineName")] + public bool DisplayMachineName { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj index c8f46ee502..025ef8e5bf 100644 --- a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj +++ b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.DevTools SmartStore.DevTools - v4.5.1 + v4.6.1 512 ..\..\ @@ -36,6 +37,7 @@ + true @@ -75,26 +77,36 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + False + + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll + False + + + ..\..\packages\FluentValidation.Mvc5.7.4.0\lib\net45\FluentValidation.Mvc.dll + False ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - False - ..\..\packages\MiniProfiler.3.1.1.140\lib\net40\MiniProfiler.dll + + ..\..\packages\MiniProfiler.3.2.0.157\lib\net40\MiniProfiler.dll True @@ -102,6 +114,10 @@ ..\..\packages\MiniProfiler.EF6.3.0.11\lib\net40\MiniProfiler.EntityFramework6.dll True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False + @@ -110,32 +126,32 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - @@ -147,9 +163,14 @@ Properties\AssemblyVersionInfo.cs + + + + + @@ -160,8 +181,7 @@ - - + @@ -182,6 +202,11 @@ {75fd4163-333c-4dd5-854d-2ef294e45d94} SmartStore.Web.Framework + + {4f1f649c-1020-45be-a487-f416d9297ff3} + SmartStore.Web + False + @@ -190,6 +215,14 @@ + + Designer + Always + + + Designer + Always + PreserveNewest @@ -213,6 +246,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + @@ -222,6 +267,10 @@ bin\ + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + @@ -250,6 +299,10 @@ + + del "$(TargetDir)MiniProfiler.pdb" /q /s +del "$(TargetDir)MiniProfiler.EntityFramework6.pdb" /q /s + - - -
- + - + @@ -38,7 +42,7 @@ - + @@ -54,7 +58,7 @@ - + @@ -70,35 +74,35 @@ - + - + - + - + - + - + - + - + @@ -108,6 +112,22 @@ + + + + + + + + + + + + + + + + @@ -118,18 +138,20 @@ - + - - + + + + + + + + + + - - - - - - diff --git a/src/Plugins/SmartStore.DevTools/packages.config b/src/Plugins/SmartStore.DevTools/packages.config index 325d93b6d4..d040144b1c 100644 --- a/src/Plugins/SmartStore.DevTools/packages.config +++ b/src/Plugins/SmartStore.DevTools/packages.config @@ -1,12 +1,15 @@  - - - - - - + + + + + + + + - + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index f4bb29d728..b7b46e9263 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,8 +2,8 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll ResourceRootKey: Plugins.SmartStore.DiscountRules diff --git a/src/Plugins/SmartStore.DiscountRules/Localization/resources.de-de.xml b/src/Plugins/SmartStore.DiscountRules/Localization/resources.de-de.xml index f28cb787c5..c4ce8f67f5 100644 --- a/src/Plugins/SmartStore.DiscountRules/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.DiscountRules/Localization/resources.de-de.xml @@ -90,7 +90,7 @@ - Kunde hat einen der folgenden Produkte in seinem Warenkorb + Kunde hat eins der folgenden Produkte in seinem Warenkorb diff --git a/src/Plugins/SmartStore.DiscountRules/Localization/resources.en-us.xml b/src/Plugins/SmartStore.DiscountRules/Localization/resources.en-us.xml index 66720d5e11..59f4d39fe1 100644 --- a/src/Plugins/SmartStore.DiscountRules/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.DiscountRules/Localization/resources.en-us.xml @@ -1,5 +1,12 @@  + + Standard discount rules + + + Provides standard discount rules, e.g. "Country is", "Customer group is", "Has amount x spent" etc. + + Discount could not be loaded @@ -8,6 +15,9 @@ + + The customer must be assigned to a customer group + @@ -23,6 +33,9 @@ + + Country of invoice address is + @@ -39,6 +52,9 @@ + + Country of delivery address is + @@ -55,6 +71,9 @@ + + Limited to specific shop + @@ -71,6 +90,9 @@ + + Customer has one of the following products in his shopping cart + @@ -84,6 +106,9 @@ + + Customer has the following products in his shopping cart + @@ -97,6 +122,9 @@ + + Customer has spent amount x + diff --git a/src/Plugins/SmartStore.DiscountRules/Models/BillingCountryModel.cs b/src/Plugins/SmartStore.DiscountRules/Models/BillingCountryModel.cs index 1e12fd0397..e7357ec8f6 100644 --- a/src/Plugins/SmartStore.DiscountRules/Models/BillingCountryModel.cs +++ b/src/Plugins/SmartStore.DiscountRules/Models/BillingCountryModel.cs @@ -10,6 +10,7 @@ public BillingCountryModel() { AvailableCountries = new List(); } + [SmartResourceDisplayName("Plugins.DiscountRules.BillingCountry.Fields.Country")] public int CountryId { get; set; } public IList AvailableCountries { get; set; } diff --git a/src/Plugins/SmartStore.DiscountRules/Notes.txt b/src/Plugins/SmartStore.DiscountRules/Notes.txt deleted file mode 100644 index 4eead83fd3..0000000000 --- a/src/Plugins/SmartStore.DiscountRules/Notes.txt +++ /dev/null @@ -1,9 +0,0 @@ -'Copy local' property of the referenced assemblies are set to 'false'. -We know that they're referenced by the main web applications. So there's no need to deploy them. -It can dramatically reduce package size. - - -Set project output path to ..\..\Presentation\SmartStore.Web\Plugins\{PluginName}\ (both 'Release' and 'Debug' configurations) - - -All views should marked as embedded resources \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/Providers/HasOneProductRule.cs b/src/Plugins/SmartStore.DiscountRules/Providers/HasOneProductRule.cs index b7845e10fe..3204b29f6b 100644 --- a/src/Plugins/SmartStore.DiscountRules/Providers/HasOneProductRule.cs +++ b/src/Plugins/SmartStore.DiscountRules/Providers/HasOneProductRule.cs @@ -38,10 +38,11 @@ public override bool CheckRequirement(CheckDiscountRequirementRequest request) .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToList(); + if (restrictedProducts.Count == 0) return false; - //cart + // cart var cart = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); bool found = false; @@ -56,18 +57,15 @@ public override bool CheckRequirement(CheckDiscountRequirementRequest request) { if (restrictedProduct.Contains("-")) { - //the third way (the quantity rage specified) - //{Product ID}:{Min quantity}-{Max quantity}. For example, 77:1-3, 123:2-5, 156:3-8 - int restrictedProductId = 0; - if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[0], out restrictedProductId)) + // the third way (the quantity rage specified) + // {Product ID}:{Min quantity}-{Max quantity}. For example, 77:1-3, 123:2-5, 156:3-8 + if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[0], out int restrictedProductId)) //parsing error; exit; return false; - int quantityMin = 0; - if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1].Split(new[] { '-' })[0], out quantityMin)) + if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1].Split(new[] { '-' })[0], out int quantityMin)) //parsing error; exit; return false; - int quantityMax = 0; - if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1].Split(new[] { '-' })[1], out quantityMax)) + if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1].Split(new[] { '-' })[1], out int quantityMax)) //parsing error; exit; return false; @@ -79,14 +77,12 @@ public override bool CheckRequirement(CheckDiscountRequirementRequest request) } else { - //the second way (the quantity specified) - //{Product ID}:{Quantity}. For example, 77:1, 123:2, 156:3 - int restrictedProductId = 0; - if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[0], out restrictedProductId)) + // the second way (the quantity specified) + // {Product ID}:{Quantity}. For example, 77:1, 123:2, 156:3 + if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[0], out int restrictedProductId)) //parsing error; exit; return false; - int quantity = 0; - if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1], out quantity)) + if (!int.TryParse(restrictedProduct.Split(new[] { ':' })[1], out int quantity)) //parsing error; exit; return false; diff --git a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj index dc162de8da..97356664ed 100644 --- a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj +++ b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.DiscountRules SmartStore.DiscountRules - v4.5.1 + v4.6.1 512 @@ -43,6 +44,9 @@ + + + true @@ -84,10 +88,11 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False @@ -96,32 +101,32 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - @@ -185,7 +190,6 @@ Designer Always - @@ -242,6 +246,12 @@ + + + Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". + + + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.FacebookAuth/Content/facebookstyles.css b/src/Plugins/SmartStore.FacebookAuth/Content/facebookstyles.css deleted file mode 100644 index a16c3e4d52..0000000000 --- a/src/Plugins/SmartStore.FacebookAuth/Content/facebookstyles.css +++ /dev/null @@ -1,13 +0,0 @@ - -/* Facebook -================================================ */ - -.btn.btn-facebook { - background: #405e94; -} -.btn.btn-facebook:hover { - background: #3a5587; -} -.btn.btn-facebook:active { - background: #344c79; -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Content/icon.png b/src/Plugins/SmartStore.FacebookAuth/Content/icon.png index 663b15ccca..a9d0524dfa 100644 Binary files a/src/Plugins/SmartStore.FacebookAuth/Content/icon.png and b/src/Plugins/SmartStore.FacebookAuth/Content/icon.png differ diff --git a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs index 38b7223019..d7462cd9e5 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs @@ -1,4 +1,5 @@ using System.Web.Mvc; +using SmartStore.ComponentModel; using SmartStore.Core.Domain.Customers; using SmartStore.FacebookAuth.Core; using SmartStore.FacebookAuth.Models; @@ -12,7 +13,7 @@ namespace SmartStore.FacebookAuth.Controllers { - //[UnitOfWork] + //[UnitOfWork] public class ExternalAuthFacebookController : PluginControllerBase { private readonly IOAuthProviderFacebookAuthorizer _oAuthProviderFacebookAuthorizer; @@ -26,15 +27,15 @@ public ExternalAuthFacebookController( ExternalAuthenticationSettings externalAuthenticationSettings, ICommonServices services) { - this._oAuthProviderFacebookAuthorizer = oAuthProviderFacebookAuthorizer; - this._openAuthenticationService = openAuthenticationService; - this._externalAuthenticationSettings = externalAuthenticationSettings; - this._services = services; + _oAuthProviderFacebookAuthorizer = oAuthProviderFacebookAuthorizer; + _openAuthenticationService = openAuthenticationService; + _externalAuthenticationSettings = externalAuthenticationSettings; + _services = services; } private bool HasPermission(bool notify = true) { - bool hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods); + var hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods); if (notify && !hasPermission) NotifyError(_services.Localization.GetResource("Admin.AccessDenied.Description")); @@ -42,114 +43,111 @@ private bool HasPermission(bool notify = true) return hasPermission; } - [AdminAuthorize, ChildActionOnly] - public ActionResult Configure() + [LoadSetting, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(FacebookExternalAuthSettings settings) { if (!HasPermission(false)) return AccessDeniedPartialView(); var model = new ConfigurationModel(); - int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); + MiniMapper.Map(settings, model); - model.ClientKeyIdentifier = settings.ClientKeyIdentifier; - model.ClientSecret = settings.ClientSecret; + var host = _services.StoreContext.CurrentStore.GetHost(true); + model.RedirectUrl = $"{host}Plugins/SmartStore.FacebookAuth/logincallback/"; - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); - - return View(model); + return View(model); } - [HttpPost, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(ConfigurationModel model, FormCollection form) + [SaveSetting, HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(FacebookExternalAuthSettings settings, ConfigurationModel model) { if (!HasPermission(false)) - return Configure(); + return Configure(settings); if (!ModelState.IsValid) - return Configure(); + return Configure(settings); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); - - settings.ClientKeyIdentifier = model.ClientKeyIdentifier; - settings.ClientSecret = model.ClientSecret; - - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + MiniMapper.Map(model, settings); + settings.ClientKeyIdentifier = model.ClientKeyIdentifier.TrimSafe(); + settings.ClientSecret = model.ClientSecret.TrimSafe(); NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); - return Configure(); + return RedirectToConfiguration(FacebookExternalAuthMethod.SystemName, true); } [ChildActionOnly] public ActionResult PublicInfo() { - return View(); + var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); + + if (settings.ClientKeyIdentifier.HasValue() && settings.ClientSecret.HasValue()) + return View(); + else + return new EmptyResult(); } + public ActionResult Login(string returnUrl) + { + return LoginInternal(returnUrl, false); + } + + public ActionResult LoginCallback(string returnUrl) + { + return LoginInternal(returnUrl, true); + } + [NonAction] private ActionResult LoginInternal(string returnUrl, bool verifyResponse) - { - var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(Provider.SystemName, _services.StoreContext.CurrentStore.Id); + { + var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(FacebookExternalAuthMethod.SystemName, _services.StoreContext.CurrentStore.Id); if (processor == null || !processor.IsMethodActive(_externalAuthenticationSettings)) { - throw new SmartException("Facebook module cannot be loaded"); + NotifyError(T("Plugins.CannotLoadModule", T("Plugins.FriendlyName.SmartStore.FacebookAuth"))); + return new RedirectResult(Url.LogOn(returnUrl)); } - var viewModel = new LoginModel(); - TryUpdateModel(viewModel); + var viewModel = new LoginModel(); + TryUpdateModel(viewModel); var result = _oAuthProviderFacebookAuthorizer.Authorize(returnUrl, verifyResponse); - switch (result.AuthenticationStatus) - { - case OpenAuthenticationStatus.Error: - { - if (!result.Success) - foreach (var error in result.Errors) - NotifyError(error); - - return new RedirectResult(Url.LogOn(returnUrl)); - } - case OpenAuthenticationStatus.AssociateOnLogon: - { - return new RedirectResult(Url.LogOn(returnUrl)); - } - case OpenAuthenticationStatus.AutoRegisteredEmailValidation: - { - //result - return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.EmailValidation }); - } - case OpenAuthenticationStatus.AutoRegisteredAdminApproval: - { - return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.AdminApproval }); - } - case OpenAuthenticationStatus.AutoRegisteredStandard: - { - return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.Standard }); - } - default: - break; - } - - if (result.Result != null) + switch (result.AuthenticationStatus) + { + case OpenAuthenticationStatus.Error: + { + if (!result.Success) + { + result.Errors.Each(x => NotifyError(x)); + } + + return new RedirectResult(Url.LogOn(returnUrl)); + } + case OpenAuthenticationStatus.AssociateOnLogon: + { + return new RedirectResult(Url.LogOn(returnUrl)); + } + case OpenAuthenticationStatus.AutoRegisteredEmailValidation: + { + return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.EmailValidation, returnUrl }); + } + case OpenAuthenticationStatus.AutoRegisteredAdminApproval: + { + return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.AdminApproval, returnUrl }); + } + case OpenAuthenticationStatus.AutoRegisteredStandard: + { + return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.Standard, returnUrl }); + } + default: + break; + } + + if (result.Result != null) return result.Result; return HttpContext.Request.IsAuthenticated ? RedirectToReferrer(returnUrl, "~/") : new RedirectResult(Url.LogOn(returnUrl)); } - - public ActionResult Login(string returnUrl) - { - return LoginInternal(returnUrl, false); - } - - public ActionResult LoginCallback(string returnUrl) - { - return LoginInternal(returnUrl, true); - } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs new file mode 100644 index 0000000000..e11239ef4f --- /dev/null +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Data; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Web; +using System.Web.Script.Serialization; +using DotNetOpenAuth.AspNet; +using DotNetOpenAuth.AspNet.Clients; +using Newtonsoft.Json; + +namespace SmartStore.FacebookAuth.Core +{ + /// + /// + internal class FacebookOAuth2Client : OAuth2Client + { + /// + /// The authorization endpoint. + /// + private const string AuthorizationEndpoint = "https://www.facebook.com/v2.8/dialog/oauth"; + /// + /// The token endpoint. + /// + private const string TokenEndpoint = "https://graph.facebook.com/v2.8/oauth/access_token"; + /// + /// The user info endpoint. + /// + private const string UserInfoEndpoint = "https://graph.facebook.com/v2.8/me"; + /// + /// The app id. + /// + private readonly string _appId; + /// + /// The app secret. + /// + private readonly string _appSecret; + + /// + /// The requested scopes. + /// + private readonly string[] _requestedScopes; + + + /// + /// Creates a new Facebook OAuth2 client, requesting the default "email" scope. + /// + /// The Facebook App Id + /// The Facebook App Secret + public FacebookOAuth2Client(string appId, string appSecret) + : this(appId, appSecret, new[] { "email" }) { } + + /// + /// Creates a new Facebook OAuth2 client. + /// + /// The Facebook App Id + /// The Facebook App Secret + /// One or more requested scopes, passed without the base URI. + public FacebookOAuth2Client(string appId, string appSecret, params string[] requestedScopes) + : base("facebook") + { + if (string.IsNullOrWhiteSpace(appId)) + throw new ArgumentNullException("appId"); + + if (string.IsNullOrWhiteSpace(appSecret)) + throw new ArgumentNullException("appSecret"); + + if (requestedScopes == null) + throw new ArgumentNullException("requestedScopes"); + + if (requestedScopes.Length == 0) + throw new ArgumentException("One or more scopes must be requested.", "requestedScopes"); + + _appId = appId; + _appSecret = appSecret; + _requestedScopes = requestedScopes; + } + + public override void RequestAuthentication(HttpContextBase context, Uri returnUrl) + { + string redirectUrl = this.GetServiceLoginUrl(returnUrl).AbsoluteUri; + context.Response.Redirect(redirectUrl, endResponse: true); + } + + public new AuthenticationResult VerifyAuthentication(HttpContextBase context) + { + throw new NoNullAllowedException(); + } + + public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl) + { + string code = context.Request.QueryString["code"]; + if (string.IsNullOrEmpty(code)) + { + return AuthenticationResult.Failed; + } + + string accessToken = this.QueryAccessToken(returnPageUrl, code); + if (accessToken == null) + { + return AuthenticationResult.Failed; + } + + IDictionary userData = this.GetUserData(accessToken); + if (userData == null) + { + return AuthenticationResult.Failed; + } + + string id = userData["id"]; + string name; + + // Some oAuth providers do not return value for the 'username' attribute. + // In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id' + if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name)) + { + name = id; + } + + // add the access token to the user data dictionary just in case page developers want to use it + userData["accesstoken"] = accessToken; + + return new AuthenticationResult(isSuccessful: true, provider: ProviderName, providerUserId: id, userName: name, extraData: userData); + } + + protected override Uri GetServiceLoginUrl(Uri returnUrl) + { + var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1); + + return BuildUri(AuthorizationEndpoint, new NameValueCollection + { + { "client_id", _appId }, + { "scope", string.Join(" ", _requestedScopes) }, + { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) }, + { "state", state }, + }); + } + + protected override IDictionary GetUserData(string accessToken) + { + var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } }); + + var webRequest = (HttpWebRequest)WebRequest.Create(uri); + + using (var webResponse = webRequest.GetResponse()) + using (var stream = webResponse.GetResponseStream()) + { + if (stream == null) + return null; + + using (var textReader = new StreamReader(stream)) + { + var json = textReader.ReadToEnd(); + var extraData = JsonConvert.DeserializeObject>(json); + var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString()); + + data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"])); + + return data; + } + } + } + + public string QueryAccessTokenByCode(Uri returnUrl, string authorizationCode) + { + return this.QueryAccessToken(returnUrl, authorizationCode); + } + + protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) + { + var uri = BuildUri(TokenEndpoint, new NameValueCollection + { + { "code", authorizationCode }, + { "client_id", _appId }, + { "client_secret", _appSecret }, + { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) }, + }); + + var webRequest = (HttpWebRequest)WebRequest.Create(uri); + string accessToken = null; + + using (var response = (HttpWebResponse)webRequest.GetResponse()) + { + // handle response from FB + // this will not be a url with params like the first request to get the 'code' + Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet); + + using (var sr = new StreamReader(response.GetResponseStream(), rEncoding)) + { + var serializer = new JavaScriptSerializer(); + var jsonObject = serializer.DeserializeObject(sr.ReadToEnd()); + var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject)); + + Dictionary desirializedJsonObject = JsonConvert.DeserializeObject>(jConvert.ToString()); + accessToken = desirializedJsonObject["access_token"].ToString(); + } + } + + return accessToken; + } + + private static Uri BuildUri(string baseUri, NameValueCollection queryParameters) + { + var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k])); + var qs = String.Join("&", keyValuePairs); + + var builder = new UriBuilder(baseUri) { Query = qs }; + return builder.Uri; + } + + /// + /// Facebook works best when return data be packed into a "state" parameter. + /// This should be called before verifying the request, so that the url is rewritten to support this. + /// + public static void RewriteRequest() + { + var ctx = HttpContext.Current; + + var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]); + if (stateString == null || !stateString.Contains("__provider__=facebook")) + return; + + var q = HttpUtility.ParseQueryString(stateString); + q.Add(ctx.Request.QueryString); + q.Remove("state"); + + ctx.RewritePath(ctx.Request.Path + "?" + q); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs index 5f89fe62cc..6f987af313 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs @@ -9,9 +9,9 @@ using System.Web; using System.Web.Mvc; using DotNetOpenAuth.AspNet; -using DotNetOpenAuth.AspNet.Clients; using Newtonsoft.Json.Linq; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Authentication.External; @@ -27,7 +27,7 @@ public class FacebookProviderAuthorizer : IOAuthProviderFacebookAuthorizer private readonly HttpContextBase _httpContext; private readonly ICommonServices _services; - private FacebookClient _facebookApplication; + private FacebookOAuth2Client _facebookApplication; #endregion @@ -39,18 +39,22 @@ public FacebookProviderAuthorizer(IExternalAuthorizer authorizer, HttpContextBase httpContext, ICommonServices services) { - this._authorizer = authorizer; - this._openAuthenticationService = openAuthenticationService; - this._externalAuthenticationSettings = externalAuthenticationSettings; - this._httpContext = httpContext; - this._services = services; - } + _authorizer = authorizer; + _openAuthenticationService = openAuthenticationService; + _externalAuthenticationSettings = externalAuthenticationSettings; + _httpContext = httpContext; + _services = services; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } #endregion #region Utilities - private FacebookClient FacebookApplication + private FacebookOAuth2Client FacebookApplication { get { @@ -58,7 +62,7 @@ private FacebookClient FacebookApplication { var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); - _facebookApplication = new FacebookClient(settings.ClientKeyIdentifier, settings.ClientSecret); + _facebookApplication = new FacebookOAuth2Client(settings.ClientKeyIdentifier, settings.ClientSecret); } return _facebookApplication; @@ -67,9 +71,34 @@ private FacebookClient FacebookApplication private AuthorizeState VerifyAuthentication(string returnUrl) { - var authResult = this.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri()); + string error = null; + AuthenticationResult authResult = null; + + try + { + authResult = this.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri()); + } + catch (WebException wexc) + { + using (var response = wexc.Response as HttpWebResponse) + { + error = response.StatusDescription; + + var enc = Encoding.GetEncoding(response.CharacterSet); + using (var reader = new StreamReader(response.GetResponseStream(), enc)) + { + var rawResponse = reader.ReadToEnd(); + Logger.Log(LogLevel.Error, new Exception(rawResponse), response.StatusDescription, null); + } + } + } + catch (Exception exception) + { + error = exception.ToString(); + Logger.Log(LogLevel.Error, exception, null, null); + } - if (authResult.IsSuccessful) + if (authResult != null && authResult.IsSuccessful) { if (!authResult.ExtraData.ContainsKey("id")) throw new Exception("Authentication result does not contain id data"); @@ -77,7 +106,7 @@ private AuthorizeState VerifyAuthentication(string returnUrl) if (!authResult.ExtraData.ContainsKey("accesstoken")) throw new Exception("Authentication result does not contain accesstoken data"); - var parameters = new OAuthAuthenticationParameters(Provider.SystemName) + var parameters = new OAuthAuthenticationParameters(FacebookExternalAuthMethod.SystemName) { ExternalIdentifier = authResult.ProviderUserId, OAuthToken = authResult.ExtraData["accesstoken"], @@ -92,9 +121,18 @@ private AuthorizeState VerifyAuthentication(string returnUrl) return new AuthorizeState(returnUrl, result); } + if (error.IsEmpty() && authResult != null && authResult.Error != null) + { + error = authResult.Error.Message; + } + if (error.IsEmpty()) + { + error = _services.Localization.GetResource("Admin.Common.UnknownError"); + } + var state = new AuthorizeState(returnUrl, OpenAuthenticationStatus.Error); - var error = authResult.Error != null ? authResult.Error.Message : "Unknown error"; state.AddError(error); + return state; } diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/Provider.cs b/src/Plugins/SmartStore.FacebookAuth/Core/Provider.cs deleted file mode 100644 index 61ed3cde9c..0000000000 --- a/src/Plugins/SmartStore.FacebookAuth/Core/Provider.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace SmartStore.FacebookAuth.Core -{ - public static class Provider - { - public static string SystemName - { - get - { - return "SmartStore.FacebookAuth"; - } - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index b5411fc4ac..447d4bf86d 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,8 +1,8 @@ -FriendlyName: Facebook +FriendlyName: Facebook Login SystemName: SmartStore.FacebookAuth Group: Security -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll ResourceRootKey: Plugins.ExternalAuth.Facebook \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs index d3a0ea7b26..db68ea5d5c 100644 --- a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs +++ b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs @@ -1,6 +1,5 @@ using System.Web.Routing; using SmartStore.Core.Plugins; -using SmartStore.FacebookAuth.Core; using SmartStore.Services.Authentication.External; using SmartStore.Services.Localization; @@ -11,36 +10,26 @@ namespace SmartStore.FacebookAuth ///
public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable { - #region Fields - - private readonly FacebookExternalAuthSettings _facebookExternalAuthSettings; private readonly ILocalizationService _localizationService; - #endregion - - #region Ctor - - public FacebookExternalAuthMethod(FacebookExternalAuthSettings facebookExternalAuthSettings, ILocalizationService localizationService) + public FacebookExternalAuthMethod(ILocalizationService localizationService) { - this._facebookExternalAuthSettings = facebookExternalAuthSettings; _localizationService = localizationService; } - #endregion + public static string SystemName => "SmartStore.FacebookAuth"; - #region Methods - - /// - /// Gets a route for provider configuration - /// - /// Action name - /// Controller name - /// Route values - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + /// + /// Gets a route for provider configuration + /// + /// Action name + /// Controller name + /// Route values + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; controllerName = "ExternalAuthFacebook"; - routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = Provider.SystemName }); + routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = SystemName }); } /// @@ -53,7 +42,7 @@ public void GetPublicInfoRoute(out string actionName, out string controllerName, { actionName = "PublicInfo"; controllerName = "ExternalAuthFacebook"; - routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = Provider.SystemName }); + routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = SystemName }); } /// @@ -61,21 +50,16 @@ public void GetPublicInfoRoute(out string actionName, out string controllerName, /// public override void Install() { - //locales - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); + _localizationService.ImportPluginResourcesFromXml(PluginDescriptor); base.Install(); } public override void Uninstall() { - //locales - _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); + _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); base.Uninstall(); - } - - #endregion - + } } } diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml index 34d1ab5e11..f65160a398 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml @@ -1,28 +1,35 @@  - - Facebook - + + Facebook Login + - -
  • Die Facebook Zugangsdaten (s.u.) erhalten Sie, indem Sie bei Facebook unter Entwickler eine Anwendung mit der Option Facebook-Anmeldung erstellen.
  • -
  • Aktivieren Sie in den Kundeneinstellungen die automatische Registrierung, falls extern autorisierte Besucher automatisch registriert werden sollen.
  • -]]> + So richten Sie die Facebook Authentifizierungen ein:

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

      +
    • Create a new app on Facebook of type Facebook Login. Please enter their access data in the form below.
    • +
    • Enter the URL listed below under Facebook Login > Settings > Valid OAuth Redirect URIs.
    • +
    • Activate automatic registration in the shop's customer settings if you want to register externally authorized visitors automatically.
    • +
    ]]>
    - - Sign in with Facebook - - - Client key identifier (App ID) - - - Enter your client key identifier (App ID) here. - - - Client secret (App Secret) - - - Enter your client secret (App Secret) here. - + + Sign in with Facebook + + + Client key identifier (App ID) + + + Enter your client key identifier (App ID) here. + + + Client secret (App Secret) + + + Enter your client secret (App Secret) here. + + + OAuth Redirect URI + + + The OAuth Redirect URI to be entered on Facebook. +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs index 928feab5b2..dc0a098627 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs @@ -10,5 +10,8 @@ public class ConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.ExternalAuth.Facebook.ClientSecret")] public string ClientSecret { get; set; } - } + + [SmartResourceDisplayName("Plugins.ExternalAuth.Facebook.RedirectUri")] + public string RedirectUrl { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Notes.txt b/src/Plugins/SmartStore.FacebookAuth/Notes.txt deleted file mode 100644 index 4eead83fd3..0000000000 --- a/src/Plugins/SmartStore.FacebookAuth/Notes.txt +++ /dev/null @@ -1,9 +0,0 @@ -'Copy local' property of the referenced assemblies are set to 'false'. -We know that they're referenced by the main web applications. So there's no need to deploy them. -It can dramatically reduce package size. - - -Set project output path to ..\..\Presentation\SmartStore.Web\Plugins\{PluginName}\ (both 'Release' and 'Debug' configurations) - - -All views should marked as embedded resources \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs b/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs index 90465eb41a..fdb9911da7 100644 --- a/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs +++ b/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs @@ -1,20 +1,19 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.FacebookAuth.Core; using SmartStore.Web.Framework.Routing; namespace SmartStore.FacebookAuth { - public partial class RouteProvider : IRouteProvider + public partial class RouteProvider : IRouteProvider { public void RegisterRoutes(RouteCollection routes) { - routes.MapRoute("SmartStore.FacebookAuth", + routes.MapRoute("SmartStore.FacebookAuth", "Plugins/SmartStore.FacebookAuth/{action}", new { controller = "ExternalAuthFacebook" }, new[] { "SmartStore.FacebookAuth.Controllers" } ) - .DataTokens["area"] = Provider.SystemName; + .DataTokens["area"] = FacebookExternalAuthMethod.SystemName; } public int Priority { diff --git a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj index 3709846816..832cc36b51 100644 --- a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj +++ b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.FacebookAuth SmartStore.FacebookAuth - v4.5.1 + v4.6.1 512 @@ -43,6 +44,9 @@ + + + true @@ -82,11 +86,13 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll + False False @@ -120,22 +126,23 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False - - False - ..\..\packages\Microsoft.Net.Http.2.2.19\lib\net45\System.Net.Http.Extensions.dll + + ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll + False - - False - ..\..\packages\Microsoft.Net.Http.2.2.19\lib\net45\System.Net.Http.Primitives.dll + + ..\..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll + False @@ -143,32 +150,32 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - @@ -180,10 +187,10 @@ Properties\AssemblyVersionInfo.cs + - @@ -207,9 +214,6 @@ - - Always - PreserveNewest @@ -224,7 +228,6 @@ Designer Always - @@ -247,11 +250,6 @@ - - - - - @@ -272,6 +270,13 @@ + + + + Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". + + + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png b/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png index 5a8e11a77b..7e3e7da076 100644 Binary files a/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png and b/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png differ diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs index 4159908a34..bd0551c79b 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs @@ -3,94 +3,78 @@ using System.Linq; using System.Text; using System.Web.Mvc; +using SmartStore.ComponentModel; using SmartStore.Core; -using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; using SmartStore.GoogleAnalytics.Models; using SmartStore.Services.Catalog; using SmartStore.Services.Configuration; -using SmartStore.Core.Logging; using SmartStore.Services.Orders; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.GoogleAnalytics.Controllers { - - public class WidgetsGoogleAnalyticsController : SmartController + public class WidgetsGoogleAnalyticsController : SmartController { private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; - private readonly IStoreService _storeService; private readonly ISettingService _settingService; private readonly IOrderService _orderService; private readonly ICategoryService _categoryService; - public WidgetsGoogleAnalyticsController(IWorkContext workContext, - IStoreContext storeContext, IStoreService storeService, - ISettingService settingService, IOrderService orderService, + public WidgetsGoogleAnalyticsController( + IWorkContext workContext, + IStoreContext storeContext, + ISettingService settingService, + IOrderService orderService, ICategoryService categoryService) { - this._workContext = workContext; - this._storeContext = storeContext; - this._storeService = storeService; - this._settingService = settingService; - this._orderService = orderService; - this._categoryService = categoryService; - } - - [AdminAuthorize] - [ChildActionOnly] - public ActionResult Configure() + _workContext = workContext; + _storeContext = storeContext; + _settingService = settingService; + _orderService = orderService; + _categoryService = categoryService; + } + + [AdminAuthorize, ChildActionOnly, LoadSetting] + public ActionResult Configure(GoogleAnalyticsSettings settings) { - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var googleAnalyticsSettings = _settingService.LoadSetting(storeScope); var model = new ConfigurationModel(); - model.GoogleId = googleAnalyticsSettings.GoogleId; - model.TrackingScript = googleAnalyticsSettings.TrackingScript; - model.EcommerceScript = googleAnalyticsSettings.EcommerceScript; - model.EcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; + MiniMapper.Map(settings, model); - model.ZoneId = googleAnalyticsSettings.WidgetZone; - model.AvailableZones.Add(new SelectListItem() { Text = " HTML tag", Value = "head_html_tag"}); - model.AvailableZones.Add(new SelectListItem() { Text = "Before end HTML tag", Value = "body_end_html_tag_before" }); - - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(googleAnalyticsSettings, model, storeScope, _settingService); + model.ZoneId = settings.WidgetZone; + model.AvailableZones.Add(new SelectListItem { Text = " HTML tag", Value = "head_html_tag"}); + model.AvailableZones.Add(new SelectListItem { Text = "Before end HTML tag", Value = "body_end_html_tag_before" }); return View(model); } - [HttpPost] - [AdminAuthorize] - [ChildActionOnly] - [ValidateInput(false)] + [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] public ActionResult Configure(ConfigurationModel model, FormCollection form) { + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + ModelState.Clear(); - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var googleAnalyticsSettings = _settingService.LoadSetting(storeScope); - googleAnalyticsSettings.GoogleId = model.GoogleId; - googleAnalyticsSettings.TrackingScript = model.TrackingScript; - googleAnalyticsSettings.EcommerceScript = model.EcommerceScript; - googleAnalyticsSettings.EcommerceDetailScript = model.EcommerceDetailScript; - googleAnalyticsSettings.WidgetZone = model.ZoneId; + MiniMapper.Map(model, settings); + settings.WidgetZone = model.ZoneId; - using (_settingService.BeginBatch()) + using (Services.Settings.BeginScope()) { - _settingService.SaveSetting(googleAnalyticsSettings, x => x.WidgetZone, 0, false); + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.UpdateSettings(googleAnalyticsSettings, form, storeScope, _settingService); + using (Services.Settings.BeginScope()) + { + _settingService.SaveSetting(settings, x => x.WidgetZone, 0, false); } - return Configure(); + return RedirectToConfiguration("SmartStore.GoogleAnalytics"); } [ChildActionOnly] @@ -100,9 +84,9 @@ public ActionResult PublicInfo(string widgetZone) var routeData = ((System.Web.UI.Page)this.HttpContext.CurrentHandler).RouteData; try - { - //Special case, if we are in last step of checkout, we can use order total for conversion value - if (routeData.Values["controller"].ToString().Equals("checkout", StringComparison.InvariantCultureIgnoreCase) && + { + // Special case, if we are in last step of checkout, we can use order total for conversion value + if (routeData.Values["controller"].ToString().Equals("checkout", StringComparison.InvariantCultureIgnoreCase) && routeData.Values["action"].ToString().Equals("completed", StringComparison.InvariantCultureIgnoreCase)) { var lastOrder = GetLastOrder(); @@ -115,7 +99,7 @@ public ActionResult PublicInfo(string widgetZone) } catch (Exception ex) { - Logger.InsertLog(SmartStore.Core.Domain.Logging.LogLevel.Error, "Error creating scripts for google ecommerce tracking", ex.ToString()); + Logger.Error(ex, "Error creating scripts for google ecommerce tracking"); } return Content(globalScript); } @@ -126,67 +110,104 @@ private Order GetLastOrder() null, null, null, null, null, null, null, null, 0, 1).FirstOrDefault(); return order; } - - private string GetTrackingScript() + + private string GetOptOutCookieScript() + { + var settings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var script = @" + var gaProperty = '{GOOGLEID}'; + var disableStr = 'ga-disable-' + gaProperty; + if (document.cookie.indexOf(disableStr + '=true') > -1) { + window[disableStr] = true; + } + function gaOptout() { + document.cookie = disableStr + '=true; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/'; + window[disableStr] = true; + alert('{NOTIFICATION}'); + } + "; + + script = script + "\n"; + script = script.Replace("{GOOGLEID}", settings.GoogleId); + script = script.Replace("{NOTIFICATION}", T("Plugins.Widgets.GoogleAnalytics.OptOutNotification").JsText.ToHtmlString()); + + return script; + } + + private string GetTrackingScript() { - var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); - string analyticsTrackingScript = ""; - analyticsTrackingScript = googleAnalyticsSettings.TrackingScript + "\n"; - analyticsTrackingScript = analyticsTrackingScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); - analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", ""); - return analyticsTrackingScript; + var settings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var script = ""; + script = settings.TrackingScript + "\n"; + script = script.Replace("{GOOGLEID}", settings.GoogleId); + script = script.Replace("{ECOMMERCE}", ""); + script = script.Replace("{OPTOUTCOOKIE}", GetOptOutCookieScript()); + + return script; } private string GetEcommerceScript(Order order) { - var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var settings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); var usCulture = new CultureInfo("en-US"); - string analyticsTrackingScript = ""; - analyticsTrackingScript = googleAnalyticsSettings.TrackingScript + "\n"; - analyticsTrackingScript = analyticsTrackingScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); + var script = ""; + var ecScript = ""; + + script = settings.TrackingScript + "\n"; + script = script.Replace("{GOOGLEID}", settings.GoogleId); + script = script.Replace("{OPTOUTCOOKIE}", GetOptOutCookieScript()); - string analyticsEcommerceScript = ""; - if (order != null) + if (order != null) { - analyticsEcommerceScript = googleAnalyticsSettings.EcommerceScript + "\n"; - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{ORDERID}", order.GetOrderNumber()); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SITE}", _storeContext.CurrentStore.Url.Replace("http://", "").Replace("/", "")); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TOTAL}", order.OrderTotal.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TAX}", order.OrderTax.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SHIP}", order.OrderShippingInclTax.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CURRENCY}", order.CustomerCurrencyCode); + var site = _storeContext.CurrentStore.Url + .EmptyNull() + .Replace("http://", "") + .Replace("https://", "") + .Replace("/", ""); + + ecScript = settings.EcommerceScript + "\n"; + ecScript = ecScript.Replace("{GOOGLEID}", settings.GoogleId); + ecScript = ecScript.Replace("{ORDERID}", order.GetOrderNumber()); + ecScript = ecScript.Replace("{SITE}", FixIllegalJavaScriptChars(site)); + ecScript = ecScript.Replace("{TOTAL}", order.OrderTotal.ToString("0.00", usCulture)); + ecScript = ecScript.Replace("{TAX}", order.OrderTax.ToString("0.00", usCulture)); + ecScript = ecScript.Replace("{SHIP}", order.OrderShippingInclTax.ToString("0.00", usCulture)); + ecScript = ecScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); + ecScript = ecScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null + ? "" + : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); + ecScript = ecScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null + ? "" + : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); + ecScript = ecScript.Replace("{CURRENCY}", order.CustomerCurrencyCode); var sb = new StringBuilder(); foreach (var item in order.OrderItems) { - string analyticsEcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; - //get category - string categ = ""; + var ecDetailScript = settings.EcommerceDetailScript; var defaultProductCategory = _categoryService.GetProductCategoriesByProductId(item.ProductId).FirstOrDefault(); - if (defaultProductCategory != null) - categ = defaultProductCategory.Category.Name; - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{ORDERID}", order.GetOrderNumber()); - //The SKU code is a required parameter for every item that is added to the transaction - item.Product.MergeWithCombination(item.AttributesXml); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTSKU}", FixIllegalJavaScriptChars(item.Product.Sku)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTNAME}", FixIllegalJavaScriptChars(item.Product.Name)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{CATEGORYNAME}", FixIllegalJavaScriptChars(categ)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{UNITPRICE}", item.UnitPriceInclTax.ToString("0.00", usCulture)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{QUANTITY}", item.Quantity.ToString()); - sb.AppendLine(analyticsEcommerceDetailScript); - } + var categoryName = defaultProductCategory != null + ? defaultProductCategory.Category.Name + : ""; - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{DETAILS}", sb.ToString()); + // The SKU code is a required parameter for every item that is added to the transaction. + item.Product.MergeWithCombination(item.AttributesXml); - analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", analyticsEcommerceScript); + ecDetailScript = ecDetailScript.Replace("{ORDERID}", order.GetOrderNumber()); + ecDetailScript = ecDetailScript.Replace("{PRODUCTSKU}", FixIllegalJavaScriptChars(item.Product.Sku)); + ecDetailScript = ecDetailScript.Replace("{PRODUCTNAME}", FixIllegalJavaScriptChars(item.Product.Name)); + ecDetailScript = ecDetailScript.Replace("{CATEGORYNAME}", FixIllegalJavaScriptChars(categoryName)); + ecDetailScript = ecDetailScript.Replace("{UNITPRICE}", item.UnitPriceInclTax.ToString("0.00", usCulture)); + ecDetailScript = ecDetailScript.Replace("{QUANTITY}", item.Quantity.ToString()); + + sb.AppendLine(ecDetailScript); + } + ecScript = ecScript.Replace("{DETAILS}", sb.ToString()); + script = script.Replace("{ECOMMERCE}", ecScript); } - return analyticsTrackingScript; + return script; } private string FixIllegalJavaScriptChars(string text) diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index 59bf21b018..ac4936f475 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll ResourceRootKey: Plugins.Widgets.GoogleAnalytics diff --git a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs index 185c203527..87f8f2f2b4 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs @@ -7,140 +7,139 @@ namespace SmartStore.GoogleAnalytics { - /// - /// Google Analytics Plugin - /// + /// + /// Google Analytics Plugin + /// public class GoogleAnalyticPlugin : BasePlugin, IWidget, IConfigurable - { - private readonly ISettingService _settingService; - private readonly GoogleAnalyticsSettings _googleAnalyticsSettings; - private readonly ILocalizationService _localizationService; - - public GoogleAnalyticPlugin(ISettingService settingService, - GoogleAnalyticsSettings googleAnalyticsSettings, - ILocalizationService localizationService) - { - this._settingService = settingService; - this._googleAnalyticsSettings = googleAnalyticsSettings; - _localizationService = localizationService; - } - - /// - /// Gets widget zones where this widget should be rendered - /// - /// Widget zones - public IList GetWidgetZones() - { - var zones = new List() { "head_html_tag", "mobile_head_html_tag" }; - if(!string.IsNullOrWhiteSpace(_googleAnalyticsSettings.WidgetZone)) - { - zones = new List() { - _googleAnalyticsSettings.WidgetZone, - _googleAnalyticsSettings.WidgetZone == "head_html_tag" ? "mobile_head_html_tag" : "mobile_body_end_html_tag_after" - }; - } - - return zones; - } - - /// - /// Gets a route for provider configuration - /// - /// Action name - /// Controller name - /// Route values - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) - { - actionName = "Configure"; - controllerName = "WidgetsGoogleAnalytics"; + { + #region Scripts + + private const string TRACKING_SCRIPT = @" +"; + + private const string ECOMMERCE_SCRIPT = @"ga('require', 'ecommerce'); +ga('ecommerce:addTransaction', { + 'id': '{ORDERID}', + 'affiliation': '{SITE}', + 'revenue': '{TOTAL}', + 'shipping': '{SHIP}', + 'tax': '{TAX}', + 'currency': '{CURRENCY}' +}); + +{DETAILS} + +ga('ecommerce:send');"; + + private const string ECOMMERCE_DETAIL_SCRIPT = @"ga('ecommerce:addItem', { + 'id': '{ORDERID}', + 'name': '{PRODUCTNAME}', + 'sku': '{PRODUCTSKU}', + 'category': '{CATEGORYNAME}', + 'price': '{UNITPRICE}', + 'quantity': '{QUANTITY}' +});"; + + #endregion + + private readonly ISettingService _settingService; + private readonly GoogleAnalyticsSettings _googleAnalyticsSettings; + private readonly ILocalizationService _localizationService; + + public GoogleAnalyticPlugin(ISettingService settingService, + GoogleAnalyticsSettings googleAnalyticsSettings, + ILocalizationService localizationService) + { + _settingService = settingService; + _googleAnalyticsSettings = googleAnalyticsSettings; + _localizationService = localizationService; + } + + /// + /// Gets widget zones where this widget should be rendered + /// + /// Widget zones + public IList GetWidgetZones() + { + var zones = new List { "head_html_tag" }; + if (!string.IsNullOrWhiteSpace(_googleAnalyticsSettings.WidgetZone)) + { + zones = new List + { + _googleAnalyticsSettings.WidgetZone + }; + } + + return zones; + } + + /// + /// Gets a route for provider configuration + /// + /// Action name + /// Controller name + /// Route values + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "Configure"; + controllerName = "WidgetsGoogleAnalytics"; routeValues = new RouteValueDictionary() { { "area", "SmartStore.GoogleAnalytics" } }; - } - - /// - /// Gets a route for displaying widget - /// - /// Widget zone where it's displayed - /// Action name - /// Controller name - /// Route values + } + + /// + /// Gets a route for displaying widget + /// + /// Widget zone where it's displayed + /// Action name + /// Controller name + /// Route values public void GetDisplayWidgetRoute(string widgetZone, object model, int storeId, out string actionName, out string controllerName, out RouteValueDictionary routeValues) - { - actionName = "PublicInfo"; - controllerName = "WidgetsGoogleAnalytics"; - routeValues = new RouteValueDictionary() - { - {"area", "SmartStore.GoogleAnalytics"}, - {"widgetZone", widgetZone} - }; - } - - /// - /// Install plugin - /// - public override void Install() - { - var settings = new GoogleAnalyticsSettings() - { - GoogleId = "UA-0000000-0", - TrackingScript = @" - ", - EcommerceScript = @" - ga('require', 'ecommerce'); - - ga('ecommerce:addTransaction', { - 'id': '{ORDERID}', - 'affiliation': '{SITE}', - 'revenue': '{TOTAL}', - 'shipping': '{SHIP}', - 'tax': '{TAX}', - 'currency': '{CURRENCY}' - }); - - {DETAILS} - - ga('ecommerce:send'); - ", - EcommerceDetailScript = @" - ga('ecommerce:addItem', { - 'id': '{ORDERID}', - 'name': '{PRODUCTNAME}', - 'sku': '{PRODUCTSKU}', - 'category': '{CATEGORYNAME}', - 'price': '{UNITPRICE}', - 'quantity': '{QUANTITY}' - }); - ", - - }; - _settingService.SaveSetting(settings); - - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); - - base.Install(); - } - - /// - /// Uninstall plugin - /// - public override void Uninstall() - { - //locales - _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); - _localizationService.DeleteLocaleStringResources("Plugins.FriendlyName.Widgets.GoogleAnalytics", false); - + { + actionName = "PublicInfo"; + controllerName = "WidgetsGoogleAnalytics"; + routeValues = new RouteValueDictionary() + { + {"area", "SmartStore.GoogleAnalytics"}, + {"widgetZone", widgetZone} + }; + } + + public override void Install() + { + var settings = new GoogleAnalyticsSettings + { + GoogleId = "UA-0000000-0", + TrackingScript = TRACKING_SCRIPT, + EcommerceScript = ECOMMERCE_SCRIPT, + EcommerceDetailScript = ECOMMERCE_DETAIL_SCRIPT + }; + + _settingService.SaveSetting(settings); + _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); + + base.Install(); + } + + public override void Uninstall() + { + _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); + _localizationService.DeleteLocaleStringResources("Plugins.FriendlyName.Widgets.GoogleAnalytics", false); _settingService.DeleteSetting(); - - base.Uninstall(); - } - } + + base.Uninstall(); + } + } } diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml index d7f6055f92..40ef8fd50d 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml @@ -14,6 +14,10 @@
  • Klicken Sie den Speichern-Button.
  • Aktivieren Sie das Google Analytics Widget unter CMS > Widgets und Google Analytics wird in Ihre Webseite integriert.
  • +

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

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

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

    +
    <a href="javascript:gaOptout()">Deactivate Google Analytics</a>
    ]]> @@ -41,4 +45,7 @@ Paste the tracking code generated by Google analytics here. {ORDERID}, {PRODUCTSKU}, {PRODUCTNAME}, {CATEGORYNAME}, {UNITPRICE}, {QUANTITY} will be dynamically replaced. + + Tracking is now disabled + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj b/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj index 0b26e4d1d5..b48e5d3f5d 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj +++ b/src/Plugins/SmartStore.GoogleAnalytics/SmartStore.GoogleAnalytics.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.GoogleAnalytics SmartStore.GoogleAnalytics - v4.5.1 + v4.6.1 512 @@ -42,6 +43,8 @@ + + true @@ -83,6 +86,7 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False @@ -91,32 +95,32 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config b/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config index e9d36a3c51..26c177a9ab 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml b/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml index 32cbd49177..75dda5169e 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml @@ -1,76 +1,71 @@ -@{ - Layout = ""; -} -@model SmartStore.GoogleAnalytics.Models.ConfigurationModel +@model SmartStore.GoogleAnalytics.Models.ConfigurationModel @using SmartStore.Web.Framework; - +@{ + Layout = ""; +}
    @Html.Raw(@T("Plugins.Widgets.GoogleAnalytics.AdminInstruction"))
    -@Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) +@{ Html.RenderAction("StoreScopeConfiguration", "Setting", new { area = "Admin" }); } + +
    + +
    @using (Html.BeginForm()) { - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.ZoneId) - - @Html.DropDownListFor(model => model.ZoneId, Model.AvailableZones) - @Html.ValidationMessageFor(model => model.ZoneId) -
    - @Html.SmartLabelFor(model => model.GoogleId) - - @Html.SettingEditorFor(model => model.GoogleId) - @Html.ValidationMessageFor(model => model.GoogleId) -
    - @Html.SmartLabelFor(model => model.TrackingScript) - - @Html.SettingOverrideCheckbox(model => model.TrackingScript) - @Html.TextAreaFor(model => model.TrackingScript, new { style = "Width: 600px; Height: 200px;" }) - @Html.ValidationMessageFor(model => model.TrackingScript) -
    - @Html.SmartLabelFor(model => model.EcommerceScript) - - @Html.SettingOverrideCheckbox(model => model.EcommerceScript) - @Html.TextAreaFor(model => model.EcommerceScript, new { style = "Width: 600px; Height: 50px;" }) - @Html.ValidationMessageFor(model => model.EcommerceScript) -
    - @Html.SmartLabelFor(model => model.EcommerceDetailScript) - - @Html.SettingOverrideCheckbox(model => model.EcommerceDetailScript) - @Html.TextAreaFor(model => model.EcommerceDetailScript, new { style = "Width: 600px; Height: 50px;" }) - @Html.ValidationMessageFor(model => model.EcommerceDetailScript) -
    -   - - -
    - + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.ZoneId) + + @Html.DropDownListFor(model => model.ZoneId, Model.AvailableZones) + @Html.ValidationMessageFor(model => model.ZoneId) +
    + @Html.SmartLabelFor(model => model.GoogleId) + + @Html.SettingEditorFor(model => model.GoogleId) + @Html.ValidationMessageFor(model => model.GoogleId) +
    + @Html.SmartLabelFor(model => model.TrackingScript) + + @Html.SettingEditorFor(model => model.TrackingScript, + Html.TextAreaFor(model => model.TrackingScript, new { style = "height: 300px;" })) + @Html.ValidationMessageFor(model => model.TrackingScript) +
    + @Html.SmartLabelFor(model => model.EcommerceScript) + + @Html.SettingEditorFor(model => model.EcommerceScript, + Html.TextAreaFor(model => model.EcommerceScript, new { style = "height: 300px;" })) + @Html.ValidationMessageFor(model => model.EcommerceScript) +
    + @Html.SmartLabelFor(model => model.EcommerceDetailScript) + + @Html.SettingEditorFor(model => model.EcommerceDetailScript, + Html.TextAreaFor(model => model.EcommerceDetailScript, new { style = "height: 200px;" })) + @Html.ValidationMessageFor(model => model.EcommerceDetailScript) +
    } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/packages.config b/src/Plugins/SmartStore.GoogleAnalytics/packages.config index 4bc06fc1a7..704b8f0f85 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/packages.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/packages.config @@ -1,7 +1,7 @@  - - - + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index c0f82db177..fbe6b4c179 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -1,115 +1,147 @@ - + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs index 2dbc226f57..6a62863d9a 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs @@ -9,7 +9,8 @@ protected override void BuildMenuCore(TreeNode pluginsNode) { var menuItem = new MenuItem().ToBuilder() .Text("Google Merchant Center") - .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") + .Icon("fab fa-google") + .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") .Action("ConfigurePlugin", "Plugin", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, area = "Admin" }) .ToItem(); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css deleted file mode 100644 index 4a8bfa9c72..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css +++ /dev/null @@ -1,6 +0,0 @@ -.edit-taxonomy { - min-width: 460px; -} -.gmc-product-search { - margin-bottom: 8px !important; -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index 6c37461c87..ed8533af16 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Web.Mvc; @@ -18,13 +19,13 @@ namespace SmartStore.GoogleMerchantCenter.Controllers public class FeedGoogleMerchantCenterController : PluginControllerBase { private readonly IGoogleFeedService _googleFeedService; - private readonly AdminAreaSettings _adminAreaSettings; - private readonly IExportProfileService _exportService; + private readonly Lazy _adminAreaSettings; + private readonly Lazy _exportService; public FeedGoogleMerchantCenterController( IGoogleFeedService googleFeedService, - AdminAreaSettings adminAreaSettings, - IExportProfileService exportService) + Lazy adminAreaSettings, + Lazy exportService) { _googleFeedService = googleFeedService; _adminAreaSettings = adminAreaSettings; @@ -75,9 +76,10 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultIsBundle = ""; ViewBag.DefaultEnergyEfficiencyClass = notSpecified; ViewBag.DefaultCustomLabel = ""; + ViewBag.LanguageSeoCode = Services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToLower(); - // we do not have export profile context here, so we simply use the first profile - var profile = _exportService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName).FirstOrDefault(); + // We do not have export profile context here, so we simply use the first profile. + var profile = _exportService.Value.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName).FirstOrDefault(); if (profile != null) { var config = XmlHelper.Deserialize(profile.ProviderConfigData, typeof(ProfileConfigurationModel)) as ProfileConfigurationModel; @@ -99,7 +101,11 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + culture.TextInfo.ToTitleCase(config.AgeGroup)); } } - } + } + + ViewBag.AvailableCategories = model.Taxonomy.HasValue() + ? new List { new SelectListItem { Text = model.Taxonomy, Value = model.Taxonomy, Selected = true } } + : new List(); ViewBag.AvailableGenders = new List { @@ -124,37 +130,61 @@ public ActionResult ProductEditTab(int productId) return result; } - public ActionResult GoogleCategories() - { - var categories = _googleFeedService.GetTaxonomyList(); - return Json(categories, JsonRequestBehavior.AllowGet); - } - public ActionResult Configure() { var model = new FeedGoogleMerchantCenterModel(); - model.GridPageSize = _adminAreaSettings.GridPageSize; - model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + model.GridPageSize = _adminAreaSettings.Value.GridPageSize; + model.LanguageSeoCode = Services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToLower(); model.EnergyEfficiencyClasses = T("Plugins.Feed.Froogle.EnergyEfficiencyClasses").Text.SplitSafe(","); return View(model); } + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult GoogleProductList(GridCommand command, string searchProductName, string touched) + { + return new JsonResult + { + Data = _googleFeedService.GetGridModel(command, searchProductName, touched) + }; + } + [HttpPost] public ActionResult GoogleProductEdit(int pk, string name, string value) { _googleFeedService.Upsert(pk, name, value); - return this.Content(""); + return new EmptyResult(); } - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult GoogleProductList(GridCommand command, string searchProductName, string touched) + public JsonResult GetGoogleCategories(string search, int? page) { + page = page ?? 1; + const int take = 100; + var skip = (page.Value - 1) * take; + var categories = _googleFeedService.GetTaxonomyList(search); + var hasMoreItems = (page.Value * take) < categories.Count; + //$"{skip}\\{categories.Length} {hasMoreItems}: {search.NaIfEmpty()}".Dump(); + + var items = categories.Select(x => new + { + id = x, + text = x + }) + .Skip(skip) + .Take(take) + .ToList(); + return new JsonResult { - Data = _googleFeedService.GetGridModel(command, searchProductName, touched) + Data = new + { + hasMoreItems, + results = items + }, + MaxJsonLength = int.MaxValue, + JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.Designer.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.Designer.cs new file mode 100644 index 0000000000..251c767acc --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.GoogleMerchantCenter.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class Indexes : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Indexes)); + + string IMigrationMetadata.Id + { + get { return "201705250945238_Indexes"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.cs new file mode 100644 index 0000000000..bd0adfd1dc --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.cs @@ -0,0 +1,21 @@ +namespace SmartStore.GoogleMerchantCenter.Data.Migrations +{ + using System.Data.Entity.Migrations; + + public partial class Indexes : DbMigration + { + public override void Up() + { + CreateIndex("dbo.GoogleProduct", "ProductId"); + CreateIndex("dbo.GoogleProduct", "IsTouched"); + CreateIndex("dbo.GoogleProduct", "Export"); + } + + public override void Down() + { + DropIndex("dbo.GoogleProduct", new[] { "Export" }); + DropIndex("dbo.GoogleProduct", new[] { "IsTouched" }); + DropIndex("dbo.GoogleProduct", new[] { "ProductId" }); + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.resx b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.resx new file mode 100644 index 0000000000..ebd4b64ec7 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201705250945238_Indexes.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 + + + H4sIAAAAAAAEAM1a227jNhB9L9B/EPTUAtnIlxRoA3sXWSdZGF1vFqtk32lp7KhLkSpJBXaLflkf+kn9hQ51sURJtiVfgiIv8Yg8c+FodHTsf//+Z/RuFVLrBYQMOBvb/cuebQHzuB+w5diO1eLNz/a7t99/N7rzw5X1NV831OtwJ5Nj+1mp6NpxpPcMIZGXYeAJLvlCXXo8dIjPnUGv94vT7zuAEDZiWdboS8xUEELyAT9OOPMgUjGhM+4DlZkdr7gJqvWJhCAj4sHYdkMilKu4gMsPnC8pzEB4z4SpCTAF4vKWKGJbNzQgGJsLdGFbhDGuiMLIr58kuEpwtnQjNBD6uI4A1y0IlZBldF0sb5tcb6CTc4qNOZQXS8XDjoD9YVYtp7r9oJrbm2piPe+w7mqts05qOrbTGn4W3I899QU8Lnzbqvq9nlCh97SoPm4L2GUD6oW1Z+/Fpr2wu/TfhTWJqYoFjBnEShB6YX2O5zTwfoX1I/8GbMxiSsvpYYJ4zTCgCcOIQKj1F1hkSU8xR8fc51Q3braV9qQ1mDI1HNjWJ3RO5hQ23VOqV5LlB2AgiAL/M1GYH9MYkNS/5r3iKyvcfpe7YR7JijMernMU7Hy8rW1rRlYfgS3V89jGf23rPliBn1sy5CcW4BTATUrEex1hpj6Is7u5WcIHwePo7I4mnPLzp+MGf8DZncywAQUOurM7ypr87H6mCsKkC4q743y+5COPccZuPL3nnAJhne/EiQA9CB7Yk/JyLHxQwSM+BDuDPUX+6cDuVhEX6tj8ZjioA3w+fztuYE3l+5j5FGrh7Nt242MAHXfd4XReru8Wi8ALkPKsJ5RIuaOlfuqdYrIkT9aPZA60t8NXv3diZ/3XdDZ4TWfD13R2dWJnI6cgZHWahrRYIZsC0cTVHua/gaf0ElipBsqGTDdjbTLzbCaZunBBbSeCeDcU4aVkuonZNae0Cb4g807K5nPW72yh/aMZiSKsbuk1ILNYbvoOMHnjdqfCYYrheLKBEW+i3XhCBkeWULmqqYEP94GQSr9nzIk+34kf1pbtOKotx5A73nEaVWJbHE6+Wf+fkYsWr0mNh1lxUhT/HusR4uakNLCJu/kVooaSvMsRSkQDrUa6FYdsGzXftbtElMsgJXN7rIItl6EKa3uknA6XcXJbe5SC7ZZxCmt7pIzOlmEyU3uMlKyWIVJLe4SCiZZRCmuHU8+ppnHmubE9jkEljSYsX+iAV9BFA60wdzgzgzEaR2dcaY9o0sYyonmlPWLOHctYua1DXxTc0WiMwtzlBHICaR5Abu2ClHFKEygzdqhRM8s0Sta8pEOvGKTS6BXjykGI/a2I/QMRB1sRBwciDrciDg9EvNqKeNWEOHIqD8nqo9qpPasr8lOVB+xiVNUlG+8bZlVhUKOMzexXV2v0Jl1iW1iql8DX1MZdSxyPKYFwf6cTik2rigUzwoIFSJUqdPag1x9U5Nj/jzTqSOnT7vroqyuOgS7wXk2x48t2TWRMvNTeI6bIW1Zj+0/rr6P1R/ZCNAcVP4Rk9WMZrbvGeBRUVUc8CszQCo9CKuuBRwFVNb+jwCq63lFYDdrdcXhVfW4enLiFm4Q7TZbUqYS7g8FM4e7kidcUvWQ6HKnnJVF2PWJD2zsAYafOl7dfTenrOAQaZL1G6EQkOhi7f0bswRmxh2fEvjoOu5sCV1d/OotqbTW1lHjhgJhzzDAN31h4sOxWJ4Mjp/yF/OgWZLAsIPTX8ww8PVEK0HzNlC14fjiYdzmifEl1soAiOPXIjVDBgngKL3sgZSKnfiU01rdsOAd/yh5iFcXqRkoI59RQYEbObv+JtmjGPHqI9Cd5ihQwzEAP7gf2Pg6ov4n7vj4Ut0HoTsrIHEblKk3qlusN0ifOWgJl5buFSBMkph4hjCiCyQfmkhc4JLYnCR9hSbx1zum3g+w/CLPso9uALAUJZYZR7Nc/MnH0r0ze/gcewwGrlyIAAA== + + + dbo + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 5bc3e9ad59..3b87cd8d04 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.GoogleMerchantCenter.dll diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs index 7064e84880..1d177d7809 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core; namespace SmartStore.GoogleMerchantCenter.Domain @@ -13,6 +14,7 @@ public GoogleProductRecord() Export = true; } + [Index] public int ProductId { get; set; } public string Taxonomy { get; set; } @@ -24,10 +26,12 @@ public GoogleProductRecord() public string Pattern { get; set; } public string ItemGroupId { get; set; } + [Index] public bool IsTouched { get; set; } public DateTime CreatedOnUtc { get; set; } public DateTime UpdatedOnUtc { get; set; } + [Index] public bool Export { get; set; } public int Multipack { get; set; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs index 10b5732247..211c45083d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs @@ -8,15 +8,13 @@ namespace SmartStore.GoogleMerchantCenter { - public class Events : - IConsumer, - IConsumer + public class Events : IConsumer { private readonly IGoogleFeedService _googleService; public Events(IGoogleFeedService googleService) { - this._googleService = googleService; + _googleService = googleService; } public void HandleEvent(TabStripCreated eventMessage) @@ -27,7 +25,7 @@ public void HandleEvent(TabStripCreated eventMessage) eventMessage.ItemFactory.Add().Text("GMC") .Name("tab-gmc") - .Icon("fa fa-google fa-lg fa-fw") + .Icon("fab fa-google fa-lg fa-fw") .LinkHtmlAttributes(new { data_tab_name = "GMC" }) .Route("SmartStore.GoogleMerchantCenter", new { action = "ProductEditTab", productId = productId }) .Ajax(); @@ -56,7 +54,6 @@ public void HandleEvent(ModelBoundEvent eventMessage) }; } - // map objects entity.AgeGroup = model.AgeGroup; entity.Color = model.Color; entity.Gender = model.Gender; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs index 5a8dd0168d..e3a3702bf1 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs @@ -1,27 +1,9 @@ -using System.Web.Mvc; -using SmartStore.GoogleMerchantCenter.Domain; +using SmartStore.GoogleMerchantCenter.Domain; namespace SmartStore.GoogleMerchantCenter { public static class MiscExtensions { - public static string XEditableLink(this HtmlHelper hlp, string fieldName, string type) - { - string displayText = null; - - if (fieldName == "Gender" || fieldName == "AgeGroup" || fieldName == "Export2" || fieldName == "IsBundle" || fieldName == "IsAdult") - displayText = "<#= {0}Localize #>".FormatInvariant(fieldName); - else - displayText = "<#= {0} #>".FormatInvariant(fieldName); - - string skeleton = - "\" class=\"edit-link-{1}\"" + - " data-pk=\"<#= ProductId #>\" data-name=\"{0}\" data-value=\"<#= {0} #>\" data-inputclass=\"edit-{1}\" data-type=\"{2}\">" + - "{3}"; - - return skeleton.FormatInvariant(fieldName, fieldName.ToLower(), type, displayText); - } - public static bool IsTouched(this GoogleProductRecord p) { if (p != null) @@ -32,6 +14,7 @@ public static bool IsTouched(this GoogleProductRecord p) !p.Export || p.Multipack != 0 || p.IsBundle.HasValue || p.IsAdult.HasValue || p.EnergyEfficiencyClass.HasValue() || p.CustomLabel0.HasValue() || p.CustomLabel1.HasValue() || p.CustomLabel2.HasValue() || p.CustomLabel3.HasValue() || p.CustomLabel4.HasValue(); } + return false; } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.de-DE.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.de-DE.txt index 6be270d3aa..e910006a64 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.de-DE.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.de-DE.txt @@ -24,7 +24,7 @@ Baby & Kleinkind > Babyspielwaren > Babylauflernhilfen Baby & Kleinkind > Babyspielwaren > Babyrasseln Baby & Kleinkind > Babyspielwaren > Babyschaukeln & Türhopser Baby & Kleinkind > Babyspielwaren > Babywippen -Baby & Kleinkind > Babyspielwaren > Lauf- & Spielgitterreinigungst +Baby & Kleinkind > Babyspielwaren > Lauf- & Spielgitter Baby & Kleinkind > Babyspielwaren > Schnuller & Beruhigung Baby & Kleinkind > Babyspielwaren > Sortier-, Stapel- & Steckspielzeug Baby & Kleinkind > Babyspielwaren > Spiel- & Krabbeldecken diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt index 671672c564..07bdc95e7f 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Files/taxonomy.en-US.txt @@ -775,8 +775,8 @@ Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds Arts & Entertainment > Hobbies & Creative Arts > Musical Instruments > Woodwinds > Train Whistles Arts & Entertainment > Party & Celebration Arts & Entertainment > Party & Celebration > Gift Giving -Arts & Entertainment > Party & Celebration > Gift Giving > Corsage & Boutonnière Pins -Arts & Entertainment > Party & Celebration > Gift Giving > Corsages & Boutonnières +Arts & Entertainment > Party & Celebration > Gift Giving > Corsage & Boutonnière Pins +Arts & Entertainment > Party & Celebration > Gift Giving > Corsages & Boutonnières Arts & Entertainment > Party & Celebration > Gift Giving > Fresh Cut Flowers Arts & Entertainment > Party & Celebration > Gift Giving > Gift Cards & Certificates Arts & Entertainment > Party & Celebration > Gift Giving > Gift Wrapping @@ -812,7 +812,7 @@ Arts & Entertainment > Party & Celebration > Party Supplies > Party Games Arts & Entertainment > Party & Celebration > Party Supplies > Party Hats Arts & Entertainment > Party & Celebration > Party Supplies > Party Streamers & Curtains Arts & Entertainment > Party & Celebration > Party Supplies > Party Supply Kits -Arts & Entertainment > Party & Celebration > Party Supplies > Piñatas +Arts & Entertainment > Party & Celebration > Party Supplies > Piñatas Arts & Entertainment > Party & Celebration > Party Supplies > Place Card Holders Arts & Entertainment > Party & Celebration > Party Supplies > Place Cards Arts & Entertainment > Party & Celebration > Party Supplies > Response Cards @@ -1811,7 +1811,7 @@ Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fr Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Longan Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Loquats Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Lychees -Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Madroño +Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Madroño Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Mamey Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Mangosteens Food, Beverages & Tobacco > Food Items > Fruits & Vegetables > Fresh & Frozen Fruits > Melons @@ -1935,7 +1935,7 @@ Food, Beverages & Tobacco > Food Items > Nuts & Seeds Food, Beverages & Tobacco > Food Items > Pasta & Noodles Food, Beverages & Tobacco > Food Items > Prepared Foods Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Appetizers & Side Dishes -Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Meals & Entrées +Food, Beverages & Tobacco > Food Items > Prepared Foods > Prepared Meals & Entrées Food, Beverages & Tobacco > Food Items > Seasonings & Spices Food, Beverages & Tobacco > Food Items > Seasonings & Spices > Herbs & Spices Food, Beverages & Tobacco > Food Items > Seasonings & Spices > MSG @@ -2653,7 +2653,7 @@ Health & Beauty > Health Care > Fitness & Nutrition Health & Beauty > Health Care > Fitness & Nutrition > Nutrition Bars Health & Beauty > Health Care > Fitness & Nutrition > Nutrition Drinks & Shakes Health & Beauty > Health Care > Fitness & Nutrition > Nutrition Gels & Chews -Health & Beauty > Health Care > Fitness & Nutrition > Nutritional Food Purées +Health & Beauty > Health Care > Fitness & Nutrition > Nutritional Food Purées Health & Beauty > Health Care > Fitness & Nutrition > Tube Feeding Supplements Health & Beauty > Health Care > Fitness & Nutrition > Vitamins & Supplements Health & Beauty > Health Care > Hearing Aids @@ -3367,7 +3367,7 @@ Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware & Bakeware Combo Sets Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Casserole Dishes Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Cookware Sets -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Crêpe & Blini Pans +Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Crêpe & Blini Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Double Boilers Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Dutch Ovens Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Fermentation & Pickling Crocks @@ -3376,7 +3376,7 @@ Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Grill Presse Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Paella Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Pressure Cookers & Canners Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Saucepans -Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Sauté Pans +Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Sauté Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Skillets & Frying Pans Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Stock Pots Home & Garden > Kitchen & Dining > Cookware & Bakeware > Cookware > Stovetop Kettles @@ -4304,7 +4304,7 @@ Sporting Goods > Athletics > Dancing > Ballet Barres Sporting Goods > Athletics > Fencing Sporting Goods > Athletics > Fencing > Fencing Protective Gear Sporting Goods > Athletics > Fencing > Fencing Protective Gear > Fencing Gloves & Cuffs -Sporting Goods > Athletics > Fencing > Fencing Protective Gear > Fencing Jackets & Lamés +Sporting Goods > Athletics > Fencing > Fencing Protective Gear > Fencing Jackets & Lamés Sporting Goods > Athletics > Fencing > Fencing Protective Gear > Fencing Masks Sporting Goods > Athletics > Fencing > Fencing Weapons Sporting Goods > Athletics > Field Hockey & Lacrosse diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs index 3808bca487..a930d8907c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs @@ -2,8 +2,10 @@ using System.Web.Routing; using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Data.Migrations; +using SmartStore.GoogleMerchantCenter.Providers; using SmartStore.GoogleMerchantCenter.Services; using SmartStore.Services; +using SmartStore.Services.DataExchange.Export; namespace SmartStore.GoogleMerchantCenter { @@ -11,13 +13,16 @@ public class GoogleMerchantCenterFeedPlugin : BasePlugin, IConfigurable { private readonly IGoogleFeedService _googleFeedService; private readonly ICommonServices _services; + private readonly IExportProfileService _exportProfileService; public GoogleMerchantCenterFeedPlugin( IGoogleFeedService googleFeedService, - ICommonServices services) + ICommonServices services, + IExportProfileService exportProfileService) { _googleFeedService = googleFeedService; _services = services; + _exportProfileService = exportProfileService; } public static string SystemName @@ -35,7 +40,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "FeedGoogleMerchantCenter"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; + routeValues = new RouteValueDictionary { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; } /// @@ -55,7 +60,11 @@ public override void Uninstall() { _services.Localization.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); - var migrator = new DbMigrator(new Configuration()); + // Delete existing export profiles. + var profiles = _exportProfileService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName); + profiles.Each(x => _exportProfileService.DeleteExportProfile(x, true)); + + var migrator = new DbMigrator(new Configuration()); migrator.Update(DbMigrator.InitialDatabase); base.Uninstall(); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index 17b90e856c..1940907121 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -15,13 +15,30 @@ -
  • Für die Produktidentifizierung muss entweder die GTIN (z.B. als UPC, EAN etc.) oder der Hersteller samt Hersteller-Artikelnummer (MPN) hinterlegt sein. Google empfiehlt die Angabe aller drei Informationen.
  • -
  • Standard Steuer- und Versanddaten sind in den Einstellungen Ihres Google-Merchant-Center-Kontos zu hinterlegen.
  • -
  • Mehr Informationen zu benötigten Feldern finden Sie im Artikel Produkt-Feed-Spezifikationen.
  • -
  • Eine Liste mit allen gültigen Google-Produkt-Kategorie finden Sie hier.
  • -]]> +
  • Für die Produktidentifizierung muss entweder die GTIN (z.B. als UPC, EAN etc.) oder der Hersteller samt Hersteller-Artikelnummer (MPN) hinterlegt sein. Google empfiehlt die Angabe aller drei Informationen.
  • +
  • Standard Steuer- und Versanddaten sind in den Einstellungen Ihres Google-Merchant-Center-Kontos zu hinterlegen.
  • +
  • Mehr Informationen zu benötigten Feldern finden Sie im Artikel Produkt-Feed-Spezifikationen.
  • +
  • Eine Liste mit allen gültigen Google-Produkt-Kategorie finden Sie hier.
  • + ]]>
    + + + Produktdaten + + + Export-Profile + + + + Allgemeine Einstellungen + + + Standardwerte + + + Standardwerte werden exportiert, wenn die entsprechende Information für das jeweilge Produkt nicht gepflegt wurde. Für einzelne Produkte können diese Werte sowohl in der Plugin-Konfiguration als auch direkt auf der Bearbeitungsseite des Produktes eingegeben werden. + Standard-Google-Kategorie fehlt @@ -29,7 +46,7 @@ Standard-Google-Kategorie - Wird verwendet, falls auf Produktebene keine Kategorie angegeben ist. + Wird verwendet, falls auf Produktebene keine Google-Kategorie angegeben ist. Produkt @@ -155,14 +172,11 @@ Noch nicht bearbeitet - Verfällt in Tagen + Verfällt in Anzahl der Tage, nach dem die Artikel verfallen bzw. nicht mehr angezeigt werden sollen. Der Wert 0 bewirkt, dass kein Verfallsdatum exportiert wird. - - Der Wert muss zwischen 0 und 29 Tagen liegen. - Versanddaten exportieren diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index 3b75a8c306..f8cffa44f3 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -15,13 +15,29 @@ -
  • Either the GTIN (such as UPC, EAN, etc.) or manufacturer and manufacturer part number (MPN) are required for product identification. Google recommends that all three pieces of information be specified.
  • -
  • Specify default tax and shipping values in your Google Merchant Center account settings.
  • -
  • In order to get more info about required fields look at the following article Product feed specification.
  • -
  • You can find a list of all Google categories here.
  • -]]> +
  • Either the GTIN (such as UPC, EAN, etc.) or manufacturer and manufacturer part number (MPN) are required for product identification. Google recommends that all three pieces of information be specified.
  • +
  • Specify default tax and shipping values in your Google Merchant Center account settings.
  • +
  • In order to get more info about required fields look at the following article Product feed specification.
  • +
  • You can find a list of all Google categories here.
  • + ]]>
    + + + Product data + + + Export profiles + + + General settings + + + Default values + + + Default values will be exported if the information does not exist or is not available for a particular product. For a particular product, these values can be edited in the plugin configuration and the product edit page. + Default Google category is not set @@ -155,14 +171,11 @@ Not edited yet - Expires in days + Expires in Number of days after products should expire or no longer appear. A value of 0 causes that no expiry date will be exported. - - The value must be between 0 and 29 days. - Export shipping data diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs index 36c5f02799..973d44f295 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs @@ -1,7 +1,5 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Text; -using Newtonsoft.Json; using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -11,18 +9,7 @@ namespace SmartStore.GoogleMerchantCenter.Models public class FeedGoogleMerchantCenterModel { public int GridPageSize { get; set; } - - public string[] AvailableGoogleCategories { get; set; } - public string AvailableGoogleCategoriesAsJson - { - get - { - if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) - return JsonConvert.SerializeObject(AvailableGoogleCategories); - return "[ ]"; - } - } - + public string LanguageSeoCode { get; set; } public string[] EnergyEfficiencyClasses { get; set; } [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchProductName")] @@ -36,7 +23,7 @@ public class GoogleProductModel : ModelBase { public int TotalCount { get; set; } - //this attribute is required to disable editing + // This attribute is required to disable editing. [ScaffoldColumn(false)] public int ProductId { @@ -45,7 +32,7 @@ public int ProductId } public int Id { get; set; } - //this attribute is required to disable editing + // This attribute is required to disable editing. [ReadOnly(true)] [ScaffoldColumn(false)] [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.ProductName")] @@ -62,7 +49,7 @@ public string ProductTypeLabelHint switch (ProductType) { case ProductType.SimpleProduct: - return "smnet-hide"; + return "secondary d-none"; case ProductType.GroupedProduct: return "success"; case ProductType.BundledProduct: diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs index 69d81d50dd..828defcb7d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs @@ -1,13 +1,14 @@ -using System; -using System.Xml.Serialization; +using FluentValidation; using FluentValidation.Attributes; -using Newtonsoft.Json; -using SmartStore.GoogleMerchantCenter.Validators; using SmartStore.Web.Framework; +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using System.Xml.Serialization; namespace SmartStore.GoogleMerchantCenter.Models { - [Serializable] + [Serializable] [Validator(typeof(ProfileConfigurationValidator))] public class ProfileConfigurationModel { @@ -18,22 +19,14 @@ public ProfileConfigurationModel() SpecialPrice = true; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.DefaultGoogleCategory")] - public string DefaultGoogleCategory { get; set; } - [XmlIgnore] - public string[] AvailableGoogleCategories { get; set; } + public string LanguageSeoCode { get; set; } [XmlIgnore] - public string AvailableGoogleCategoriesAsJson - { - get - { - if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) - return JsonConvert.SerializeObject(AvailableGoogleCategories); - return ""; - } - } + public List AvailableCategories { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.DefaultGoogleCategory")] + public string DefaultGoogleCategory { get; set; } [SmartResourceDisplayName("Plugins.Feed.Froogle.AdditionalImages")] public bool AdditionalImages { get; set; } @@ -74,4 +67,12 @@ public string AvailableGoogleCategoriesAsJson [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportBasePrice")] public bool ExportBasePrice { get; set; } } + + public class ProfileConfigurationValidator : AbstractValidator + { + public ProfileConfigurationValidator() + { + RuleFor(x => x.ExpirationDays).InclusiveBetween(0, 29); + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index bc9047d888..c2e73c4fc4 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Web.Mvc; using System.Xml; +using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Directory; @@ -9,8 +12,11 @@ using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Models; using SmartStore.GoogleMerchantCenter.Services; +using SmartStore.Services; +using SmartStore.Services.Catalog; using SmartStore.Services.DataExchange.Export; using SmartStore.Services.Directory; +using SmartStore.Services.Localization; namespace SmartStore.GoogleMerchantCenter.Providers { @@ -24,23 +30,30 @@ namespace SmartStore.GoogleMerchantCenter.Providers ExportFeatures.CanProjectDescription | ExportFeatures.UsesSkuAsMpnFallback | ExportFeatures.OffersBrandFallback | - ExportFeatures.CanIncludeMainPicture | - ExportFeatures.UsesSpecialPrice)] + ExportFeatures.UsesSpecialPrice | + ExportFeatures.UsesAttributeCombination)] public class GmcXmlExportProvider : ExportProviderBase { private const string _googleNamespace = "http://base.google.com/ns/1.0"; private readonly IGoogleFeedService _googleFeedService; private readonly IMeasureService _measureService; + private readonly ICommonServices _services; + private readonly IProductAttributeService _productAttributeService; private readonly MeasureSettings _measureSettings; + private Multimap _attributeMappings; public GmcXmlExportProvider( IGoogleFeedService googleFeedService, IMeasureService measureService, + ICommonServices services, + IProductAttributeService productAttributeService, MeasureSettings measureSettings) { _googleFeedService = googleFeedService; _measureService = measureService; + _services = services; + _productAttributeService = productAttributeService; _measureSettings = measureSettings; T = NullLocalizer.Instance; @@ -48,6 +61,19 @@ public GmcXmlExportProvider( public Localizer T { get; set; } + private Multimap AttributeMappings + { + get + { + if (_attributeMappings == null) + { + _attributeMappings = _productAttributeService.GetExportFieldMappings("gmc"); + } + + return _attributeMappings; + } + } + private string BasePriceUnits(string value) { const string defaultValue = "kg"; @@ -120,16 +146,82 @@ private bool BasePriceSupported(int baseAmount, string unit) return false; } - public static string SystemName + private void WriteString(XmlWriter writer, string fieldName, string value) { - get { return "Feeds.GoogleMerchantCenterProductXml"; } + if (value != null) + { + writer.WriteElementString("g", fieldName, _googleNamespace, value); + } + } + + private string GetAttributeValue( + Multimap attributeValues, + string fieldName, + int languageId, + string productEditTabValue, + string defaultValue) + { + // 1. attribute export mapping. + if (attributeValues != null && AttributeMappings.ContainsKey(fieldName)) + { + foreach (var attributeId in AttributeMappings[fieldName]) + { + if (attributeValues.ContainsKey(attributeId)) + { + var attributeValue = attributeValues[attributeId].FirstOrDefault(x => x.ProductVariantAttribute.ProductAttributeId == attributeId); + if (attributeValue != null) + { + return attributeValue.GetLocalized(x => x.Name, languageId, true, false).Value.EmptyNull(); + } + } + } + } + + // 2. explicit set to unspecified. + if (defaultValue.IsCaseInsensitiveEqual(Unspecified)) + { + return string.Empty; + } + + // 3. product edit tab value. + if (productEditTabValue.HasValue()) + { + return productEditTabValue; + } + + return defaultValue.EmptyNull(); } - public static string Unspecified + private string GetBaseMeasureWeight() { - get { return "__nospec__"; } + var measureWeightEntity = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + var measureWeight = measureWeightEntity != null + ? measureWeightEntity.SystemKeyword.EmptyNull().ToLower() + : string.Empty; + + switch (measureWeight) + { + case "gram": + case "gramme": + return "g"; + case "mg": + case "milligramme": + case "milligram": + return "mg"; + case "lb": + return "lb"; + case "ounce": + case "oz": + return "oz"; + default: + return "kg"; + } } + public static string SystemName => "Feeds.GoogleMerchantCenterProductXml"; + + public static string Unspecified => "__nospec__"; + public override ExportConfigurationInfo ConfigurationInfo { get @@ -142,29 +234,47 @@ public override ExportConfigurationInfo ConfigurationInfo { var model = (obj as ProfileConfigurationModel); - model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + model.LanguageSeoCode = _services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToLower(); + + model.AvailableCategories = model.DefaultGoogleCategory.HasValue() + ? new List { new SelectListItem { Text = model.DefaultGoogleCategory, Value = model.DefaultGoogleCategory, Selected = true } } + : new List(); } }; } } - public override string FileExtension - { - get { return "XML"; } - } + public override string FileExtension => "XML"; protected override void Export(ExportExecuteContext context) { - dynamic currency = context.Currency; - string measureWeightSystemKey = ""; + Currency currency = context.Currency.Entity; + var languageId = context.Projection.LanguageId ?? 0; var dateFormat = "yyyy-MM-ddTHH:mmZ"; + var defaultCondition = "new"; + var defaultAvailability = "in stock"; + var measureWeight = GetBaseMeasureWeight(); - var measureWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); - if (measureWeight != null) - measureWeightSystemKey = measureWeight.SystemKeyword; + if (config.Condition.IsCaseInsensitiveEqual(Unspecified)) + { + defaultCondition = string.Empty; + } + else if (config.Condition.HasValue()) + { + defaultCondition = config.Condition; + } + + if (config.Availability.IsCaseInsensitiveEqual(Unspecified)) + { + defaultAvailability = string.Empty; + } + else if (config.Availability.HasValue()) + { + defaultAvailability = config.Availability; + } - var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); using (var writer = XmlWriter.Create(context.DataStream, ExportXmlHelper.DefaultSettings)) { @@ -182,7 +292,7 @@ protected override void Export(ExportExecuteContext context) var segment = context.DataSegmenter.CurrentSegment; int[] productIds = segment.Select(x => (int)((dynamic)x).Id).ToArray(); - var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds); + var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds).ToDictionarySafe(x => x.ProductId); foreach (dynamic product in segment) { @@ -190,7 +300,7 @@ protected override void Export(ExportExecuteContext context) break; Product entity = product.Entity; - var gmc = googleProducts.FirstOrDefault(x => x.ProductId == entity.Id); + var gmc = googleProducts.Get(entity.Id); if (gmc != null && !gmc.Export) continue; @@ -201,14 +311,26 @@ protected override void Export(ExportExecuteContext context) { string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; - string mainImageUrl = product._MainPictureUrl; - var futureSpecialPrice = product._FutureSpecialPrice as decimal?; var price = (decimal)product.Price; + var uniqueId = (string)product._UniqueId; + var isParent = (bool)product._IsParent; string brand = product._Brand; string gtin = product.Gtin; string mpn = product.ManufacturerPartNumber; - string condition = "new"; - string availability = "in stock"; + var availability = defaultAvailability; + List productPictures = product.ProductPictures; + var pictureUrls = productPictures + .Select(x => (string)x.Picture._FullSizeImageUrl) + .Where(x => x.HasValue()) + .ToList(); + + var attributeValues = !isParent && product._AttributeCombinationValues != null + ? ((ICollection)product._AttributeCombinationValues).ToMultimap(x => x.ProductVariantAttribute.ProductAttributeId, x => x) + : new Multimap(); + + var specialPrice = product._FutureSpecialPrice as decimal?; + if (!specialPrice.HasValue) + specialPrice = product._SpecialPrice; if (category.IsEmpty()) category = config.DefaultGoogleCategory; @@ -216,35 +338,15 @@ protected override void Export(ExportExecuteContext context) if (category.IsEmpty()) context.Log.Error(T("Plugins.Feed.Froogle.MissingDefaultCategory")); - if (config.Condition.IsCaseInsensitiveEqual(Unspecified)) + if (entity.ManageInventoryMethod == ManageInventoryMethod.ManageStock && entity.StockQuantity <= 0) { - condition = ""; - } - else if (config.Condition.HasValue()) - { - condition = config.Condition; + if (entity.BackorderMode == BackorderMode.NoBackorders) + availability = "out of stock"; + else if (entity.BackorderMode == BackorderMode.AllowQtyBelow0 || entity.BackorderMode == BackorderMode.AllowQtyBelow0AndNotifyCustomer) + availability = entity.AvailableForPreOrder ? "preorder" : "out of stock"; } - if (config.Availability.IsCaseInsensitiveEqual(Unspecified)) - { - availability = ""; - } - else if (config.Availability.HasValue()) - { - availability = config.Availability; - } - else - { - if (entity.ManageInventoryMethod == ManageInventoryMethod.ManageStock && entity.StockQuantity <= 0) - { - if (entity.BackorderMode == BackorderMode.NoBackorders) - availability = "out of stock"; - else if (entity.BackorderMode == BackorderMode.AllowQtyBelow0 || entity.BackorderMode == BackorderMode.AllowQtyBelow0AndNotifyCustomer) - availability = (entity.AvailableForPreOrder ? "preorder" : "out of stock"); - } - } - - writer.WriteElementString("g", "id", _googleNamespace, entity.Id.ToString()); + WriteString(writer, "id", uniqueId); writer.WriteStartElement("title"); writer.WriteCData(((string)product.Name).Truncate(70).RemoveInvalidXmlChars()); @@ -267,89 +369,92 @@ protected override void Export(ExportExecuteContext context) writer.WriteElementString("link", (string)product._DetailUrl); - if (mainImageUrl.HasValue()) + if (pictureUrls.Any()) { - writer.WriteElementString("g", "image_link", _googleNamespace, mainImageUrl); - } + WriteString(writer, "image_link", pictureUrls.First()); - if (config.AdditionalImages) - { - var imageCount = 0; - foreach (dynamic productPicture in product.ProductPictures) + if (config.AdditionalImages) { - string pictureUrl = productPicture.Picture._ImageUrl; - if (pictureUrl.HasValue() && (mainImageUrl.IsEmpty() || !mainImageUrl.IsCaseInsensitiveEqual(pictureUrl)) && ++imageCount <= 10) + var imageCount = 0; + foreach (var url in pictureUrls.Skip(1)) { - writer.WriteElementString("g", "additional_image_link", _googleNamespace, pictureUrl); + if (++imageCount <= 10) + { + WriteString(writer, "additional_image_link", url); + } } } } - writer.WriteElementString("g", "condition", _googleNamespace, condition); - writer.WriteElementString("g", "availability", _googleNamespace, availability); + var condition = GetAttributeValue(attributeValues, "condition", languageId, null, defaultCondition); + WriteString(writer, "condition", condition); + + WriteString(writer, "availability", availability); if (availability == "preorder" && entity.AvailableStartDateTimeUtc.HasValue && entity.AvailableStartDateTimeUtc.Value > DateTime.UtcNow) { var availabilityDate = entity.AvailableStartDateTimeUtc.Value.ToString(dateFormat); - writer.WriteElementString("g", "availability_date", _googleNamespace, availabilityDate); + WriteString(writer, "availability_date", availabilityDate); } - if (config.SpecialPrice && futureSpecialPrice.HasValue && entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + if (config.SpecialPrice && specialPrice.HasValue) { - var specialPriceDate = "{0}/{1}".FormatInvariant( - entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); + WriteString(writer, "sale_price", string.Concat(specialPrice.Value.FormatInvariant(), " ", currency.CurrencyCode)); + + if (entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + { + var specialPriceDate = "{0}/{1}".FormatInvariant( + entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); - writer.WriteElementString("g", "sale_price", _googleNamespace, futureSpecialPrice.Value.FormatInvariant() + " " + (string)currency.CurrencyCode); - writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); + WriteString(writer, "sale_price_effective_date", specialPriceDate); + } price = (product._RegularPrice as decimal?) ?? price; } - writer.WriteElementString("g", "price", _googleNamespace, price.FormatInvariant() + " " + (string)currency.CurrencyCode); + WriteString(writer, "price", string.Concat(price.FormatInvariant(), " ", currency.CurrencyCode)); + + WriteString(writer, "gtin", gtin); + WriteString(writer, "brand", brand); + WriteString(writer, "mpn", mpn); - writer.WriteCData("gtin", gtin, "g", _googleNamespace); - writer.WriteCData("brand", brand, "g", _googleNamespace); - writer.WriteCData("mpn", mpn, "g", _googleNamespace); + var identifierExists = brand.HasValue() && (gtin.HasValue() || mpn.HasValue()); + WriteString(writer, "identifier_exists", identifierExists ? "yes" : "no"); - if (config.Gender.IsCaseInsensitiveEqual(Unspecified)) - writer.WriteCData("gender", "", "g", _googleNamespace); - else - writer.WriteCData("gender", gmc != null && gmc.Gender.HasValue() ? gmc.Gender : config.Gender, "g", _googleNamespace); + var gender = GetAttributeValue(attributeValues, "gender", languageId, gmc?.Gender, config.Gender); + WriteString(writer, "gender", gender); - if (config.AgeGroup.IsCaseInsensitiveEqual(Unspecified)) - writer.WriteCData("age_group", "", "g", _googleNamespace); - else - writer.WriteCData("age_group", gmc != null && gmc.AgeGroup.HasValue() ? gmc.AgeGroup : config.AgeGroup, "g", _googleNamespace); + var ageGroup = GetAttributeValue(attributeValues, "age_group", languageId, gmc?.AgeGroup, config.AgeGroup); + WriteString(writer, "age_group", ageGroup); - writer.WriteCData("color", gmc != null && gmc.Color.HasValue() ? gmc.Color : config.Color, "g", _googleNamespace); - writer.WriteCData("size", gmc != null && gmc.Size.HasValue() ? gmc.Size : config.Size, "g", _googleNamespace); - writer.WriteCData("material", gmc != null && gmc.Material.HasValue() ? gmc.Material : config.Material, "g", _googleNamespace); - writer.WriteCData("pattern", gmc != null && gmc.Pattern.HasValue() ? gmc.Pattern : config.Pattern, "g", _googleNamespace); - writer.WriteCData("item_group_id", gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : "", "g", _googleNamespace); + var color = GetAttributeValue(attributeValues, "color", languageId, gmc?.Color, config.Color); + WriteString(writer, "color", color); - writer.WriteElementString("g", "identifier_exists", _googleNamespace, gtin.HasValue() || brand.HasValue() || mpn.HasValue() ? "TRUE" : "FALSE"); + var size = GetAttributeValue(attributeValues, "size", languageId, gmc?.Size, config.Size); + WriteString(writer, "size", size); + + var material = GetAttributeValue(attributeValues, "material", languageId, gmc?.Material, config.Material); + WriteString(writer, "material", material); + + var pattern = GetAttributeValue(attributeValues, "pattern", languageId, gmc?.Pattern, config.Pattern); + WriteString(writer, "pattern", pattern); + + var itemGroupId = gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : string.Empty; + if (itemGroupId.HasValue()) + { + WriteString(writer, "item_group_id", itemGroupId); + } if (config.ExpirationDays > 0) { - writer.WriteElementString("g", "expiration_date", _googleNamespace, DateTime.UtcNow.AddDays(config.ExpirationDays).ToString("yyyy-MM-dd")); + WriteString(writer, "expiration_date", DateTime.UtcNow.AddDays(config.ExpirationDays).ToString("yyyy-MM-dd")); } if (config.ExportShipping) { - string weightInfo; - var weight = ((decimal)product.Weight).FormatInvariant(); - - if (measureWeightSystemKey.IsCaseInsensitiveEqual("gram")) - weightInfo = weight + " g"; - else if (measureWeightSystemKey.IsCaseInsensitiveEqual("lb")) - weightInfo = weight + " lb"; - else if (measureWeightSystemKey.IsCaseInsensitiveEqual("ounce")) - weightInfo = weight + " oz"; - else - weightInfo = weight + " kg"; - - writer.WriteElementString("g", "shipping_weight", _googleNamespace, weightInfo); + var weight = string.Concat(((decimal)product.Weight).FormatInvariant(), " ", measureWeight); + WriteString(writer, "shipping_weight", weight); } if (config.ExportBasePrice && entity.BasePriceHasValue) @@ -361,61 +466,39 @@ protected override void Export(ExportExecuteContext context) var basePriceMeasure = "{0} {1}".FormatInvariant((entity.BasePriceAmount ?? decimal.Zero).FormatInvariant(), measureUnit); var basePriceBaseMeasure = "{0} {1}".FormatInvariant(entity.BasePriceBaseAmount ?? 1, measureUnit); - writer.WriteElementString("g", "unit_pricing_measure", _googleNamespace, basePriceMeasure); - writer.WriteElementString("g", "unit_pricing_base_measure", _googleNamespace, basePriceBaseMeasure); + WriteString(writer, "unit_pricing_measure", basePriceMeasure); + WriteString(writer, "unit_pricing_base_measure", basePriceBaseMeasure); } } - if (gmc != null && gmc.Multipack > 1) - { - writer.WriteElementString("g", "multipack", _googleNamespace, gmc.Multipack.ToString()); - } - - if (gmc != null && gmc.IsBundle.HasValue) - { - writer.WriteElementString("g", "is_bundle", _googleNamespace, gmc.IsBundle.Value ? "TRUE" : "FALSE"); - } - - if (gmc != null && gmc.IsAdult.HasValue) - { - writer.WriteElementString("g", "adult", _googleNamespace, gmc.IsAdult.Value ? "TRUE" : "FALSE"); - } - - if (gmc != null && gmc.EnergyEfficiencyClass.HasValue()) + if (gmc != null) { - writer.WriteElementString("g", "energy_efficiency_class", _googleNamespace, gmc.EnergyEfficiencyClass); + WriteString(writer, "multipack", gmc.Multipack > 1 ? gmc.Multipack.ToString() : null); + WriteString(writer, "is_bundle", gmc.IsBundle.HasValue ? (gmc.IsBundle.Value ? "yes" : "no") : null); + WriteString(writer, "adult", gmc.IsAdult.HasValue ? (gmc.IsAdult.Value ? "yes" : "no") : null); + WriteString(writer, "energy_efficiency_class", gmc.EnergyEfficiencyClass.HasValue() ? gmc.EnergyEfficiencyClass : null); } - if (gmc != null && gmc.CustomLabel0.HasValue()) - { - writer.WriteElementString("g", "custom_label_0", _googleNamespace, gmc.CustomLabel0); - } + var customLabel0 = GetAttributeValue(attributeValues, "custom_label_0", languageId, gmc?.CustomLabel0, null); + WriteString(writer, "custom_label_0", gmc?.CustomLabel0.NullEmpty()); - if (gmc != null && gmc.CustomLabel1.HasValue()) - { - writer.WriteElementString("g", "custom_label_1", _googleNamespace, gmc.CustomLabel1); - } + var customLabel1 = GetAttributeValue(attributeValues, "custom_label_1", languageId, gmc?.CustomLabel1, null); + WriteString(writer, "custom_label_1", gmc?.CustomLabel1.NullEmpty()); - if (gmc != null && gmc.CustomLabel2.HasValue()) - { - writer.WriteElementString("g", "custom_label_2", _googleNamespace, gmc.CustomLabel2); - } + var customLabel2 = GetAttributeValue(attributeValues, "custom_label_2", languageId, gmc?.CustomLabel2, null); + WriteString(writer, "custom_label_2", gmc?.CustomLabel2.NullEmpty()); - if (gmc != null && gmc.CustomLabel3.HasValue()) - { - writer.WriteElementString("g", "custom_label_3", _googleNamespace, gmc.CustomLabel3); - } + var customLabel3 = GetAttributeValue(attributeValues, "custom_label_3", languageId, gmc?.CustomLabel3, null); + WriteString(writer, "custom_label_3", gmc?.CustomLabel3.NullEmpty()); - if (gmc != null && gmc.CustomLabel4.HasValue()) - { - writer.WriteElementString("g", "custom_label_4", _googleNamespace, gmc.CustomLabel4); - } + var customLabel4 = GetAttributeValue(attributeValues, "custom_label_4", languageId, gmc?.CustomLabel4, null); + WriteString(writer, "custom_label_4", gmc?.CustomLabel4.NullEmpty()); - ++context.RecordsSucceeded; + ++context.RecordsSucceeded; } - catch (Exception exception) + catch (Exception ex) { - context.RecordException(exception, entity.Id); + context.RecordException(ex, entity.Id); } writer.WriteEndElement(); // item diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index b5fab3af04..c2a8ff01a3 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; @@ -15,22 +16,25 @@ namespace SmartStore.GoogleMerchantCenter.Services { - public partial class GoogleFeedService : IGoogleFeedService + public partial class GoogleFeedService : IGoogleFeedService { private const string _googleNamespace = "http://base.google.com/ns/1.0"; private readonly IRepository _gpRepository; private readonly ICommonServices _services; private readonly IPluginFinder _pluginFinder; + private readonly ILogger _logger; public GoogleFeedService( IRepository gpRepository, ICommonServices services, - IPluginFinder pluginFinder) + IPluginFinder pluginFinder, + ILogger logger) { _gpRepository = gpRepository; _services = services; _pluginFinder = pluginFinder; + _logger = logger; T = NullLocalizer.Instance; } @@ -90,7 +94,7 @@ public void Upsert(int pk, string name, string value) return; var product = GetGoogleProductRecord(pk); - bool insert = (product == null); + var insert = (product == null); var utcNow = DateTime.UtcNow; if (product == null) @@ -291,29 +295,47 @@ public GridModel GetGridModel(GridCommand command, string se return model; } - public string[] GetTaxonomyList() + public List GetTaxonomyList(string searchTerm) { + var result = new List(); + try { var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(GoogleMerchantCenterFeedPlugin.SystemName); - - var fileDir = Path.Combine(descriptor.OriginalAssemblyFile.Directory.FullName, "Files"); - var fileName = "taxonomy.{0}.txt".FormatWith(_services.WorkContext.WorkingLanguage.LanguageCulture ?? "de-DE"); + var fileDir = Path.Combine(descriptor.Assembly.OriginalFile.Directory.FullName, "Files"); + var fileName = "taxonomy.{0}.txt".FormatInvariant(_services.WorkContext.WorkingLanguage.LanguageCulture ?? "de-DE"); var path = Path.Combine(fileDir, fileName); + var filter = searchTerm.HasValue(); + string line; if (!File.Exists(path)) + { path = Path.Combine(fileDir, "taxonomy.en-US.txt"); + } - string[] lines = File.ReadAllLines(path, Encoding.UTF8); - - return lines; + using (var file = new StreamReader(path, Encoding.UTF8)) + { + while ((line = file.ReadLine()) != null) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + if (filter && line.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) < 0) + { + continue; + } + + result.Add(line); + } + } } catch (Exception exc) { - exc.Dump(); + _logger.Error(exc); } - return new string[] { }; + return result; } } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs index 6b89e81213..cfd2bc5f06 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs @@ -5,7 +5,7 @@ namespace SmartStore.GoogleMerchantCenter.Services { - public partial interface IGoogleFeedService + public partial interface IGoogleFeedService { GoogleProductRecord GetGoogleProductRecord(int productId); @@ -21,6 +21,6 @@ public partial interface IGoogleFeedService GridModel GetGridModel(GridCommand command, string searchProductName = null, string touched = null); - string[] GetTaxonomyList(); + List GetTaxonomyList(string searchTerm); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index f743627997..0a93c5cd18 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.GoogleMerchantCenter SmartStore.GoogleMerchantCenter - v4.5.1 + v4.6.1 512 @@ -43,6 +44,9 @@ + + + true @@ -82,29 +86,33 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + False - - ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll + False ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False @@ -114,37 +122,38 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - False ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll + False @@ -174,6 +183,10 @@ 201601061649324_IsBundle.cs + + + 201705250945238_Indexes.cs + @@ -187,7 +200,6 @@ - @@ -211,9 +223,6 @@ 201403112356126_Initial.cs - - PreserveNewest - PreserveNewest @@ -226,6 +235,9 @@ 201601061649324_IsBundle.cs + + 201705250945238_Indexes.cs + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs deleted file mode 100644 index 9799ada57f..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentValidation; -using SmartStore.GoogleMerchantCenter.Models; -using SmartStore.Services.Localization; - -namespace SmartStore.GoogleMerchantCenter.Validators -{ - public class ProfileConfigurationValidator : AbstractValidator - { - public ProfileConfigurationValidator(ILocalizationService localize) - { - RuleFor(x => x.ExpirationDays).InclusiveBetween(0, 29) - .WithMessage(localize.GetResource("Plugins.Feed.Froogle.ExpirationDays.Validate")); - } - } -} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index d91ec16571..d85fa485ea 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -1,27 +1,37 @@ -@model FeedGoogleMerchantCenterModel -@using SmartStore.GoogleMerchantCenter; +@using SmartStore.GoogleMerchantCenter; @using SmartStore.GoogleMerchantCenter.Models; @using SmartStore.GoogleMerchantCenter.Providers; @using SmartStore.Web.Framework; @using Telerik.Web.Mvc.UI; @using SmartStore.Web.Framework.UI; - +@model FeedGoogleMerchantCenterModel @{ Layout = ""; - - Html.AddCssFileParts(true, "~/Content/x-editable/bootstrap-editable.css"); - Html.AddCssFileParts(true, "~/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css"); - Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); + + Html.AddCssFileParts(true, "~/Content/vendors/x-editable/bootstrap-editable.css"); + Html.AppendScriptParts(true, "~/Content/vendors/x-editable/bootstrap-editable.js"); } + +
    -
    - +
    @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction")) -
    -
    - @Html.Action("InfoProfile", "Export", new { systemName = GmcXmlExportProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" }) +
    @@ -31,133 +41,263 @@
    -

     

    - -
    - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("gmc-products-grid") - .DataKeys(keys => - { - keys.Add(x => x.ProductId).RouteKey("ProductId"); - }) - .Columns(c => - { - c.Bound(x => x.ProductId) - .ReadOnly() - .Visible(false); - c.Bound(x => x.Name) - .ReadOnly().Visible(true) - .Template(x => @Html.LabeledProductName(x.ProductId, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "Name")); - c.Bound(x => x.SKU) - .ReadOnly() - .Visible(true); - c.Bound(x => x.Export2) - .ClientTemplate(Html.XEditableLink("Export2", "select2")); - c.Bound(x => x.Taxonomy) - .ClientTemplate(Html.XEditableLink("Taxonomy", "typeahead")); - c.Bound(x => x.Gender) - .ClientTemplate(Html.XEditableLink("Gender", "select2")); - c.Bound(x => x.AgeGroup) - .ClientTemplate(Html.XEditableLink("AgeGroup", "select2")); - c.Bound(x => x.IsAdult) - .ClientTemplate(Html.XEditableLink("IsAdult", "select2")); - c.Bound(x => x.Color) - .ClientTemplate(Html.XEditableLink("Color", "text")); - c.Bound(x => x.Size) - .ClientTemplate(Html.XEditableLink("Size", "text")); - c.Bound(x => x.Material) - .ClientTemplate(Html.XEditableLink("Material", "text")); - c.Bound(x => x.Pattern) - .ClientTemplate(Html.XEditableLink("Pattern", "text")); - c.Bound(x => x.Multipack2) - .ClientTemplate(Html.XEditableLink("Multipack2", "text")); - c.Bound(x => x.IsBundle) - .ClientTemplate(Html.XEditableLink("IsBundle", "select2")); - c.Bound(x => x.EnergyEfficiencyClass) - .ClientTemplate(Html.XEditableLink("EnergyEfficiencyClass", "select2")); - c.Bound(x => x.CustomLabel0) - .ClientTemplate(Html.XEditableLink("CustomLabel0", "text")); - c.Bound(x => x.CustomLabel1) - .ClientTemplate(Html.XEditableLink("CustomLabel1", "text")); - c.Bound(x => x.CustomLabel2) - .ClientTemplate(Html.XEditableLink("CustomLabel2", "text")); - c.Bound(x => x.CustomLabel3) - .ClientTemplate(Html.XEditableLink("CustomLabel3", "text")); - c.Bound(x => x.CustomLabel4) - .ClientTemplate(Html.XEditableLink("CustomLabel4", "text")); - }) - .ClientEvents(e => - { - e.OnDataBound("OnGridDataBound"); - e.OnDataBinding("OnGridDataBinding"); - e.OnError("OnGridError"); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("GoogleProductList", "FeedGoogleMerchantCenter"); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .PreserveGridState() - .EnableCustomBinding(true) - ) -
    -
    - \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml index c20ef9acd7..af40550606 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml @@ -1,38 +1,11 @@ -@model SmartStore.GoogleMerchantCenter.Models.GoogleProductModel - +@using SmartStore.GoogleMerchantCenter; +@model SmartStore.GoogleMerchantCenter.Models.GoogleProductModel @{ Layout = ""; } - - @* VERY IMPORTANT for proper model binding *@ @Html.Hidden("__Type__", Model.GetType().AssemblyQualifiedName) - @Html.HiddenFor(m => m.ProductId) @@ -49,8 +22,8 @@ - @@ -189,4 +162,44 @@ @Html.ValidationMessageFor(m => m.CustomLabel4) -
    @Html.SmartLabelFor(m => m.Taxonomy) - @Html.TextBoxFor(m => m.Taxonomy, new { data_provide = "typeahead", placeholder = ViewBag.DefaultCategory, style = "width: 97%", autocomplete = "off" }) + + @Html.DropDownListFor(model => model.Taxonomy, (IEnumerable)ViewBag.AvailableCategories, new { autocomplete = "off" }) @Html.ValidationMessageFor(m => m.Taxonomy)
    \ No newline at end of file + + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml index 5f41005fb9..d3352b5bc6 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.GoogleMerchantCenter.Providers +@using SmartStore.GoogleMerchantCenter; +@using SmartStore.GoogleMerchantCenter.Providers @using SmartStore.GoogleMerchantCenter.Models; @model ProfileConfigurationModel @{ @@ -6,13 +7,66 @@ } + + + + + + + + + + + + + + + + + + + + + + @@ -21,7 +75,7 @@ @Html.SmartLabelFor(m => m.ExpirationDays) @@ -124,40 +178,44 @@ @Html.ValidationMessageFor(m => m.Pattern) - - - - - - - - - - - - - - - - -
    +
    +
    @T("Plugins.Feed.Froogle.GeneralSettings")
    +
    +
    - @Html.SmartLabelFor(m => m.DefaultGoogleCategory) + @Html.SmartLabelFor(m => m.ExportShipping) + + @Html.EditorFor(m => m.ExportShipping) + @Html.ValidationMessageFor(m => m.ExportShipping) +
    + @Html.SmartLabelFor(m => m.SpecialPrice) + + @Html.EditorFor(m => m.SpecialPrice) + @Html.ValidationMessageFor(m => m.SpecialPrice) +
    + @Html.SmartLabelFor(m => m.ExportBasePrice) + + @Html.EditorFor(m => m.ExportBasePrice) + @Html.ValidationMessageFor(m => m.ExportBasePrice) +
    + @Html.SmartLabelFor(m => m.AdditionalImages) - @Html.TextBoxFor(m => m.DefaultGoogleCategory, new { data_provide = "typeahead", placeholder = Model.DefaultGoogleCategory, style = "width: 98%", autocomplete = "off", - data_items = 18, data_source = Model.AvailableGoogleCategoriesAsJson }) + @Html.EditorFor(m => m.AdditionalImages) + @Html.ValidationMessageFor(m => m.AdditionalImages) +
    +
    +
    @T("Plugins.Feed.Froogle.DefaultValueSettings")
    +
    + @T("Plugins.Feed.Froogle.DefaultValueSettingsNote") + +
    +
    +
    + @Html.SmartLabelFor(m => m.DefaultGoogleCategory) + + @Html.DropDownListFor(model => model.DefaultGoogleCategory, Model.AvailableCategories, new { autocomplete = "off" }) @Html.ValidationMessageFor(m => m.DefaultGoogleCategory)
    - @Html.EditorFor(m => m.ExpirationDays) + @Html.EditorFor(m => m.ExpirationDays, new { postfix = T("Time.Days").Text }) @Html.ValidationMessageFor(m => m.ExpirationDays)
    - @Html.SmartLabelFor(m => m.AdditionalImages) - - @Html.EditorFor(m => m.AdditionalImages) - @Html.ValidationMessageFor(m => m.AdditionalImages) -
    - @Html.SmartLabelFor(m => m.SpecialPrice) - - @Html.EditorFor(m => m.SpecialPrice) - @Html.ValidationMessageFor(m => m.SpecialPrice) -
    - @Html.SmartLabelFor(m => m.ExportShipping) - - @Html.EditorFor(m => m.ExportShipping) - @Html.ValidationMessageFor(m => m.ExportShipping) -
    - @Html.SmartLabelFor(m => m.ExportBasePrice) - - @Html.EditorFor(m => m.ExportBasePrice) - @Html.ValidationMessageFor(m => m.ExportBasePrice) -
    \ No newline at end of file + + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/Web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/Web.config index e9d36a3c51..26c177a9ab 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/Web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index c368c87dd7..71064e723d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,5 +1,17 @@ #Release Notes +##Google Merchant Center (GMC) 3.0.0.1 +###Improvements +* Perf: Added missing database indexes +* Export yes/no rather than true/false +###Bugfixes +* "identifier_exists" did not follow specification + +##Google Merchant Center (GMC) 2.6.0.1 +###Bugfixes +* Id should be unique when exporting attribute combinations as products +* No special price exported when the special price period was not specified + ##Google Merchant Center (GMC) 2.5.0.1 ###Bugfixes * GMC feed does not generate the sale price if the sale price is set for a future date diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config index 3527aa9a5c..aeca1e30ef 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config @@ -1,12 +1,12 @@  - - - - - - - + + + + + + + - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config index ba87d0f098..522d79a602 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -1,116 +1,148 @@ - + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs b/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs index c5259e6e0f..3bbdb6c1d7 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; -using Autofac; +using Autofac; using FluentValidation; using FluentValidation.Results; -using SmartStore.Core.Localization; using SmartStore.OfflinePayment.Models; using SmartStore.OfflinePayment.Settings; -using SmartStore.OfflinePayment.Validators; using SmartStore.Services; +using SmartStore.Services.Media; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; -using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; +using SmartStore.Web.Framework.Theming; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; namespace SmartStore.OfflinePayment.Controllers { @@ -23,18 +22,18 @@ namespace SmartStore.OfflinePayment.Controllers public class OfflinePaymentController : PaymentControllerBase { private readonly IComponentContext _ctx; - private readonly ICommonServices _services; - private readonly IStoreService _storeService; + private readonly HttpContextBase _httpContext; + private readonly IPictureService _pictureService; public OfflinePaymentController( - ICommonServices services, - IStoreService storeService, - IComponentContext ctx) + HttpContextBase httpContext, + IComponentContext ctx, + IPictureService pictureService) { - this._services = services; - this._storeService = storeService; - this._ctx = ctx; - } + _httpContext = httpContext; + _ctx = ctx; + _pictureService = pictureService; + } #region Global @@ -57,10 +56,15 @@ private TModel ConfigureGet(Action fn = null { var model = new TModel(); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + var store = storeScope == 0 + ? Services.StoreContext.CurrentStore + : Services.StoreService.GetStoreById(storeScope); + model.PrimaryStoreCurrencyCode = store.PrimaryStoreCurrency.CurrencyCode; model.DescriptionText = settings.DescriptionText; + model.PaymentMethodLogo = settings.ThumbnailPictureId; model.AdditionalFee = settings.AdditionalFee; model.AdditionalFeePercentage = settings.AdditionalFeePercentage; @@ -70,7 +74,7 @@ private TModel ConfigureGet(Action fn = null } var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, Services.Settings); return model; } @@ -83,10 +87,11 @@ private void ConfigurePost(TModel model, FormCollection form, ModelState.Clear(); var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); - var settings = _services.Settings.LoadSetting(storeScope); + int storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); settings.DescriptionText = model.DescriptionText; + settings.ThumbnailPictureId = model.PaymentMethodLogo; settings.AdditionalFee = model.AdditionalFee; settings.AdditionalFeePercentage = model.AdditionalFeePercentage; @@ -95,9 +100,12 @@ private void ConfigurePost(TModel model, FormCollection form, fn(settings); } - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); } [NonAction] @@ -108,6 +116,7 @@ private TModel PaymentInfoGet(Action fn = nu var settings = _ctx.Resolve(); var model = new TModel(); model.DescriptionText = GetLocalizedText(settings.DescriptionText); + model.ThumbnailUrl = _pictureService.GetUrl(settings.ThumbnailPictureId, 120, false); if (fn != null) { @@ -140,7 +149,7 @@ public override IList ValidatePaymentForm(FormCollection form) { if (type == "Manual") { - validator = new ManualPaymentInfoValidator(_services.Localization); + validator = new ManualPaymentInfoValidator(T); var model = new ManualPaymentInfoModel { CardholderName = form["CardholderName"], @@ -151,7 +160,7 @@ public override IList ValidatePaymentForm(FormCollection form) } else if (type == "DirectDebit") { - validator = new DirectDebitPaymentInfoValidator(_services.Localization); + validator = new DirectDebitPaymentInfoValidator(); var model = new DirectDebitPaymentInfoModel { EnterIBAN = form["EnterIBAN"], @@ -189,8 +198,8 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) paymentInfo.CreditCardType = form["CreditCardType"]; paymentInfo.CreditCardName = form["CardholderName"]; paymentInfo.CreditCardNumber = form["CardNumber"]; - paymentInfo.CreditCardExpireMonth = int.Parse(form["ExpireMonth"]); - paymentInfo.CreditCardExpireYear = int.Parse(form["ExpireYear"]); + paymentInfo.CreditCardExpireMonth = int.Parse(form["ExpireMonth"].SplitSafe(",")[0]); + paymentInfo.CreditCardExpireYear = int.Parse(form["ExpireYear"].SplitSafe(",")[0]); paymentInfo.CreditCardCvv2 = form["CardCode"]; } else if (type == "DirectDebit") @@ -255,11 +264,10 @@ public override string GetPaymentSummary(FormCollection form) } #endregion - - + #region CashOnDelivery - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult CashOnDeliveryConfigure() { var model = ConfigureGet(); @@ -267,7 +275,7 @@ public ActionResult CashOnDeliveryConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult CashOnDeliveryConfigure(CashOnDeliveryConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -285,11 +293,10 @@ public ActionResult CashOnDeliveryPaymentInfo() } #endregion - - + #region Invoice - [ChildActionOnly, AdminAuthorize] + [ChildActionOnly, AdminThemed, AdminAuthorize] public ActionResult InvoiceConfigure() { var model = ConfigureGet(); @@ -297,7 +304,7 @@ public ActionResult InvoiceConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult InvoiceConfigure(InvoiceConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -315,11 +322,10 @@ public ActionResult InvoicePaymentInfo() } #endregion - - + #region PayInStore - [ChildActionOnly, AdminAuthorize] + [ChildActionOnly, AdminThemed, AdminAuthorize] public ActionResult PayInStoreConfigure() { var model = ConfigureGet(); @@ -327,7 +333,7 @@ public ActionResult PayInStoreConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult PayInStoreConfigure(PayInStoreConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -345,11 +351,10 @@ public ActionResult PayInStorePaymentInfo() } #endregion - - + #region Prepayment - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult PrepaymentConfigure() { var model = ConfigureGet(); @@ -357,7 +362,7 @@ public ActionResult PrepaymentConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult PrepaymentConfigure(PrepaymentConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -375,11 +380,10 @@ public ActionResult PrepaymentPaymentInfo() } #endregion - - + #region DirectDebit - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult DirectDebitConfigure() { var model = ConfigureGet(); @@ -387,7 +391,7 @@ public ActionResult DirectDebitConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult DirectDebitConfigure(DirectDebitConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -401,25 +405,24 @@ public ActionResult DirectDebitConfigure(DirectDebitConfigurationModel model, Fo public ActionResult DirectDebitPaymentInfo() { var model = PaymentInfoGet(); + var paymentData = _httpContext.GetCheckoutState().PaymentData; - var form = this.GetPaymentData(); - model.DirectDebitAccountHolder = form["DirectDebitAccountHolder"]; - model.DirectDebitAccountNumber = form["DirectDebitAccountNumber"]; - model.DirectDebitBankCode = form["DirectDebitBankCode"]; - model.DirectDebitBankName = form["DirectDebitBankName"]; - model.DirectDebitBic = form["DirectDebitBic"]; - model.DirectDebitCountry = form["DirectDebitCountry"]; - model.DirectDebitIban = form["DirectDebitIban"]; + model.DirectDebitAccountHolder = (string)paymentData.Get("DirectDebitAccountHolder"); + model.DirectDebitAccountNumber = (string)paymentData.Get("DirectDebitAccountNumber"); + model.DirectDebitBankCode = (string)paymentData.Get("DirectDebitBankCode"); + model.DirectDebitBankName = (string)paymentData.Get("DirectDebitBankName"); + model.DirectDebitBic = (string)paymentData.Get("DirectDebitBic"); + model.DirectDebitCountry = (string)paymentData.Get("DirectDebitCountry"); + model.DirectDebitIban = (string)paymentData.Get("DirectDebitIban"); return PartialView(model); } #endregion - - + #region Manual - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult ManualConfigure() { var model = ConfigureGet((m, s) => @@ -441,7 +444,7 @@ public ActionResult ManualConfigure() return View(model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult ManualConfigure(ManualConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -490,18 +493,23 @@ public ActionResult ManualPaymentInfo() } // set postback values - var form = this.GetPaymentData(); - model.CardholderName = form["CardholderName"]; - model.CardNumber = form["CardNumber"]; - model.CardCode = form["CardCode"]; + var paymentData = _httpContext.GetCheckoutState().PaymentData; + model.CardholderName = (string)paymentData.Get("CardholderName"); + model.CardNumber = (string)paymentData.Get("CardNumber"); + model.CardCode = (string)paymentData.Get("CardCode"); - var selectedCcType = model.CreditCardTypes.Where(x => x.Value.Equals(form["CreditCardType"], StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + var creditCardType = (string)paymentData.Get("CreditCardType"); + var selectedCcType = model.CreditCardTypes.Where(x => x.Value.Equals(creditCardType, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (selectedCcType != null) selectedCcType.Selected = true; - var selectedMonth = model.ExpireMonths.Where(x => x.Value.Equals(form["ExpireMonth"], StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + + var expireMonth = (string)paymentData.Get("ExpireMonth"); + var selectedMonth = model.ExpireMonths.Where(x => x.Value.Equals(expireMonth, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (selectedMonth != null) selectedMonth.Selected = true; - var selectedYear = model.ExpireYears.Where(x => x.Value.Equals(form["ExpireYear"], StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + + var expireYear = (string)paymentData.Get("ExpireYear"); + var selectedYear = model.ExpireYears.Where(x => x.Value.Equals(expireYear, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); if (selectedYear != null) selectedYear.Selected = true; @@ -512,8 +520,7 @@ public ActionResult ManualPaymentInfo() #region PurchaseOrderNumber - [AdminAuthorize] - [ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult PurchaseOrderNumberConfigure() { var model = ConfigureGet(); @@ -521,13 +528,13 @@ public ActionResult PurchaseOrderNumberConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult PurchaseOrderNumberConfigure(PurchaseOrderNumberConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) - return InvoiceConfigure(); + return PurchaseOrderNumberConfigure(); - ConfigurePost(model, form); + ConfigurePost(model, form); return PurchaseOrderNumberConfigure(); } @@ -535,9 +542,9 @@ public ActionResult PurchaseOrderNumberConfigure(PurchaseOrderNumberConfiguratio public ActionResult PurchaseOrderNumberPaymentInfo() { var model = PaymentInfoGet(); + var paymentData = _httpContext.GetCheckoutState().PaymentData; - var form = this.GetPaymentData(); - model.PurchaseOrderNumber = form["PurchaseOrderNumber"]; + model.PurchaseOrderNumber = (string)paymentData.Get("PurchaseOrderNumber"); return PartialView("PurchaseOrderNumberPaymentInfo", model); } diff --git a/src/Plugins/SmartStore.OfflinePayment/Description.txt b/src/Plugins/SmartStore.OfflinePayment/Description.txt index db6f0241f9..d2ed286f27 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Description.txt +++ b/src/Plugins/SmartStore.OfflinePayment/Description.txt @@ -2,8 +2,8 @@ Description: Contains common offline payment methods like Direct Debit, Invoice, Prepayment etc. Group: Payment SystemName: SmartStore.OfflinePayment -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 0 FileName: SmartStore.OfflinePayment.dll ResourceRootKey: Plugins.SmartStore.OfflinePayment diff --git a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml index a9f2235ba1..aabebe0d5a 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml @@ -13,17 +13,11 @@ Geben Sie hier die Beschreibung an, die dem Kunden im Bestellprozess angezeigt wird. - - Zusätzliche Gebühr + + Logo - - Bestimmt die Gebühr, die dem Kunden für die Nutzung dieser Zahlart berechnet wird. - - - Zusätzliche Gebühren (prozentual) - - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Ein fester Wert wird verwendet, falls diese Option nicht aktiviert ist. + + Laden Sie hier eine Grafik hoch, welche auf der Zahlartauswahlseite als Thumbnail der Zahlart angezeigt wird. @@ -40,7 +34,7 @@ @@ -61,7 +55,7 @@ @@ -103,7 +97,7 @@ @@ -121,6 +115,13 @@ + + + + + Zahlungsstatus nach Bestellabschluss @@ -150,7 +151,7 @@ @@ -199,21 +200,6 @@ Ja - - Geben Sie bitte den Namen des Kontoinhabers ein. - - - Geben Sie bitte Ihre Kontonummer ein. - - - Geben Sie bitte die Bankleitzahl (BLZ) ein. - - - Geben Sie bitte die IBAN (International Bank Account Number) ein. - - - Geben Sie bitte die BIC (Bank Interchange Code) ein. - diff --git a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml index af3d0cc477..0dcc2a5491 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml @@ -1,22 +1,23 @@  + + Offline payment methods + + + Provides offline payment methods, e.g. direct debit, credit card, invoice, prepayment, cash on delivery etc. + + Description Enter info that will be shown to customers during checkout. - - Additional fee + + Logo - - Determines the additional fee. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. + + Upload an image which will be displayed as thumbnail of the payment method on the payment selection page. @@ -114,6 +115,13 @@ + + + + + Payment status after order completion @@ -195,21 +203,6 @@ Yes - - Please enter the name of the account holder. - - - Please enter your account number. - - - Please enter the bank code. - - - Please enter the IBAN (International Bank Account Number). - - - Please enter the BIC (Bank Interchange Code). - diff --git a/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs index ba59935fdb..f69436a624 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs @@ -3,21 +3,28 @@ using SmartStore.OfflinePayment.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; +using System.ComponentModel.DataAnnotations; namespace SmartStore.OfflinePayment.Models { public abstract class ConfigurationModelBase : ModelBase { + public string PrimaryStoreCurrencyCode { get; set; } + [AllowHtml] [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.DescriptionText")] public string DescriptionText { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.AdditionalFee")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.AdditionalFeePercentage")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } - } + + [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.PaymentMethodLogo")] + [UIHint("Picture")] + public int PaymentMethodLogo { get; set; } + } public class CashOnDeliveryConfigurationModel : ConfigurationModelBase { diff --git a/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs b/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs index b13dd98de0..6046a28975 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs @@ -2,13 +2,17 @@ using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; +using FluentValidation; +using SmartStore.Core.Localization; +using SmartStore.Web.Framework.Validators; namespace SmartStore.OfflinePayment.Models { public abstract class PaymentInfoModelBase : ModelBase { public string DescriptionText { get; set; } - } + public string ThumbnailUrl { get; set; } + } public class CashOnDeliveryPaymentInfoModel : PaymentInfoModelBase { @@ -103,4 +107,30 @@ public class PurchaseOrderNumberPaymentInfoModel : PaymentInfoModelBase [AllowHtml] public string PurchaseOrderNumber { get; set; } } + + #region validators + + public class DirectDebitPaymentInfoValidator : AbstractValidator + { + public DirectDebitPaymentInfoValidator() + { + RuleFor(x => x.DirectDebitAccountHolder).NotEmpty(); + RuleFor(x => x.DirectDebitAccountNumber).NotEmpty().When(x => x.EnterIBAN == "no-iban"); + RuleFor(x => x.DirectDebitBankCode).NotEmpty().When(x => x.EnterIBAN == "no-iban"); + RuleFor(x => x.DirectDebitIban).Matches(RegularExpressions.IsIban).When(x => x.EnterIBAN == "iban"); + RuleFor(x => x.DirectDebitBic).Matches(RegularExpressions.IsBic).When(x => x.EnterIBAN == "iban"); + } + } + + public class ManualPaymentInfoValidator : AbstractValidator + { + public ManualPaymentInfoValidator(Localizer T) + { + RuleFor(x => x.CardholderName).NotEmpty(); + RuleFor(x => x.CardNumber).CreditCard().WithMessage(T("Payment.CardNumber.Wrong")); + RuleFor(x => x.CardCode).CreditCardCvvNumber(); + } + } + + #endregion } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs b/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs index 52fbe9bc18..89c409a2b8 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs @@ -6,6 +6,7 @@ namespace SmartStore.OfflinePayment.Settings public abstract class PaymentSettingsBase : ISettings { public string DescriptionText { get; set; } + public int ThumbnailPictureId { get; set; } public decimal AdditionalFee { get; set; } public bool AdditionalFeePercentage { get; set; } } diff --git a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj index 9083d62e99..d7f708d887 100644 --- a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj +++ b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.OfflinePayment SmartStore.OfflinePayment - v4.5.1 + v4.6.1 512 @@ -43,6 +44,7 @@ + true @@ -82,52 +84,56 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - - ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll + False ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False + + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - @@ -153,8 +159,6 @@ - - @@ -209,15 +213,9 @@ PreserveNewest - - PreserveNewest - PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/src/Plugins/SmartStore.OfflinePayment/Validators/DirectDebitPaymentInfoValidator.cs b/src/Plugins/SmartStore.OfflinePayment/Validators/DirectDebitPaymentInfoValidator.cs deleted file mode 100644 index 7698daebba..0000000000 --- a/src/Plugins/SmartStore.OfflinePayment/Validators/DirectDebitPaymentInfoValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using SmartStore.OfflinePayment.Models; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Validators; - -namespace SmartStore.OfflinePayment.Validators -{ - public class DirectDebitPaymentInfoValidator : AbstractValidator - { - public DirectDebitPaymentInfoValidator(ILocalizationService localize) - { - RuleFor(x => x.DirectDebitAccountHolder).NotEmpty() - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitAccountHolderRequired")); - - - RuleFor(x => x.DirectDebitAccountNumber).NotEmpty().When(x => x.EnterIBAN == "no-iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitAccountNumberRequired")); - - RuleFor(x => x.DirectDebitBankCode).NotEmpty().When(x => x.EnterIBAN == "no-iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitBankCodeRequired")); - - - RuleFor(x => x.DirectDebitIban).Matches(RegularExpressions.IsIban).When(x => x.EnterIBAN == "iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitIbanRequired")); - - RuleFor(x => x.DirectDebitBic).Matches(RegularExpressions.IsBic).When(x => x.EnterIBAN == "iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitBicRequired")); - }} -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs b/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs index 5fa9e03a2f..a7fac39eb0 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs @@ -14,7 +14,8 @@ public ManualPaymentInfoValidator(ILocalizationService localizationService) //http://benjii.me/2010/11/credit-card-validator-attribute-for-asp-net-mvc-3/ RuleFor(x => x.CardholderName).NotEmpty().WithMessage(localizationService.GetResource("Payment.CardholderName.Required")); - RuleFor(x => x.CardNumber).IsCreditCard().WithMessage(localizationService.GetResource("Payment.CardNumber.Wrong")); - RuleFor(x => x.CardCode).Matches(@"^[0-9]{3,4}$").WithMessage(localizationService.GetResource("Payment.CardCode.Wrong")); - }} + RuleFor(x => x.CardNumber).CreditCard().WithMessage(localizationService.GetResource("Payment.CardNumber.Wrong")); + RuleFor(x => x.CardCode).CreditCardCvvNumber(); + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml index 49303754cf..6893c04136 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml @@ -1,87 +1,58 @@ @model SmartStore.OfflinePayment.Models.DirectDebitPaymentInfoModel @using SmartStore.Web.Framework; - @{ Layout = ""; } -

    - @Html.Raw(Model.DescriptionText) -

    - @Html.Hidden("OfflinePaymentMethodType", "DirectDebit") -
    -
    - @Html.LabelFor(model => model.EnterIBAN, new { @class = "control-label" }) -
    - - +@if (Model.ThumbnailUrl.HasValue()) +{ +
    + +
    + @Html.Raw(Model.DescriptionText)
    - -
    - @Html.LabelFor(model => model.DirectDebitAccountHolder, new { @class="control-label required" }) -
    - @Html.TextBoxFor(model => model.DirectDebitAccountHolder, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitAccountHolder) -
    +} +else +{ +
    + @Html.Raw(Model.DescriptionText)
    +} -
    -
    - @Html.LabelFor(model => model.DirectDebitAccountNumber, new { @class="control-label required" }) -
    - @Html.TextBoxFor(model => model.DirectDebitAccountNumber, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitAccountNumber) -
    -
    -
    - @Html.LabelFor(model => model.DirectDebitBankCode, new { @class="control-label required" }) -
    - @Html.TextBoxFor(model => model.DirectDebitBankCode, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitBankCode) -
    -
    -
    - @Html.LabelFor(model => model.DirectDebitCountry, new { @class="control-label required" }) -
    - @Html.TextBoxFor(model => model.DirectDebitCountry, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitCountry) +
    +
    + @Html.LabelFor(model => model.EnterIBAN, new { @class = "col-md-3 col-form-label" }) +
    +
    +
    + + +
    +
    + + +
    -
    +
    +
    + + @Html.ControlGroupFor(model => model.DirectDebitAccountHolder, required: true, breakpoint: "md") + +
    + @Html.ControlGroupFor(model => model.DirectDebitAccountNumber, required: true, breakpoint: "md") + @Html.ControlGroupFor(model => model.DirectDebitBankCode, required: true, breakpoint: "md") + @Html.ControlGroupFor(model => model.DirectDebitCountry, required: true, breakpoint: "md")
    -
    - @Html.LabelFor(model => model.DirectDebitIban, new { @class="control-label required" }) -
    - @Html.TextBoxFor(model => model.DirectDebitIban, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitIban) -
    -
    -
    - @Html.LabelFor(model => model.DirectDebitBic, new { @class="control-label required" }) -
    - @Html.TextBoxFor(model => model.DirectDebitBic, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitBic) -
    -
    + @Html.ControlGroupFor(model => model.DirectDebitIban, required: true, breakpoint: "md") + @Html.ControlGroupFor(model => model.DirectDebitBic, required: true, breakpoint: "md")
    -
    - @Html.LabelFor(model => model.DirectDebitBankName, new { @class = "control-label" }) -
    - @Html.TextBoxFor(model => model.DirectDebitBankName, new { autocomplete = "off" }) - @Html.ValidationMessageFor(model => model.DirectDebitBankName) -
    -
    + @Html.ControlGroupFor(model => model.DirectDebitBankName, required: true, breakpoint: "md")
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalInstalments/Promotion.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalInstalments/Promotion.cshtml new file mode 100644 index 0000000000..ab7d6645c1 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalInstalments/Promotion.cshtml @@ -0,0 +1,7 @@ +@using SmartStore.PayPal.Services; +@model FinancingOptions +
    +
    + @{ Html.RenderPartial("Promotion.Content"); } +
    +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalInstalments/PromotionPopup.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalInstalments/PromotionPopup.cshtml new file mode 100644 index 0000000000..56690c6801 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalInstalments/PromotionPopup.cshtml @@ -0,0 +1,65 @@ +@using SmartStore.PayPal.Services; +@model FinancingOptions +@{ + var defaultOption = Model.GetDefaultOption(false); +} +
    +
    + @T( +
    +
    @T("Plugins.Payments.PayPalInstalments.PromotionDetailsTitle")
    +
    @Html.Raw(T("Plugins.Payments.PayPalInstalments.PromotionDetailsIntro"))
    +
    @T("Plugins.Payments.PayPalInstalments.NetLoanAmount"): @Model.NetLoanAmount.ToString()
    +
    + @foreach (var option in Model.Qualified) + { +
    +
    + @(T("Plugins.Payments.PayPalInstalments.PlanX", option.Index)) @(defaultOption != null && defaultOption.Index == option.Index ? " *" : "") +
    +
    + + + + + + @*PayPal review: do not display last instalment.*@ + @*@if (option.MonthlyPayment.Amount != option.LastInstalment.Amount) + { + + + + + }*@ + + + + + + + + + + + + + + + + +
    @T("Plugins.Payments.PayPalInstalments.XMonthlyInstalmentsAmount", option.Term)@(option.MonthlyPayment.ToString())
    @T("Plugins.Payments.PayPalInstalments.LastInstalment")@(option.LastInstalment.ToString())
    @T("Plugins.Payments.PayPalInstalments.FixedNominalRate"):@(Math.Round(option.NominalRate, 2).ToString("0.00")) %
    @T("Plugins.Payments.PayPalInstalments.AnnualPercentageRate"):@(Math.Round(option.AnnualPercentageRate, 2).ToString("0.00")) %
    @T("Plugins.Payments.PayPalInstalments.InterestAmount"):@(option.TotalInterest.ToString())
    @T("Plugins.Payments.PayPalInstalments.TotalPayableAmount"):@(option.TotalCost.ToString())
    +
    +
    + } +
    +
    + @if (defaultOption != null) + { +
    * @T("Plugins.Payments.PayPalInstalments.RepresentativeExample").
    + } +
    @T("Plugins.Payments.PayPalInstalments.Lender"): @Html.Raw(Model.Lender)
    +
    +
    +
    +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 129922ca27..a4f7f4476c 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -1,20 +1,22 @@ -@using SmartStore.PayPal; -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal.Models; @using SmartStore.Web.Framework; -@using SmartStore.Web.Framework.UI; @model PayPalPlusConfigurationModel @{ Layout = ""; - Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); - - var hasCredentials = (Model.ClientId.HasValue() && Model.Secret.HasValue()); } +
    + +
    +
    -
    - +
    @Html.Raw(T("Plugins.Payments.PayPalPlus.AdminInstruction")) +
    @@ -24,123 +26,22 @@
    -@Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) +@{ Html.RenderAction("StoreScopeConfiguration", "Setting", new { area = "Admin" }); } @Html.ValidationSummary(false) @using (Html.BeginForm()) { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    -
    -
    - @Model.ConfigGroups.SafeGet(0) -
    -
    -
    - @Html.SmartLabelFor(model => model.UseSandbox) - - @Html.EditorFor(model => model.UseSandbox) - @Html.ValidationMessageFor(model => model.UseSandbox) -
    - @Html.SmartLabelFor(model => model.ClientId) - - @Html.SettingOverrideCheckbox(model => model.ClientId) - @Html.TextBoxFor(model => model.ClientId, new { @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.ClientId) -
    - @Html.SmartLabelFor(model => model.Secret) - - @Html.SettingOverrideCheckbox(model => model.Secret) - @Html.TextBoxFor(model => model.Secret, new { @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.Secret) -
    -
    -
    - @Model.ConfigGroups.SafeGet(1) -
    -
    -
    - @Html.SmartLabelFor(model => model.SecurityProtocol) - - @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) -
    - @Html.SmartLabelFor(model => model.ExperienceProfileId) - - @Html.SettingEditorFor(model => model.ExperienceProfileId) - - -   - @T(Model.ExperienceProfileId.HasValue() ? "Common.Refresh" : "Common.AddNew") - + @Html.HiddenFor(model => model.TransactMode) - @if (Model.ExperienceProfileId.HasValue()) - { - -  @T("Admin.Common.Delete") - - } + @Html.Partial("ApiConfiguration") - @Html.ValidationMessageFor(model => model.ExperienceProfileId) -
    - @Html.SmartLabelFor(model => model.WebhookId) - - @* IPNs and webhook messages have no store context, so multistore configuration not possible here *@ - @Html.EditorFor(model => model.WebhookId) - - @if (Model.WebhookId.HasValue()) - { - -  @T("Admin.Common.Delete") - - } - else - { - -  @T("Common.AddNew") - - } - - @Html.ValidationMessageFor(model => model.WebhookId) -
    @@ -150,10 +51,9 @@ @Html.SmartLabelFor(model => model.ThirdPartyPaymentMethods) @@ -180,7 +80,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) @@ -193,15 +93,5 @@ @Html.ValidationMessageFor(model => model.AdditionalFeePercentage) - - - -
    - @Model.ConfigGroups.SafeGet(2) + @T("Common.Misc")
    - @Html.SettingOverrideCheckbox(model => model.ThirdPartyPaymentMethods) - @Html.ListBoxFor(x => x.ThirdPartyPaymentMethods, - new MultiSelectList(Model.AvailableThirdPartyPaymentMethods, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) + @Html.SettingEditorFor(model => model.ThirdPartyPaymentMethods, @
    + @Html.EditorFor(model => model.ThirdPartyPaymentMethods, "SelectList", new { multiple = "multiple", data_tags = "true", selectOptions = Model.AvailableThirdPartyPaymentMethods }) +
    ) @Html.ValidationMessageFor(model => model.ThirdPartyPaymentMethods)
    - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    -   - - -
    } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml deleted file mode 100644 index 266ae25887..0000000000 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml +++ /dev/null @@ -1,66 +0,0 @@ -@using SmartStore.Web.Models.Checkout; -@using SmartStore.PayPal.Models; -@model PayPalPlusCheckoutModel -@{ - Layout = "~/Views/Shared/_Root.cshtml"; - Html.AddTitleParts(T("PageTitle.Checkout").Text); -} - -@Html.Partial("PaymentWallScripting") - -
    -
    -

    @T("Checkout.SelectPaymentMethod")

    -
    -
    - @Html.Widget("mobile_checkout_payment_method_top") - - @if (Model.ErrorMessage.HasValue()) - { -
    -
      -
    • @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable"))
    • -
    • - @if (!Model.ApprovalUrl.HasValue()) - { - @T("Plugins.SmartStore.PayPal.NoApprovalUrlReturned") - } - @Model.ErrorMessage -
    • -
    -
    - } - else if (Model.PayPalPlusPseudoMessageFlag.HasValue() && Model.PayPalPlusPseudoMessageFlag == "1") - { -
    -
      -
    • @Html.Raw(T("Plugins.Payments.PayPalPlus.SorryFailure"))
    • -
    -
    - } - - @if (Model.FullDescription.HasValue()) - { -
    - @Html.Raw(Model.FullDescription) -
    - } - - @if (Model.HasAnyFees) - { - - } - -
    - -
    - -
    - -
    - - @Html.Widget("mobile_checkout_payment_method_bottom") -
    -
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index 461b02a3ab..4c6f0456ab 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -2,26 +2,26 @@ @using SmartStore.PayPal.Models; @model PayPalPlusCheckoutModel @{ - Layout = "~/Views/Shared/_Checkout.cshtml"; - Html.AddTitleParts(T("PageTitle.Checkout").Text); + Layout = "_Checkout"; + Html.AddTitleParts(T("PageTitle.Checkout").Text); } @section orderProgress { - @Html.Action("CheckoutProgress", "Checkout", new { step = CheckoutProgressStep.Payment }) + @{ Html.RenderAction("CheckoutProgress", "Checkout", new { step = CheckoutProgressStep.Payment }); } } -@Html.Partial("PaymentWallScripting") +@{ Html.RenderPartial("PaymentWallScripting"); }

    @T("Checkout.SelectPaymentMethod")

    -
    - @Html.Widget("checkout_payment_method_top") +
    + @{ Html.RenderWidget("checkout_payment_method_top"); } @if (Model.ErrorMessage.HasValue()) { -
    +
    @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable"))
    @if (!Model.ApprovalUrl.HasValue()) @@ -33,7 +33,7 @@ } else if (Model.PayPalPlusPseudoMessageFlag.HasValue() && Model.PayPalPlusPseudoMessageFlag == "1") { -
    +
    @Html.Raw(T("Plugins.Payments.PayPalPlus.SorryFailure"))
    } @@ -47,23 +47,25 @@ @if (Model.HasAnyFees) { - \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index 212ae653cc..5c2bdca8b4 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal +@using SmartStore.PayPal.Models @model PayPalPlusCheckoutModel \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml index 5846c1ed43..0534131b2a 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -5,11 +5,18 @@ Layout = ""; } +
    + +
    +
    -
    - +
    @Html.Raw(T("Plugins.Payments.PayPalStandard.AdminInstruction")) +
    @@ -19,19 +26,11 @@
    -@Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) +@{ Html.RenderAction("StoreScopeConfiguration", "Setting", new { area = "Admin" }); } @using (Html.BeginForm()) { - - - - @@ -64,10 +63,11 @@ - + @@ -105,12 +105,31 @@ @Html.ValidationMessageFor(model => model.PassProductNamesAndTotals) + + + + + + + + @@ -128,7 +147,7 @@ @Html.SmartLabelFor(model => model.IpnUrl) @@ -139,29 +158,5 @@ - - - -
    - @Html.SmartLabelFor(model => model.SecurityProtocol) - - @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) -
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -56,7 +55,7 @@ @Html.SmartLabelFor(model => model.PdtToken) - @Html.SettingEditorFor(model => model.PdtToken) + @Html.SettingEditorFor(model => model.PdtToken) @Html.ValidationMessageFor(model => model.PdtToken)
    @Html.SmartLabelFor(model => model.PdtValidateOrderTotal) - @Html.SettingEditorFor(model => model.PdtValidateOrderTotal) - @Html.ValidationMessageFor(model => model.PdtValidateOrderTotal) - + @Html.SettingEditorFor(model => model.PdtValidateOrderTotal, + Html.CheckBoxFor(model => model.PdtValidateOrderTotal, new { data_toggler_for = "#PdtValidateOnlyWarnContainer" })) + @Html.ValidationMessageFor(model => model.PdtValidateOrderTotal) +
    @@ -83,7 +83,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    + @Html.SmartLabelFor(model => model.IsShippingAddressRequired) + + @Html.SettingEditorFor(model => model.IsShippingAddressRequired) + @Html.ValidationMessageFor(model => model.IsShippingAddressRequired) +
    + @Html.SmartLabelFor(model => model.UsePayPalAddress) + + @Html.SettingEditorFor(model => model.UsePayPalAddress) + @Html.ValidationMessageFor(model => model.UsePayPalAddress) +
    @Html.SmartLabelFor(model => model.EnableIpn) - @Html.SettingEditorFor(model => model.EnableIpn) + @Html.SettingEditorFor(model => model.EnableIpn, + Html.CheckBoxFor(model => model.EnableIpn, new { data_toggler_for = ".ipn-handling" })) @Html.ValidationMessageFor(model => model.EnableIpn)
    - @Html.SettingEditorFor(model => model.IpnUrl) + @Html.SettingEditorFor(model => model.IpnUrl) @Html.ValidationMessageFor(model => model.IpnUrl)
    -   - - -
    } - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml index ee77251801..9eb786864c 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml @@ -4,9 +4,7 @@ @using SmartStore.Web.Framework;
    - - PayPal - + PayPal
    @T("Plugins.Payments.PayPalStandard.Fields.RedirectionTip")
    diff --git a/src/Plugins/SmartStore.PayPal/Views/Shared/Partials/ApiConfiguration.cshtml b/src/Plugins/SmartStore.PayPal/Views/Shared/Partials/ApiConfiguration.cshtml new file mode 100644 index 0000000000..b0d068e120 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/Shared/Partials/ApiConfiguration.cshtml @@ -0,0 +1,115 @@ +@using SmartStore.PayPal; +@using SmartStore.PayPal.Models; +@using SmartStore.Web.Framework; +@using SmartStore.Web.Framework.UI; +@model ApiConfigurationModel +@{ + Layout = ""; + + var hasCredentials = Model.ClientId.HasValue() && Model.Secret.HasValue(); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + @T("Plugins.SmartStore.PayPal.AccessData") +
    +
    +
    + @Html.SmartLabelFor(model => model.UseSandbox) + + @Html.EditorFor(model => model.UseSandbox) + @Html.ValidationMessageFor(model => model.UseSandbox) +
    + @Html.SmartLabelFor(model => model.ClientId) + + @Html.SettingEditorFor(model => model.ClientId) + @Html.ValidationMessageFor(model => model.ClientId) +
    + @Html.SmartLabelFor(model => model.Secret) + + @Html.SettingEditorFor(model => model.Secret) + @Html.ValidationMessageFor(model => model.Secret) +
    +
    +
    + @T("Admin.Common.DataExchange") +
    +
    +
    + @Html.SmartLabelFor(model => model.ExperienceProfileId) + + @Html.SettingEditorFor(model => model.ExperienceProfileId) + + + + @T(Model.ExperienceProfileId.HasValue() ? "Common.Refresh" : "Common.AddNew") + + + @if (Model.ExperienceProfileId.HasValue()) + { + + + @T("Admin.Common.Delete") + + } + + @Html.ValidationMessageFor(model => model.ExperienceProfileId) +
    + @Html.SmartLabelFor(model => model.WebhookId) + +
    + @* IPNs and webhook messages have no store context, so multistore configuration not possible here *@ + @Html.EditorFor(model => model.WebhookId) + +
    + @if (Model.WebhookId.HasValue()) + { + + + @T("Admin.Common.Delete") + + } + else + { + + + @T("Common.AddNew") + + } +
    +
    + @Html.ValidationMessageFor(model => model.WebhookId) +
    diff --git a/src/Plugins/SmartStore.PayPal/Views/Web.config b/src/Plugins/SmartStore.PayPal/Views/Web.config index e9d36a3c51..26c177a9ab 100644 --- a/src/Plugins/SmartStore.PayPal/Views/Web.config +++ b/src/Plugins/SmartStore.PayPal/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/AddressVerifyResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/AddressVerifyResponseType1.datasource index ec0277609b..0499f83aad 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/AddressVerifyResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/AddressVerifyResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.AddressVerifyResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.AddressVerifyResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BAUpdateResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BAUpdateResponseType1.datasource index 91a053f5ff..37895ac0b7 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BAUpdateResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BAUpdateResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BAUpdateResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BAUpdateResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMButtonSearchResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMButtonSearchResponseType1.datasource index 0f746c7d08..9c33167a56 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMButtonSearchResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMButtonSearchResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMButtonSearchResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMButtonSearchResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMCreateButtonResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMCreateButtonResponseType1.datasource index 1029809ff8..0383fe0796 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMCreateButtonResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMCreateButtonResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMCreateButtonResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMCreateButtonResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetButtonDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetButtonDetailsResponseType1.datasource index cdde10a947..6081bdebf1 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetButtonDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetButtonDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMGetButtonDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMGetButtonDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetInventoryResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetInventoryResponseType1.datasource index 0ee379f30e..0bacb48be0 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetInventoryResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMGetInventoryResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMGetInventoryResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMGetInventoryResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMManageButtonStatusResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMManageButtonStatusResponseType1.datasource index c1708ffd10..a722a6345c 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMManageButtonStatusResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMManageButtonStatusResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMManageButtonStatusResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMManageButtonStatusResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMSetInventoryResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMSetInventoryResponseType1.datasource index 6be469df52..8879fbc0e0 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMSetInventoryResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMSetInventoryResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMSetInventoryResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMSetInventoryResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMUpdateButtonResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMUpdateButtonResponseType1.datasource index b9228e6bdf..f27ccdaae9 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMUpdateButtonResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BMUpdateButtonResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BMUpdateButtonResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BMUpdateButtonResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillOutstandingAmountResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillOutstandingAmountResponseType1.datasource index 59b9170ade..0d1dfe2afb 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillOutstandingAmountResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillOutstandingAmountResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BillOutstandingAmountResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BillOutstandingAmountResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillUserResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillUserResponseType1.datasource index 8577b782d7..81055388cb 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillUserResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/BillUserResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.BillUserResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.BillUserResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CancelRecoupResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CancelRecoupResponseType1.datasource index f3ae101fc3..c51de2581a 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CancelRecoupResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CancelRecoupResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.CancelRecoupResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.CancelRecoupResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CompleteRecoupResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CompleteRecoupResponseType1.datasource index 098c7c4fca..70d898e7d4 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CompleteRecoupResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CompleteRecoupResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.CompleteRecoupResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.CompleteRecoupResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateBillingAgreementResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateBillingAgreementResponseType1.datasource index 3e192ba71e..8c7ebc9bb2 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateBillingAgreementResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateBillingAgreementResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.CreateBillingAgreementResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.CreateBillingAgreementResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateMobilePaymentResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateMobilePaymentResponseType1.datasource index b18423f63d..1cdf9b1e0f 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateMobilePaymentResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateMobilePaymentResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.CreateMobilePaymentResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.CreateMobilePaymentResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateRecurringPaymentsProfileResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateRecurringPaymentsProfileResponseType1.datasource index bbfd271be0..5c9cb0e648 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateRecurringPaymentsProfileResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/CreateRecurringPaymentsProfileResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.CreateRecurringPaymentsProfileResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.CreateRecurringPaymentsProfileResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoAuthorizationResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoAuthorizationResponseType1.datasource index ba676b8620..8747cac4cd 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoAuthorizationResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoAuthorizationResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoAuthorizationResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoAuthorizationResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCancelResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCancelResponseType1.datasource index 37076edf33..9378c996c1 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCancelResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCancelResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoCancelResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoCancelResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCaptureResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCaptureResponseType1.datasource index d88232b3ef..1a1ffa838a 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCaptureResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoCaptureResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoCaptureResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoCaptureResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoDirectPaymentResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoDirectPaymentResponseType1.datasource index 2aa8bbaa06..91b243b409 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoDirectPaymentResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoDirectPaymentResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoDirectPaymentResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoDirectPaymentResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoExpressCheckoutPaymentResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoExpressCheckoutPaymentResponseType1.datasource index 850af8a0f2..b1dbc75af1 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoExpressCheckoutPaymentResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoExpressCheckoutPaymentResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoExpressCheckoutPaymentResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoExpressCheckoutPaymentResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoMobileCheckoutPaymentResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoMobileCheckoutPaymentResponseType1.datasource index 5e1ca5dd9c..f10fe528d5 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoMobileCheckoutPaymentResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoMobileCheckoutPaymentResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoMobileCheckoutPaymentResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoMobileCheckoutPaymentResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoNonReferencedCreditResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoNonReferencedCreditResponseType1.datasource index 534d76aa18..e87a49b9c7 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoNonReferencedCreditResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoNonReferencedCreditResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoNonReferencedCreditResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoNonReferencedCreditResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReauthorizationResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReauthorizationResponseType1.datasource index 0441cef50b..7d04514c27 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReauthorizationResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReauthorizationResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoReauthorizationResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoReauthorizationResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReferenceTransactionResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReferenceTransactionResponseType1.datasource index 7742527e2d..cbabdf5b0a 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReferenceTransactionResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoReferenceTransactionResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoReferenceTransactionResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoReferenceTransactionResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPAuthorizationResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPAuthorizationResponseType1.datasource index 67ca77b1f6..bc899e34f3 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPAuthorizationResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPAuthorizationResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoUATPAuthorizationResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoUATPAuthorizationResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPExpressCheckoutPaymentResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPExpressCheckoutPaymentResponseType1.datasource index c1a25d5f7c..c95fb68867 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPExpressCheckoutPaymentResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoUATPExpressCheckoutPaymentResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoUATPExpressCheckoutPaymentResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoUATPExpressCheckoutPaymentResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoVoidResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoVoidResponseType1.datasource index 588928591a..1dc8b82d84 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoVoidResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/DoVoidResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.DoVoidResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.DoVoidResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/EnterBoardingResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/EnterBoardingResponseType1.datasource index c9a4533ea7..ed91c43e7f 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/EnterBoardingResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/EnterBoardingResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.EnterBoardingResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.EnterBoardingResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExecuteCheckoutOperationsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExecuteCheckoutOperationsResponseType1.datasource index ed93656bab..fa5e3801f5 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExecuteCheckoutOperationsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExecuteCheckoutOperationsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.ExecuteCheckoutOperationsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.ExecuteCheckoutOperationsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExternalRememberMeOptOutResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExternalRememberMeOptOutResponseType1.datasource index 09e7a27f73..bbaf88e42b 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExternalRememberMeOptOutResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ExternalRememberMeOptOutResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.ExternalRememberMeOptOutResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.ExternalRememberMeOptOutResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAccessPermissionDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAccessPermissionDetailsResponseType1.datasource index 126aec7d4b..88e096ae06 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAccessPermissionDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAccessPermissionDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetAccessPermissionDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetAccessPermissionDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAuthDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAuthDetailsResponseType1.datasource index effcb91711..44e2dcf35c 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAuthDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetAuthDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetAuthDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetAuthDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBalanceResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBalanceResponseType1.datasource index 0195e8fb79..3129fc73dc 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBalanceResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBalanceResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetBalanceResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetBalanceResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBillingAgreementCustomerDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBillingAgreementCustomerDetailsResponseType1.datasource index 141d30f889..0134436118 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBillingAgreementCustomerDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBillingAgreementCustomerDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetBillingAgreementCustomerDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetBillingAgreementCustomerDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBoardingDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBoardingDetailsResponseType1.datasource index 735ce0038e..28f4ea795a 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBoardingDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetBoardingDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetBoardingDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetBoardingDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetExpressCheckoutDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetExpressCheckoutDetailsResponseType1.datasource index c37ffa9714..a27863d0c9 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetExpressCheckoutDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetExpressCheckoutDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetExpressCheckoutDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetExpressCheckoutDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetIncentiveEvaluationResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetIncentiveEvaluationResponseType1.datasource index 125a1ac540..241713bf6c 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetIncentiveEvaluationResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetIncentiveEvaluationResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetIncentiveEvaluationResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetIncentiveEvaluationResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetMobileStatusResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetMobileStatusResponseType1.datasource index 0baf71137d..f375d9cc3e 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetMobileStatusResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetMobileStatusResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetMobileStatusResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetMobileStatusResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetPalDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetPalDetailsResponseType1.datasource index 252c9f0bfa..3c46b59bfc 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetPalDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetPalDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetPalDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetPalDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetRecurringPaymentsProfileDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetRecurringPaymentsProfileDetailsResponseType1.datasource index 260411e174..85a15d3b4a 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetRecurringPaymentsProfileDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetRecurringPaymentsProfileDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetRecurringPaymentsProfileDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetRecurringPaymentsProfileDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetTransactionDetailsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetTransactionDetailsResponseType1.datasource index e347d410ed..1819d1286e 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetTransactionDetailsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/GetTransactionDetailsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.GetTransactionDetailsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.GetTransactionDetailsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/InitiateRecoupResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/InitiateRecoupResponseType1.datasource index f698a0f4e3..14402b3495 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/InitiateRecoupResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/InitiateRecoupResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.InitiateRecoupResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.InitiateRecoupResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManagePendingTransactionStatusResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManagePendingTransactionStatusResponseType1.datasource index 2aa919f0d2..76dff0b426 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManagePendingTransactionStatusResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManagePendingTransactionStatusResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.ManagePendingTransactionStatusResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.ManagePendingTransactionStatusResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManageRecurringPaymentsProfileStatusResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManageRecurringPaymentsProfileStatusResponseType1.datasource index 1c8e8e39b2..10d15f0fc9 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManageRecurringPaymentsProfileStatusResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ManageRecurringPaymentsProfileStatusResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.ManageRecurringPaymentsProfileStatusResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.ManageRecurringPaymentsProfileStatusResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/MassPayResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/MassPayResponseType1.datasource index 3c0bc96da1..f07b7d5a9b 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/MassPayResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/MassPayResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.MassPayResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.MassPayResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/Reference.cs b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/Reference.cs index b8c6682313..cc0825153e 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/Reference.cs +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/Reference.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.34209 +// Laufzeitversion:4.0.30319.42000 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ // -// Der Quellcode wurde automatisch mit Microsoft.VSDesigner generiert. Version 4.0.30319.34209. +// Der Quellcode wurde automatisch mit Microsoft.VSDesigner generiert. Version 4.0.30319.42000. // #pragma warning disable 1591 @@ -23,7 +23,7 @@ namespace SmartStore.PayPal.PayPalSvc { /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="PayPalAPISoapBinding", Namespace="urn:ebay:api:PayPalAPI")] @@ -1000,7 +1000,7 @@ private bool IsLocalFileSystemWebService(string url) { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="PayPalAPIAASoapBinding", Namespace="urn:ebay:api:PayPalAPI")] @@ -2229,7 +2229,7 @@ private bool IsLocalFileSystemWebService(string url) { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2275,7 +2275,7 @@ public UserIdPasswordType Credentials { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2380,7 +2380,7 @@ public string AuthToken { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2414,7 +2414,7 @@ public string Status { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2435,7 +2435,7 @@ public string ProfileID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2456,7 +2456,7 @@ public string ProfileID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2477,7 +2477,7 @@ public string ProfileID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2596,7 +2596,7 @@ public BasicAmountType LastPaymentAmount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -2631,7 +2631,7 @@ public string Value { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum CurrencyCodeType { @@ -3163,7 +3163,7 @@ public enum CurrencyCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -3377,7 +3377,7 @@ public bool FinalPaymentDueDateSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum RecurringPaymentsProfileStatusType { @@ -3399,7 +3399,7 @@ public enum RecurringPaymentsProfileStatusType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AutoBillType { @@ -3412,7 +3412,7 @@ public enum AutoBillType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -3469,7 +3469,7 @@ public string ProfileReference { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -3734,7 +3734,7 @@ public bool AddressNormalizationStatusSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum CountryCodeType { @@ -4500,7 +4500,7 @@ public enum CountryCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AddressOwnerCodeType { @@ -4516,7 +4516,7 @@ public enum AddressOwnerCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AddressStatusCodeType { @@ -4532,7 +4532,7 @@ public enum AddressStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AddressNormalizationStatusCodeType { @@ -4551,7 +4551,7 @@ public enum AddressNormalizationStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -4645,7 +4645,7 @@ public BasicAmountType TaxAmount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BillingPeriodType { @@ -4670,7 +4670,7 @@ public enum BillingPeriodType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -4864,7 +4864,7 @@ public ThreeDSecureRequestType ThreeDSecureRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum CreditCardTypeType { @@ -4892,7 +4892,7 @@ public enum CreditCardTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5047,7 +5047,7 @@ public EnhancedPayerInfoType EnhancedPayerInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PayPalUserStatusCodeType { @@ -5060,7 +5060,7 @@ public enum PayPalUserStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5129,7 +5129,7 @@ public string Suffix { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5162,7 +5162,7 @@ public string TaxId { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5171,7 +5171,7 @@ public partial class EnhancedPayerInfoType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5240,7 +5240,7 @@ public string AuthStatus3ds { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5322,7 +5322,7 @@ public string DCCReturnCode { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5355,7 +5355,7 @@ public string TransactionID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5460,7 +5460,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -5990,7 +5990,7 @@ public string BinEligibility { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentTransactionCodeType { @@ -6066,7 +6066,7 @@ public enum PaymentTransactionCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentCodeType { @@ -6082,7 +6082,7 @@ public enum PaymentCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum RefundSourceCodeType { @@ -6101,7 +6101,7 @@ public enum RefundSourceCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentStatusCodeType { @@ -6163,7 +6163,7 @@ public enum PaymentStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PendingStatusCodeType { @@ -6212,7 +6212,7 @@ public enum PendingStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ReversalReasonCodeType { @@ -6238,7 +6238,7 @@ public enum ReversalReasonCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum POSTransactionCodeType { @@ -6251,7 +6251,7 @@ public enum POSTransactionCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6333,7 +6333,7 @@ public string SecureMerchantAccountID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6394,7 +6394,7 @@ public RiskFilterDetailsType[] ReportFilters { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6439,7 +6439,7 @@ public string Description { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6448,7 +6448,7 @@ public partial class EnhancedPaymentInfoType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6519,7 +6519,7 @@ public ErrorParameterType[] ErrorParameters { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum SeverityCodeType { @@ -6538,7 +6538,7 @@ public enum SeverityCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6572,7 +6572,7 @@ public string ParamID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6605,7 +6605,7 @@ public string InstrumentID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6638,7 +6638,7 @@ public BMLOfferInfoType BMLOfferInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6659,7 +6659,7 @@ public string OfferTrackingID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6692,7 +6692,7 @@ public AddressType BillingAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6749,7 +6749,7 @@ public PaymentInfoType PaymentInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6819,7 +6819,7 @@ public string ProtectionEligibilityType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6876,7 +6876,7 @@ public string PartnerFundingSourceID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -6909,7 +6909,7 @@ public string CoupledPaymentID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7016,7 +7016,7 @@ public CoupledPaymentInfoType[] CoupledPaymentInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7085,7 +7085,7 @@ public string ShippingOptionName { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7131,7 +7131,7 @@ public string ImmutableID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7165,7 +7165,7 @@ public string ExternalRememberMeID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7210,7 +7210,7 @@ public ErrorType PaymentError { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7279,7 +7279,7 @@ public string SubType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7388,7 +7388,7 @@ public IncentiveAppliedDetailsType[] IncentiveAppliedDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum IncentiveSiteAppliedOnType { @@ -7407,7 +7407,7 @@ public enum IncentiveSiteAppliedOnType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum IncentiveAppliedStatusType { @@ -7422,7 +7422,7 @@ public enum IncentiveAppliedStatusType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -7736,7 +7736,7 @@ public RefreshTokenStatusDetailsType RefreshTokenStatusDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8246,7 +8246,7 @@ public bool PaymentReasonSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentCategoryType { @@ -8259,7 +8259,7 @@ public enum PaymentCategoryType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ShippingServiceCodeType { @@ -8314,7 +8314,7 @@ public enum ShippingServiceCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8542,7 +8542,7 @@ public bool ItemCategorySpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8599,7 +8599,7 @@ public string CartID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ProductCategoryType { @@ -8675,7 +8675,7 @@ public enum ProductCategoryType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8710,7 +8710,7 @@ public double Value { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8719,7 +8719,7 @@ public partial class EnhancedItemDataType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ItemCategoryType { @@ -8732,7 +8732,7 @@ public enum ItemCategoryType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AllowedPaymentMethodType { @@ -8751,7 +8751,7 @@ public enum AllowedPaymentMethodType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8760,7 +8760,7 @@ public partial class EnhancedPaymentDataType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentActionCodeType { @@ -8779,7 +8779,7 @@ public enum PaymentActionCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum RecurringFlagType { @@ -8792,7 +8792,7 @@ public enum RecurringFlagType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentReasonType { @@ -8808,7 +8808,7 @@ public enum PaymentReasonType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8842,7 +8842,7 @@ public ErrorType[] AuthorizationError { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AckCodeType { @@ -8870,7 +8870,7 @@ public enum AckCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8917,7 +8917,7 @@ public ErrorType[] SetDataError { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -8950,7 +8950,7 @@ public AuthorizationResponseType AuthorizationResponse { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9007,7 +9007,7 @@ public string SubType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9126,7 +9126,7 @@ public string ErrorCode { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum IncentiveTypeCodeType { @@ -9151,7 +9151,7 @@ public enum IncentiveTypeCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9185,7 +9185,7 @@ public string RequestId { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9268,7 +9268,7 @@ public string PayerID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9325,7 +9325,7 @@ public string PayerID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9394,7 +9394,7 @@ public APIAuthenticationType Type { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum APIAuthenticationType { @@ -9411,7 +9411,7 @@ public enum APIAuthenticationType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9613,7 +9613,7 @@ public string BankAccountVerificationStatus { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BoardingStatusType { @@ -9632,7 +9632,7 @@ public enum BoardingStatusType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum UserWithdrawalLimitTypeType { @@ -9648,7 +9648,7 @@ public enum UserWithdrawalLimitTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9741,7 +9741,7 @@ public AddressType BillingAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum MerchantPullStatusCodeType { @@ -9754,7 +9754,7 @@ public enum MerchantPullStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9883,7 +9883,7 @@ public BasicAmountType NetAmount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -9976,7 +9976,7 @@ public string PaymentSourceID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10021,7 +10021,7 @@ public MerchantPullInfoType MerchantPullInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10054,7 +10054,7 @@ public string EciSubmitted3DS { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10087,7 +10087,7 @@ public ThreeDSecureResponseType ThreeDSecureResponse { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10144,7 +10144,7 @@ public string AmountCurrency { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10203,7 +10203,7 @@ public string multiItem { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10237,7 +10237,7 @@ public string period { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10408,7 +10408,7 @@ public string recurring { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10443,7 +10443,7 @@ public string value { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10633,7 +10633,7 @@ public OptionType[] Options { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -10937,7 +10937,7 @@ public string StyleNumber { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum UnitOfMeasure { @@ -11017,7 +11017,7 @@ public enum UnitOfMeasure { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11099,7 +11099,7 @@ public string RedeemedOfferID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum RedeemedOfferType { @@ -11118,7 +11118,7 @@ public enum RedeemedOfferType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11151,7 +11151,7 @@ public BasicAmountType Amount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11245,7 +11245,7 @@ public AuctionInfoType Auction { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11290,7 +11290,7 @@ public string ReceiverID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11480,7 +11480,7 @@ public string[] SurveyChoiceSelected { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11489,7 +11489,7 @@ public partial class EnhancedCompleteRecoupResponseDetailsType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11548,7 +11548,7 @@ public bool PendingReasonSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11675,7 +11675,7 @@ public bool ModifyDateSpecified { [System.Xml.Serialization.XmlIncludeAttribute(typeof(BMManageButtonStatusResponseType))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(BMUpdateButtonResponseType))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(BMCreateButtonResponseType))] - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11788,7 +11788,7 @@ public System.Xml.XmlElement Any { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11797,7 +11797,7 @@ public partial class ExternalRememberMeOptOutResponseType : AbstractResponseType } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11819,7 +11819,7 @@ public ReverseTransactionResponseDetailsType ReverseTransactionResponseDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11854,7 +11854,7 @@ public string Locale { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11876,7 +11876,7 @@ public UpdateRecurringPaymentsProfileResponseDetailsType UpdateRecurringPayments } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11898,7 +11898,7 @@ public BillOutstandingAmountResponseDetailsType BillOutstandingAmountResponseDet } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11920,7 +11920,7 @@ public ManageRecurringPaymentsProfileStatusResponseDetailsType ManageRecurringPa } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11942,7 +11942,7 @@ public GetRecurringPaymentsProfileDetailsResponseDetailsType GetRecurringPayment } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11964,7 +11964,7 @@ public CreateRecurringPaymentsProfileResponseDetailsType CreateRecurringPayments } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -11986,7 +11986,7 @@ public DoNonReferencedCreditResponseDetailsType DoNonReferencedCreditResponseDet } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12021,7 +12021,7 @@ public FMFDetailsType FMFDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12043,7 +12043,7 @@ public string BillingAgreementID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12065,7 +12065,7 @@ public GetBillingAgreementCustomerDetailsResponseDetailsType GetBillingAgreement } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12087,7 +12087,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12135,7 +12135,7 @@ public BasicAmountType[] BalanceHoldings { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12157,7 +12157,7 @@ public DoMobileCheckoutPaymentResponseDetailsType DoMobileCheckoutPaymentRespons } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12179,7 +12179,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12227,7 +12227,7 @@ public string PaymentPending { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12236,7 +12236,7 @@ public partial class CreateMobilePaymentResponseType : AbstractResponseType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12272,7 +12272,7 @@ public AuthorizationInfoType AuthorizationInfo { /// [System.Xml.Serialization.XmlIncludeAttribute(typeof(DoUATPAuthorizationResponseType))] - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12333,7 +12333,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12394,7 +12394,7 @@ public string MsgSubID1 { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12439,7 +12439,7 @@ public int ExpYear { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12474,7 +12474,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12522,7 +12522,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12544,7 +12544,7 @@ public DoCaptureResponseDetailsType DoCaptureResponseDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12566,7 +12566,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12718,7 +12718,7 @@ public string PaymentAdviceCode { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12754,7 +12754,7 @@ public string Status { /// [System.Xml.Serialization.XmlIncludeAttribute(typeof(DoUATPExpressCheckoutPaymentResponseType))] - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12789,7 +12789,7 @@ public FMFDetailsType FMFDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12811,7 +12811,7 @@ public UATPDetailsType UATPDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12833,7 +12833,7 @@ public GetExpressCheckoutDetailsResponseDetailsType GetExpressCheckoutDetailsRes } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12855,7 +12855,7 @@ public ExecuteCheckoutOperationsResponseDetailsType ExecuteCheckoutOperationsRes } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12877,7 +12877,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12899,7 +12899,7 @@ public GetIncentiveEvaluationResponseDetailsType GetIncentiveEvaluationResponseD } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12921,7 +12921,7 @@ public GetAccessPermissionDetailsResponseDetailsType GetAccessPermissionDetailsR } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12943,7 +12943,7 @@ public string Status { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12965,7 +12965,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -12987,7 +12987,7 @@ public GetAuthDetailsResponseDetailsType GetAuthDetailsResponseDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13009,7 +13009,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13031,7 +13031,7 @@ public GetBoardingDetailsResponseDetailsType GetBoardingDetailsResponseDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13053,7 +13053,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13153,7 +13153,7 @@ public string PayPalToken { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum MatchStatusCodeType { @@ -13169,7 +13169,7 @@ public enum MatchStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13191,7 +13191,7 @@ public BAUpdateResponseDetailsType BAUpdateResponseDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13200,7 +13200,7 @@ public partial class MassPayResponseType : AbstractResponseType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13222,7 +13222,7 @@ public PaymentTransactionSearchResultType[] PaymentTransactions { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13257,7 +13257,7 @@ public FMFDetailsType FMFDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13292,7 +13292,7 @@ public ThreeDSecureInfoType ThreeDSecureDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13301,7 +13301,7 @@ public partial class CancelRecoupResponseType : AbstractResponseType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13323,7 +13323,7 @@ public EnhancedCompleteRecoupResponseDetailsType EnhancedCompleteRecoupResponseD } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13332,7 +13332,7 @@ public partial class InitiateRecoupResponseType : AbstractResponseType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13445,7 +13445,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13467,7 +13467,7 @@ public ButtonSearchResultType[] ButtonSearchResult { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13593,7 +13593,7 @@ public string[] DigitalDownloadKeys { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13662,7 +13662,7 @@ public string ItemCost { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13743,7 +13743,7 @@ public string OptionCost { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -13752,7 +13752,7 @@ public partial class BMSetInventoryResponseType : AbstractResponseType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14060,7 +14060,7 @@ public string ButtonLanguage { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ButtonTypeType { @@ -14097,7 +14097,7 @@ public enum ButtonTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ButtonCodeType { @@ -14116,7 +14116,7 @@ public enum ButtonCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ButtonSubTypeType { @@ -14129,7 +14129,7 @@ public enum ButtonSubTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14163,7 +14163,7 @@ public OptionSelectionDetailsType[] OptionSelectionDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14234,7 +14234,7 @@ public InstallmentDetailsType[] PaymentPeriod { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum OptionTypeListType { @@ -14253,7 +14253,7 @@ public enum OptionTypeListType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14373,7 +14373,7 @@ public string TaxAmount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ButtonImageType { @@ -14389,7 +14389,7 @@ public enum ButtonImageType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BuyNowTextType { @@ -14402,7 +14402,7 @@ public enum BuyNowTextType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum SubscribeTextType { @@ -14415,7 +14415,7 @@ public enum SubscribeTextType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14424,7 +14424,7 @@ public partial class BMManageButtonStatusResponseType : AbstractResponseType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14485,7 +14485,7 @@ public string HostedButtonID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14546,7 +14546,7 @@ public string HostedButtonID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14567,7 +14567,7 @@ public string TransactionID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14687,7 +14687,7 @@ public BasicAmountType TaxAmount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14952,7 +14952,7 @@ public BillingPeriodDetailsType_Update PaymentPeriod { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -14997,7 +14997,7 @@ public string Note { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15042,7 +15042,7 @@ public string Note { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum StatusChangeActionType { @@ -15058,7 +15058,7 @@ public enum StatusChangeActionType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15104,7 +15104,7 @@ public bool FailedInitialAmountActionSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum FailedPaymentActionType { @@ -15117,7 +15117,7 @@ public enum FailedPaymentActionType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15224,7 +15224,7 @@ public bool AutoBillOutstandingAmountSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15294,7 +15294,7 @@ public PaymentDetailsItemType[] PaymentDetailsItem { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15387,7 +15387,7 @@ public string Comment { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15408,7 +15408,7 @@ public string DeviceID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15429,7 +15429,7 @@ public DeviceDetailsType DeviceDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15475,7 +15475,7 @@ public string CreditCardNumber { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15644,7 +15644,7 @@ public string IssueNumber { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15798,7 +15798,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum MerchantPullPaymentCodeType { @@ -15814,7 +15814,7 @@ public enum MerchantPullPaymentCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -15959,7 +15959,7 @@ public string ReqBillingAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16029,7 +16029,7 @@ public string BillingAgreementCustom { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BillingCodeType { @@ -16051,7 +16051,7 @@ public enum BillingCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16254,7 +16254,7 @@ public string BuyerEmail { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16299,7 +16299,7 @@ public string Extension { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16320,7 +16320,7 @@ public PhoneNumberType Phone { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16511,7 +16511,7 @@ public string ShareHomeAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum MobilePaymentCodeType { @@ -16530,7 +16530,7 @@ public enum MobilePaymentCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum MobileRecipientCodeType { @@ -16543,7 +16543,7 @@ public enum MobileRecipientCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16576,7 +16576,7 @@ public string Value { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16670,7 +16670,7 @@ public bool ReturnFMFDetailsSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -16883,7 +16883,7 @@ public string EndorsementOrRestrictions { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17061,7 +17061,7 @@ public FlightDetailsType[] FlightDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17082,7 +17082,7 @@ public AirlineItineraryType AirlineItinerary { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17360,7 +17360,7 @@ public CoupledBucketsType[] CoupledBuckets { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17419,7 +17419,7 @@ public string[] PaymentRequestID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum CoupleType { @@ -17429,7 +17429,7 @@ public enum CoupleType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17450,7 +17450,7 @@ public bool IsRequested { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17471,7 +17471,7 @@ public string ReqBillingAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17492,7 +17492,7 @@ public IdentificationInfoType IdentificationInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17537,7 +17537,7 @@ public IdentityTokenInfoType IdentityTokenInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17558,7 +17558,7 @@ public string SessionToken { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17579,7 +17579,7 @@ public string ExternalRememberMeID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17600,7 +17600,7 @@ public string AccessToken { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17634,7 +17634,7 @@ public bool PaymentTypeSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17667,7 +17667,7 @@ public BasicAmountType MaxAmount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17749,7 +17749,7 @@ public string Custom { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ApprovalTypeType { @@ -17762,7 +17762,7 @@ public enum ApprovalTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ApprovalSubTypeType { @@ -17781,7 +17781,7 @@ public enum ApprovalSubTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17927,7 +17927,7 @@ public ExternalPartnerTrackingDetailsType ExternalPartnerTrackingDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum UserChannelCodeType { @@ -17958,7 +17958,7 @@ public enum UserChannelCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -17979,7 +17979,7 @@ public string ExternalPartnerSegmentID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18024,7 +18024,7 @@ public AuthorizationRequestType AuthorizationRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18045,7 +18045,7 @@ public string InContextPaymentButtonImage { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18078,7 +18078,7 @@ public string InContextReturnURL { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18111,7 +18111,7 @@ public string ExternalRememberMeOwnerID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18144,7 +18144,7 @@ public ExternalRememberMeOwnerDetailsType ExternalRememberMeOwnerDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18178,7 +18178,7 @@ public IncentiveApplyIndicationType[] ApplyIndication { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18211,7 +18211,7 @@ public string ItemId { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18256,7 +18256,7 @@ public string ShippingOptionName { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18338,7 +18338,7 @@ public IdentificationInfoType IdentificationInfo { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18456,7 +18456,7 @@ public bool OtherPaymentMethodHideLabelSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18465,7 +18465,7 @@ public partial class EnhancedCheckoutDataType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -18511,7 +18511,7 @@ public bool UserSelectedFundingSourceSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum UserSelectedFundingSourceType { @@ -18530,7 +18530,7 @@ public enum UserSelectedFundingSourceType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -19461,7 +19461,7 @@ public CoupledBucketsType[] CoupledBuckets { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum SolutionTypeType { @@ -19474,7 +19474,7 @@ public enum SolutionTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum LandingPageType { @@ -19490,7 +19490,7 @@ public enum LandingPageType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ChannelType { @@ -19503,7 +19503,7 @@ public enum ChannelType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum TotalType { @@ -19516,7 +19516,7 @@ public enum TotalType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -19587,7 +19587,7 @@ public bool RequestDetailLevelSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum IncentiveRequestCodeType { @@ -19600,7 +19600,7 @@ public enum IncentiveRequestCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum IncentiveRequestDetailLevelCodeType { @@ -19613,7 +19613,7 @@ public enum IncentiveRequestDetailLevelCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -19696,7 +19696,7 @@ public string ItemQuantity { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -19814,7 +19814,7 @@ public BasicAmountType BucketTotalAmt { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -19898,7 +19898,7 @@ public IncentiveRequestDetailsType RequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -20105,7 +20105,7 @@ public AddressType Address { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -20334,7 +20334,7 @@ public AddressType Address { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -20391,7 +20391,7 @@ public string AccountNumber { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BankAccountTypeType { @@ -20404,7 +20404,7 @@ public enum BankAccountTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -20461,7 +20461,7 @@ public string SSN { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -20742,7 +20742,7 @@ public string CustomerServicePhone { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BusinessTypeType { @@ -20770,7 +20770,7 @@ public enum BusinessTypeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BusinessCategoryType { @@ -20890,7 +20890,7 @@ public enum BusinessCategoryType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum BusinessSubCategoryType { @@ -21965,7 +21965,7 @@ public enum BusinessSubCategoryType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AverageTransactionPriceType { @@ -22016,7 +22016,7 @@ public enum AverageTransactionPriceType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum AverageMonthlyVolumeType { @@ -22051,7 +22051,7 @@ public enum AverageMonthlyVolumeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum SalesVenueType { @@ -22074,7 +22074,7 @@ public enum SalesVenueType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PercentageRevenueFromOnlineSalesType { @@ -22101,7 +22101,7 @@ public enum PercentageRevenueFromOnlineSalesType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22219,7 +22219,7 @@ public BankAccountDetailsType BankAccount { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum MarketingCategoryType { @@ -22310,7 +22310,7 @@ public enum MarketingCategoryType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22391,7 +22391,7 @@ public string Note { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22581,7 +22581,7 @@ public string SoftDescriptor { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22590,7 +22590,7 @@ public partial class EnhancedCancelRecoupRequestDetailsType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22599,7 +22599,7 @@ public partial class EnhancedCompleteRecoupRequestDetailsType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22608,7 +22608,7 @@ public partial class EnhancedInitiateRecoupRequestDetailsType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22698,7 +22698,7 @@ public string TerminalID { [System.Xml.Serialization.XmlIncludeAttribute(typeof(BMManageButtonStatusRequestType))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(BMUpdateButtonRequestType))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(BMCreateButtonRequestType))] - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22745,7 +22745,7 @@ public string Version { } /// - [System.Xml.Serialization.XmlAnyElementAttribute()] + [System.Xml.Serialization.XmlIgnoreAttribute()] public System.Xml.XmlElement Any { get { return this.anyField; @@ -22757,7 +22757,7 @@ public System.Xml.XmlElement Any { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum DetailLevelCodeType { @@ -22773,7 +22773,7 @@ public enum DetailLevelCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22806,7 +22806,7 @@ public ExternalRememberMeOwnerDetailsType ExternalRememberMeOwnerDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22828,7 +22828,7 @@ public ReverseTransactionRequestDetailsType ReverseTransactionRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22837,7 +22837,7 @@ public partial class GetPalDetailsRequestType : AbstractRequestType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22859,7 +22859,7 @@ public UpdateRecurringPaymentsProfileRequestDetailsType UpdateRecurringPaymentsP } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22881,7 +22881,7 @@ public BillOutstandingAmountRequestDetailsType BillOutstandingAmountRequestDetai } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22903,7 +22903,7 @@ public ManageRecurringPaymentsProfileStatusRequestDetailsType ManageRecurringPay } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22924,7 +22924,7 @@ public string ProfileID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22946,7 +22946,7 @@ public CreateRecurringPaymentsProfileRequestDetailsType CreateRecurringPaymentsP } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -22968,7 +22968,7 @@ public DoNonReferencedCreditRequestDetailsType DoNonReferencedCreditRequestDetai } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23015,7 +23015,7 @@ public bool ReturnFMFDetailsSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23036,7 +23036,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23057,7 +23057,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23079,7 +23079,7 @@ public SetCustomerBillingAgreementRequestDetailsType SetCustomerBillingAgreement } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23100,7 +23100,7 @@ public string ReturnAllCurrencies { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23121,7 +23121,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23143,7 +23143,7 @@ public SetMobileCheckoutRequestDetailsType SetMobileCheckoutRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23165,7 +23165,7 @@ public GetMobileStatusRequestDetailsType GetMobileStatusRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23187,7 +23187,7 @@ public CreateMobilePaymentRequestDetailsType CreateMobilePaymentRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23270,7 +23270,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum TransactionEntityType { @@ -23292,7 +23292,7 @@ public enum TransactionEntityType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23338,7 +23338,7 @@ public string IPAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23542,7 +23542,7 @@ public string IPAddress { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23587,7 +23587,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23632,7 +23632,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23765,7 +23765,7 @@ public TupleType[] MerchantData { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum CompleteCodeType { @@ -23778,7 +23778,7 @@ public enum CompleteCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23823,7 +23823,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum APIType { @@ -23836,7 +23836,7 @@ public enum APIType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23883,7 +23883,7 @@ public bool ReturnFMFDetailsSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23916,7 +23916,7 @@ public FMFPendingTransactionActionType Action { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum FMFPendingTransactionActionType { @@ -23930,7 +23930,7 @@ public enum FMFPendingTransactionActionType { /// [System.Xml.Serialization.XmlIncludeAttribute(typeof(DoUATPExpressCheckoutPaymentRequestType))] - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23977,7 +23977,7 @@ public bool ReturnFMFDetailsSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -23986,7 +23986,7 @@ public partial class DoUATPExpressCheckoutPaymentRequestType : DoExpressCheckout } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24007,7 +24007,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24029,7 +24029,7 @@ public ExecuteCheckoutOperationsRequestDetailsType ExecuteCheckoutOperationsRequ } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24051,7 +24051,7 @@ public SetExpressCheckoutRequestDetailsType SetExpressCheckoutRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24073,7 +24073,7 @@ public GetIncentiveEvaluationRequestDetailsType GetIncentiveEvaluationRequestDet } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24094,7 +24094,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24115,7 +24115,7 @@ public string PayerID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24137,7 +24137,7 @@ public SetAccessPermissionsRequestDetailsType SetAccessPermissionsRequestDetails } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24158,7 +24158,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24180,7 +24180,7 @@ public SetAuthFlowParamRequestDetailsType SetAuthFlowParamRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24201,7 +24201,7 @@ public string Token { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24223,7 +24223,7 @@ public EnterBoardingRequestDetailsType EnterBoardingRequestDetails { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24268,7 +24268,7 @@ public string Zip { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24338,7 +24338,7 @@ public string BillingAgreementCustom { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24409,7 +24409,7 @@ public MassPayRequestItemType[] MassPayItem { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ReceiverInfoCodeType { @@ -24425,7 +24425,7 @@ public enum ReceiverInfoCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24666,7 +24666,7 @@ public bool StatusSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentTransactionClassCodeType { @@ -24739,7 +24739,7 @@ public enum PaymentTransactionClassCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum PaymentTransactionStatusCodeType { @@ -24761,7 +24761,7 @@ public enum PaymentTransactionStatusCodeType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24808,7 +24808,7 @@ public bool ReturnFMFDetailsSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24829,7 +24829,7 @@ public string TransactionID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24851,7 +24851,7 @@ public EnhancedCancelRecoupRequestDetailsType EnhancedCancelRecoupRequestDetails } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24873,7 +24873,7 @@ public EnhancedCompleteRecoupRequestDetailsType EnhancedCompleteRecoupRequestDet } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -24895,7 +24895,7 @@ public EnhancedInitiateRecoupRequestDetailsType EnhancedInitiateRecoupRequestDet } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25102,7 +25102,7 @@ public string MsgSubID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum RefundType { @@ -25121,7 +25121,7 @@ public enum RefundType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25180,7 +25180,7 @@ public bool EndDateSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25201,7 +25201,7 @@ public string HostedButtonID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25333,7 +25333,7 @@ public string[] DigitalDownloadKeys { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25354,7 +25354,7 @@ public string HostedButtonID { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25400,7 +25400,7 @@ public bool ButtonStatusSpecified { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:ebay:apis:eBLBaseComponents")] public enum ButtonStatusType { @@ -25410,7 +25410,7 @@ public enum ButtonStatusType { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25669,7 +25669,7 @@ public string ButtonLanguage { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25916,7 +25916,7 @@ public string ButtonLanguage { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25937,7 +25937,7 @@ public RefundTransactionRequestType RefundTransactionRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25958,7 +25958,7 @@ public InitiateRecoupRequestType InitiateRecoupRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -25979,7 +25979,7 @@ public CompleteRecoupRequestType CompleteRecoupRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26000,7 +26000,7 @@ public CancelRecoupRequestType CancelRecoupRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26021,7 +26021,7 @@ public GetTransactionDetailsRequestType GetTransactionDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26042,7 +26042,7 @@ public BMCreateButtonRequestType BMCreateButtonRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26063,7 +26063,7 @@ public BMUpdateButtonRequestType BMUpdateButtonRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26084,7 +26084,7 @@ public BMManageButtonStatusRequestType BMManageButtonStatusRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26105,7 +26105,7 @@ public BMGetButtonDetailsRequestType BMGetButtonDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26126,7 +26126,7 @@ public BMSetInventoryRequestType BMSetInventoryRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26147,7 +26147,7 @@ public BMGetInventoryRequestType BMGetInventoryRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26168,7 +26168,7 @@ public BMButtonSearchRequestType BMButtonSearchRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26189,7 +26189,7 @@ public BillUserRequestType BillUserRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26210,7 +26210,7 @@ public TransactionSearchRequestType TransactionSearchRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26231,7 +26231,7 @@ public MassPayRequestType MassPayRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26252,7 +26252,7 @@ public BAUpdateRequestType BAUpdateRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26273,7 +26273,7 @@ public AddressVerifyRequestType AddressVerifyRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26294,7 +26294,7 @@ public EnterBoardingRequestType EnterBoardingRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26315,7 +26315,7 @@ public GetBoardingDetailsRequestType GetBoardingDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26336,7 +26336,7 @@ public CreateMobilePaymentRequestType CreateMobilePaymentRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26357,7 +26357,7 @@ public GetMobileStatusRequestType GetMobileStatusRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26378,7 +26378,7 @@ public SetMobileCheckoutRequestType SetMobileCheckoutRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26399,7 +26399,7 @@ public DoMobileCheckoutPaymentRequestType DoMobileCheckoutPaymentRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26420,7 +26420,7 @@ public GetBalanceRequestType GetBalanceRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26441,7 +26441,7 @@ public GetPalDetailsRequestType GetPalDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26462,7 +26462,7 @@ public DoExpressCheckoutPaymentRequestType DoExpressCheckoutPaymentRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26483,7 +26483,7 @@ public DoUATPExpressCheckoutPaymentRequestType DoUATPExpressCheckoutPaymentReque } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26504,7 +26504,7 @@ public SetAuthFlowParamRequestType SetAuthFlowParamRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26525,7 +26525,7 @@ public GetAuthDetailsRequestType GetAuthDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26546,7 +26546,7 @@ public SetAccessPermissionsRequestType SetAccessPermissionsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26567,7 +26567,7 @@ public UpdateAccessPermissionsRequestType UpdateAccessPermissionsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26588,7 +26588,7 @@ public GetAccessPermissionDetailsRequestType GetAccessPermissionDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26609,7 +26609,7 @@ public GetIncentiveEvaluationRequestType GetIncentiveEvaluationRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26630,7 +26630,7 @@ public SetExpressCheckoutRequestType SetExpressCheckoutRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26651,7 +26651,7 @@ public ExecuteCheckoutOperationsRequestType ExecuteCheckoutOperationsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26672,7 +26672,7 @@ public GetExpressCheckoutDetailsRequestType GetExpressCheckoutDetailsRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26693,7 +26693,7 @@ public DoDirectPaymentRequestType DoDirectPaymentRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26714,7 +26714,7 @@ public ManagePendingTransactionStatusRequestType ManagePendingTransactionStatusR } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26735,7 +26735,7 @@ public DoCancelRequestType DoCancelRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26756,7 +26756,7 @@ public DoCaptureRequestType DoCaptureRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26777,7 +26777,7 @@ public DoReauthorizationRequestType DoReauthorizationRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26798,7 +26798,7 @@ public DoVoidRequestType DoVoidRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26819,7 +26819,7 @@ public DoAuthorizationRequestType DoAuthorizationRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26840,7 +26840,7 @@ public UpdateAuthorizationRequestType UpdateAuthorizationRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26861,7 +26861,7 @@ public SetCustomerBillingAgreementRequestType SetCustomerBillingAgreementRequest } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26882,7 +26882,7 @@ public GetBillingAgreementCustomerDetailsRequestType GetBillingAgreementCustomer } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26903,7 +26903,7 @@ public CreateBillingAgreementRequestType CreateBillingAgreementRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26924,7 +26924,7 @@ public DoReferenceTransactionRequestType DoReferenceTransactionRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26945,7 +26945,7 @@ public DoNonReferencedCreditRequestType DoNonReferencedCreditRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26966,7 +26966,7 @@ public DoUATPAuthorizationRequestType DoUATPAuthorizationRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -26987,7 +26987,7 @@ public CreateRecurringPaymentsProfileRequestType CreateRecurringPaymentsProfileR } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -27008,7 +27008,7 @@ public GetRecurringPaymentsProfileDetailsRequestType GetRecurringPaymentsProfile } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -27029,7 +27029,7 @@ public ManageRecurringPaymentsProfileStatusRequestType ManageRecurringPaymentsPr } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -27050,7 +27050,7 @@ public BillOutstandingAmountRequestType BillOutstandingAmountRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -27071,7 +27071,7 @@ public UpdateRecurringPaymentsProfileRequestType UpdateRecurringPaymentsProfileR } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -27092,7 +27092,7 @@ public ReverseTransactionRequestType ReverseTransactionRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.34234")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.6.1064.2")] [System.SerializableAttribute()] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] @@ -27113,11 +27113,11 @@ public ExternalRememberMeOptOutRequestType ExternalRememberMeOptOutRequest { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void RefundTransactionCompletedEventHandler(object sender, RefundTransactionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class RefundTransactionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27139,11 +27139,11 @@ public RefundTransactionResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void InitiateRecoupCompletedEventHandler(object sender, InitiateRecoupCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class InitiateRecoupCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27165,11 +27165,11 @@ public InitiateRecoupResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void CompleteRecoupCompletedEventHandler(object sender, CompleteRecoupCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CompleteRecoupCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27191,11 +27191,11 @@ public CompleteRecoupResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void CancelRecoupCompletedEventHandler(object sender, CancelRecoupCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CancelRecoupCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27217,11 +27217,11 @@ public CancelRecoupResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetTransactionDetailsCompletedEventHandler(object sender, GetTransactionDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetTransactionDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27243,11 +27243,11 @@ public GetTransactionDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMCreateButtonCompletedEventHandler(object sender, BMCreateButtonCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMCreateButtonCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27269,11 +27269,11 @@ public BMCreateButtonResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMUpdateButtonCompletedEventHandler(object sender, BMUpdateButtonCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMUpdateButtonCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27295,11 +27295,11 @@ public BMUpdateButtonResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMManageButtonStatusCompletedEventHandler(object sender, BMManageButtonStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMManageButtonStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27321,11 +27321,11 @@ public BMManageButtonStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMGetButtonDetailsCompletedEventHandler(object sender, BMGetButtonDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMGetButtonDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27347,11 +27347,11 @@ public BMGetButtonDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMSetInventoryCompletedEventHandler(object sender, BMSetInventoryCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMSetInventoryCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27373,11 +27373,11 @@ public BMSetInventoryResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMGetInventoryCompletedEventHandler(object sender, BMGetInventoryCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMGetInventoryCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27399,11 +27399,11 @@ public BMGetInventoryResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BMButtonSearchCompletedEventHandler(object sender, BMButtonSearchCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BMButtonSearchCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27425,11 +27425,11 @@ public BMButtonSearchResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BillUserCompletedEventHandler(object sender, BillUserCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BillUserCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27451,11 +27451,11 @@ public BillUserResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void TransactionSearchCompletedEventHandler(object sender, TransactionSearchCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class TransactionSearchCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27477,11 +27477,11 @@ public TransactionSearchResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void MassPayCompletedEventHandler(object sender, MassPayCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class MassPayCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27503,11 +27503,11 @@ public MassPayResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BillAgreementUpdateCompletedEventHandler(object sender, BillAgreementUpdateCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BillAgreementUpdateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27529,11 +27529,11 @@ public BAUpdateResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void AddressVerifyCompletedEventHandler(object sender, AddressVerifyCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class AddressVerifyCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27555,11 +27555,11 @@ public AddressVerifyResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void EnterBoardingCompletedEventHandler(object sender, EnterBoardingCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class EnterBoardingCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27581,11 +27581,11 @@ public EnterBoardingResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetBoardingDetailsCompletedEventHandler(object sender, GetBoardingDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetBoardingDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27607,11 +27607,11 @@ public GetBoardingDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void CreateMobilePaymentCompletedEventHandler(object sender, CreateMobilePaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CreateMobilePaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27633,11 +27633,11 @@ public CreateMobilePaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetMobileStatusCompletedEventHandler(object sender, GetMobileStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetMobileStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27659,11 +27659,11 @@ public GetMobileStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void SetMobileCheckoutCompletedEventHandler(object sender, SetMobileCheckoutCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetMobileCheckoutCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27685,11 +27685,11 @@ public SetMobileCheckoutResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoMobileCheckoutPaymentCompletedEventHandler(object sender, DoMobileCheckoutPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoMobileCheckoutPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27711,11 +27711,11 @@ public DoMobileCheckoutPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetBalanceCompletedEventHandler(object sender, GetBalanceCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetBalanceCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27737,11 +27737,11 @@ public GetBalanceResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetPalDetailsCompletedEventHandler(object sender, GetPalDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetPalDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27763,11 +27763,11 @@ public GetPalDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoExpressCheckoutPaymentCompletedEventHandler(object sender, DoExpressCheckoutPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoExpressCheckoutPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27789,11 +27789,11 @@ public DoExpressCheckoutPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoUATPExpressCheckoutPaymentCompletedEventHandler(object sender, DoUATPExpressCheckoutPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoUATPExpressCheckoutPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27815,11 +27815,11 @@ public DoUATPExpressCheckoutPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void SetAuthFlowParamCompletedEventHandler(object sender, SetAuthFlowParamCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetAuthFlowParamCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27841,11 +27841,11 @@ public SetAuthFlowParamResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetAuthDetailsCompletedEventHandler(object sender, GetAuthDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetAuthDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27867,11 +27867,11 @@ public GetAuthDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void SetAccessPermissionsCompletedEventHandler(object sender, SetAccessPermissionsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetAccessPermissionsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27893,11 +27893,11 @@ public SetAccessPermissionsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void UpdateAccessPermissionsCompletedEventHandler(object sender, UpdateAccessPermissionsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class UpdateAccessPermissionsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27919,11 +27919,11 @@ public UpdateAccessPermissionsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetAccessPermissionDetailsCompletedEventHandler(object sender, GetAccessPermissionDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetAccessPermissionDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27945,11 +27945,11 @@ public GetAccessPermissionDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetIncentiveEvaluationCompletedEventHandler(object sender, GetIncentiveEvaluationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetIncentiveEvaluationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27971,11 +27971,11 @@ public GetIncentiveEvaluationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void SetExpressCheckoutCompletedEventHandler(object sender, SetExpressCheckoutCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetExpressCheckoutCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -27997,11 +27997,11 @@ public SetExpressCheckoutResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void ExecuteCheckoutOperationsCompletedEventHandler(object sender, ExecuteCheckoutOperationsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ExecuteCheckoutOperationsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28023,11 +28023,11 @@ public ExecuteCheckoutOperationsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetExpressCheckoutDetailsCompletedEventHandler(object sender, GetExpressCheckoutDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetExpressCheckoutDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28049,11 +28049,11 @@ public GetExpressCheckoutDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoDirectPaymentCompletedEventHandler(object sender, DoDirectPaymentCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoDirectPaymentCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28075,11 +28075,11 @@ public DoDirectPaymentResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void ManagePendingTransactionStatusCompletedEventHandler(object sender, ManagePendingTransactionStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ManagePendingTransactionStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28101,11 +28101,11 @@ public ManagePendingTransactionStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoCancelCompletedEventHandler(object sender, DoCancelCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoCancelCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28127,11 +28127,11 @@ public DoCancelResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoCaptureCompletedEventHandler(object sender, DoCaptureCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoCaptureCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28153,11 +28153,11 @@ public DoCaptureResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoReauthorizationCompletedEventHandler(object sender, DoReauthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoReauthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28179,11 +28179,11 @@ public DoReauthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoVoidCompletedEventHandler(object sender, DoVoidCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoVoidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28205,11 +28205,11 @@ public DoVoidResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoAuthorizationCompletedEventHandler(object sender, DoAuthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoAuthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28231,11 +28231,11 @@ public DoAuthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void UpdateAuthorizationCompletedEventHandler(object sender, UpdateAuthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class UpdateAuthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28257,11 +28257,11 @@ public UpdateAuthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void SetCustomerBillingAgreementCompletedEventHandler(object sender, SetCustomerBillingAgreementCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class SetCustomerBillingAgreementCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28283,11 +28283,11 @@ public SetCustomerBillingAgreementResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetBillingAgreementCustomerDetailsCompletedEventHandler(object sender, GetBillingAgreementCustomerDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetBillingAgreementCustomerDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28309,11 +28309,11 @@ public GetBillingAgreementCustomerDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void CreateBillingAgreementCompletedEventHandler(object sender, CreateBillingAgreementCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CreateBillingAgreementCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28335,11 +28335,11 @@ public CreateBillingAgreementResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoReferenceTransactionCompletedEventHandler(object sender, DoReferenceTransactionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoReferenceTransactionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28361,11 +28361,11 @@ public DoReferenceTransactionResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoNonReferencedCreditCompletedEventHandler(object sender, DoNonReferencedCreditCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoNonReferencedCreditCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28387,11 +28387,11 @@ public DoNonReferencedCreditResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void DoUATPAuthorizationCompletedEventHandler(object sender, DoUATPAuthorizationCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class DoUATPAuthorizationCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28413,11 +28413,11 @@ public DoUATPAuthorizationResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void CreateRecurringPaymentsProfileCompletedEventHandler(object sender, CreateRecurringPaymentsProfileCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class CreateRecurringPaymentsProfileCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28439,11 +28439,11 @@ public CreateRecurringPaymentsProfileResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void GetRecurringPaymentsProfileDetailsCompletedEventHandler(object sender, GetRecurringPaymentsProfileDetailsCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class GetRecurringPaymentsProfileDetailsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28465,11 +28465,11 @@ public GetRecurringPaymentsProfileDetailsResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void ManageRecurringPaymentsProfileStatusCompletedEventHandler(object sender, ManageRecurringPaymentsProfileStatusCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ManageRecurringPaymentsProfileStatusCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28491,11 +28491,11 @@ public ManageRecurringPaymentsProfileStatusResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void BillOutstandingAmountCompletedEventHandler(object sender, BillOutstandingAmountCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class BillOutstandingAmountCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28517,11 +28517,11 @@ public BillOutstandingAmountResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void UpdateRecurringPaymentsProfileCompletedEventHandler(object sender, UpdateRecurringPaymentsProfileCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class UpdateRecurringPaymentsProfileCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28543,11 +28543,11 @@ public UpdateRecurringPaymentsProfileResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void ReverseTransactionCompletedEventHandler(object sender, ReverseTransactionCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ReverseTransactionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { @@ -28569,11 +28569,11 @@ public ReverseTransactionResponseType Result { } /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] public delegate void ExternalRememberMeOptOutCompletedEventHandler(object sender, ExternalRememberMeOptOutCompletedEventArgs e); /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.34209")] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.6.1055.0")] [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] public partial class ExternalRememberMeOptOutCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/RefundTransactionResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/RefundTransactionResponseType1.datasource index 1bf4b11b85..dde94aa119 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/RefundTransactionResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/RefundTransactionResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.RefundTransactionResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.RefundTransactionResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ReverseTransactionResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ReverseTransactionResponseType1.datasource index c22afccb5c..c6d5dd1b91 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ReverseTransactionResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/ReverseTransactionResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.ReverseTransactionResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.ReverseTransactionResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAccessPermissionsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAccessPermissionsResponseType1.datasource index 43d8dd371f..949ea4fe6d 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAccessPermissionsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAccessPermissionsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.SetAccessPermissionsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.SetAccessPermissionsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAuthFlowParamResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAuthFlowParamResponseType1.datasource index 57dcc8bf6b..c452380ce4 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAuthFlowParamResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetAuthFlowParamResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.SetAuthFlowParamResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.SetAuthFlowParamResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetCustomerBillingAgreementResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetCustomerBillingAgreementResponseType1.datasource index 150476a66c..4d93981cbd 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetCustomerBillingAgreementResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetCustomerBillingAgreementResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.SetCustomerBillingAgreementResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.SetCustomerBillingAgreementResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetExpressCheckoutResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetExpressCheckoutResponseType1.datasource index 9657280df1..c9a22cc649 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetExpressCheckoutResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetExpressCheckoutResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.SetExpressCheckoutResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.SetExpressCheckoutResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetMobileCheckoutResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetMobileCheckoutResponseType1.datasource index 3c24c11ede..6564ca667e 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetMobileCheckoutResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/SetMobileCheckoutResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.SetMobileCheckoutResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.SetMobileCheckoutResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/TransactionSearchResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/TransactionSearchResponseType1.datasource index 7827822e05..80b2e463c9 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/TransactionSearchResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/TransactionSearchResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.TransactionSearchResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.TransactionSearchResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAccessPermissionsResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAccessPermissionsResponseType1.datasource index 66e306887c..a8dcb4c1af 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAccessPermissionsResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAccessPermissionsResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.UpdateAccessPermissionsResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.UpdateAccessPermissionsResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAuthorizationResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAuthorizationResponseType1.datasource index 963c2cfa18..1f4d8843fa 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAuthorizationResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateAuthorizationResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.UpdateAuthorizationResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.UpdateAuthorizationResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateRecurringPaymentsProfileResponseType1.datasource b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateRecurringPaymentsProfileResponseType1.datasource index 4b83a778d4..8ae5023341 100644 --- a/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateRecurringPaymentsProfileResponseType1.datasource +++ b/src/Plugins/SmartStore.PayPal/Web References/PayPalSvc/UpdateRecurringPaymentsProfileResponseType1.datasource @@ -6,5 +6,5 @@ cause the file to be unrecognizable by the program. --> - SmartStore.PayPal.PayPalSvc.UpdateRecurringPaymentsProfileResponseType, Web References.PayPalSvc.Reference.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + SmartStore.PayPal.PayPalSvc.UpdateRecurringPaymentsProfileResponseType, Web References.PayPalSvc.Reference.cs.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index 70ed0765b7..ba84600675 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,5 +1,59 @@ #Release Notes +##Paypal 3.2.2.0 +###New Features +* New instalments provider. + +##Paypal 3.1.5.3 +###Improvements +* PayPal PLUS: now up to 10 more third party payment methods are allowed by PayPal. + +##Paypal 3.1.5.2 +###Improvements +* PayPal PLUS: additionally store access data in the database. + +##Paypal 3.1.5.1 +###Bugfixes +* PayPal Express: Checkout attributes were always ignored. + +##Paypal 3.0.0.3 +###Bugfixes +* PayPal PLUS: Fixed #1200 Invalid request if the order amount is zero. "Amount cannot be zero" still occurred. + +##Paypal 3.0.0.2 +###New Features +* PayPal Standard: New settings "UsePayPalAddress" and "IsShippingAddressRequired" to avoid payment rejection due to address validation. +###Bugfixes +* PayPal PLUS: Fixed HTTP 401 "Unauthorized" when calling PatchShipping. + +##Paypal 2.6.0.7 +###Bugfixes +* PayPal Express: Fixed net price issue. + +##Paypal 2.6.0.6 +###Bugfixes +* PayPal PLUS: Skip payment if cart total is zero. +* PayPal PLUS: Do not display payment wall if method is filtered +###Improvements +* PayPal PLUS: Log more information in case of a request failure. + +##Paypal 2.6.0.5 +###Bugfixes +* PayPal PLUS: Fixed "Cannot perform runtime binding on a null reference" when rendering the payment wall. + +##Paypal 2.6.0.4 +###Bugfixes +* PayPal PLUS: Excluding tax issue. Fixed "Transaction amount details (subtotal, tax, shipping) must add up to specified amount total". + +##Paypal 2.6.0.3 +###Bugfixes +* PayPal PLUS: Integration review through PayPal +* PayPal PLUS: Generic attribute caching problem. Fixed "Item amount must add up to specified amount subtotal (or total if amount details not specified)". + +##PayPal 2.6.0.1 +###Improvements +* Added PayPal partner attribution Id as request header + ##Paypal 2.5.0.2 ###New Features * PayPal PLUS payment provider diff --git a/src/Plugins/SmartStore.PayPal/packages.config b/src/Plugins/SmartStore.PayPal/packages.config index eb91eb3011..5fc754f3be 100644 --- a/src/Plugins/SmartStore.PayPal/packages.config +++ b/src/Plugins/SmartStore.PayPal/packages.config @@ -1,11 +1,12 @@  - - - - - - + + + + + + + - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index f6daa0fb4b..9a4ae1a370 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -17,8 +17,16 @@ + - + @@ -50,7 +58,7 @@ - + @@ -66,7 +74,7 @@ - + @@ -78,35 +86,35 @@ - + - + - + - + - + - + - + - + @@ -120,6 +128,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -130,15 +158,19 @@ - + - - + + - - + + + + + + diff --git a/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs b/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs index 5c5e03f8ab..05761ba86c 100644 --- a/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs +++ b/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs @@ -30,12 +30,12 @@ public ByTotalController( AdminAreaSettings adminAreaSettings, ICommonServices services) { - this._shippingService = shippingService; - this._shippingByTotalService = shippingByTotalService; - this._shippingByTotalSettings = shippingByTotalSettings; - this._countryService = countryService; - this._adminAreaSettings = adminAreaSettings; - this._services = services; + _shippingService = shippingService; + _shippingByTotalService = shippingByTotalService; + _shippingByTotalSettings = shippingByTotalSettings; + _countryService = countryService; + _adminAreaSettings = adminAreaSettings; + _services = services; } public ActionResult Configure() @@ -71,6 +71,7 @@ public ActionResult Configure() model.LimitMethodsToCreated = _shippingByTotalSettings.LimitMethodsToCreated; model.SmallQuantityThreshold = _shippingByTotalSettings.SmallQuantityThreshold; model.SmallQuantitySurcharge = _shippingByTotalSettings.SmallQuantitySurcharge; + model.CalculateTotalIncludingTax = _shippingByTotalSettings.CalculateTotalIncludingTax; model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.GridPageSize = _adminAreaSettings.GridPageSize; @@ -148,22 +149,27 @@ public ActionResult AddShippingRate(ByTotalListModel model) BaseCharge = model.AddBaseCharge, MaxCharge = model.AddMaxCharge }; + _shippingByTotalService.InsertShippingByTotalRecord(shippingByTotalRecord); - return Json(new { Result = true }); + NotifySuccess(T("Plugins.Shipping.ByTotal.AddNewRecord.Success")); + + return Json(new { Result = true }); } [HttpPost] - public ActionResult SaveGeneralSettings(ByTotalListModel model) + public ActionResult Configure(ByTotalListModel model) { - //save settings _shippingByTotalSettings.LimitMethodsToCreated = model.LimitMethodsToCreated; _shippingByTotalSettings.SmallQuantityThreshold = model.SmallQuantityThreshold; _shippingByTotalSettings.SmallQuantitySurcharge = model.SmallQuantitySurcharge; + _shippingByTotalSettings.CalculateTotalIncludingTax = model.CalculateTotalIncludingTax; _services.Settings.SaveSetting(_shippingByTotalSettings); - return Json(new { Result = true }); - } + NotifySuccess(T("Admin.Configuration.Updated")); + + return Configure(); + } } } diff --git a/src/Plugins/SmartStore.Shipping/Description.txt b/src/Plugins/SmartStore.Shipping/Description.txt index f241173e84..654d28c65a 100644 --- a/src/Plugins/SmartStore.Shipping/Description.txt +++ b/src/Plugins/SmartStore.Shipping/Description.txt @@ -2,8 +2,8 @@ Description: Provides shipping methods for fixed rate shipping and computation based on weight. SystemName: SmartStore.Shipping Group: Shipping -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 1 FileName: SmartStore.Shipping.dll ResourceRootKey: Plugins.SmartStore.Shipping \ No newline at end of file diff --git a/src/Plugins/SmartStore.Shipping/Localization/resources.de-de.xml b/src/Plugins/SmartStore.Shipping/Localization/resources.de-de.xml index 259ba4ecd4..de15e1946b 100644 --- a/src/Plugins/SmartStore.Shipping/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.Shipping/Localization/resources.de-de.xml @@ -15,10 +15,15 @@ Neue Versandbedingung hinzufügen - + + Die neue Versandbedingung wurde erfolgreich zugefügt. + + Optionen - + + Versandbedingungen + Land @@ -79,6 +84,12 @@ Dem Kunden wird während des Checkouts kostenloser Versand als Fallback angeboten, wenn keine der festgelegten Bedingungen zutreffen. Aktivieren Sie diese Option, wenn das nicht gewünscht ist. + + Berechne Bestellwert inkl. Steuer + + + Legt fest, ob der Bestellwert zur Berechnung der Versandkosten inkl. oder exkl. Steuern berechnet werden soll. + Basisgebühr diff --git a/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml b/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml index e229ab4d46..41e299ed1c 100644 --- a/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.Shipping/Localization/resources.en-us.xml @@ -16,10 +16,15 @@ Add new record - - Settings - - + + The new record was successfully added. + + + Options + + + Shipping records + Country @@ -80,6 +85,12 @@ If you check this option, then your customers will be limited to shipping options configured here. Otherwise, they'll be able to choose any existing shipping options even if they're not configured here (zero shipping fee in this case). + + Calculate order total incl. tax + + + Specifies whether to calculate the order total including or excluding taxes. + Base fee diff --git a/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs b/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs index 848836b1af..4816ca9b1b 100644 --- a/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs +++ b/src/Plugins/SmartStore.Shipping/Models/ByTotalListModel.cs @@ -63,6 +63,9 @@ public ByTotalListModel() [SmartResourceDisplayName("Plugins.Shipping.ByTotal.Fields.SmallQuantitySurcharge")] public decimal SmallQuantitySurcharge { get; set; } + [SmartResourceDisplayName("Plugins.Shipping.ByTotal.Fields.CalculateTotalIncludingTax")] + public bool CalculateTotalIncludingTax { get; set; } + public string PrimaryStoreCurrencyCode { get; set; } public int GridPageSize { get; set; } diff --git a/src/Plugins/SmartStore.Shipping/Plugin.cs b/src/Plugins/SmartStore.Shipping/Plugin.cs index 30eedb06c3..108c0a877a 100644 --- a/src/Plugins/SmartStore.Shipping/Plugin.cs +++ b/src/Plugins/SmartStore.Shipping/Plugin.cs @@ -57,7 +57,7 @@ public override void Install() base.Install(); - _logger.Information(string.Format("Plugin installed: SystemName: {0}, Version: {1}, Description: '{2}'", PluginDescriptor.SystemName, PluginDescriptor.Version, PluginDescriptor.FriendlyName)); + _logger.Info(string.Format("Plugin installed: SystemName: {0}, Version: {1}, Description: '{2}'", PluginDescriptor.SystemName, PluginDescriptor.Version, PluginDescriptor.FriendlyName)); } /// diff --git a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs index 82ec989cf4..f06e57d635 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs @@ -10,6 +10,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Shipping; using SmartStore.Services.Shipping.Tracking; +using SmartStore.Services.Tax; using SmartStore.Shipping.Services; namespace SmartStore.Shipping @@ -27,6 +28,7 @@ public class ByTotalProvider : IShippingRateComputationMethod, IConfigurable private readonly ILogger _logger; private readonly ISettingService _settingService; private readonly ILocalizationService _localizationService; + private readonly ITaxService _taxService; /// /// Ctor @@ -45,7 +47,8 @@ public ByTotalProvider(IShippingService shippingService, IPriceCalculationService priceCalculationService, ILogger logger, ISettingService settingService, - ILocalizationService localizationService) + ILocalizationService localizationService, + ITaxService taxService) { this._shippingService = shippingService; this._storeContext = storeContext; @@ -55,6 +58,7 @@ public ByTotalProvider(IShippingService shippingService, this._logger = logger; this._settingService = settingService; this._localizationService = localizationService; + _taxService = taxService; T = NullLocalizer.Instance; } @@ -122,7 +126,7 @@ public ShippingRateComputationMethodType ShippingRateComputationMethodType { shippingTotal = Math.Round((decimal)((((float)subtotal) * ((float)shippingByTotalRecord.ShippingChargePercentage)) / 100f), 2); shippingTotal += baseCharge; - if (maxCharge.HasValue && maxCharge > baseCharge) + if (maxCharge.HasValue && shippingTotal > maxCharge) { // shipping charge should not exceed MaxCharge shippingTotal = Math.Min(shippingTotal.Value, maxCharge.Value); @@ -184,13 +188,22 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get { continue; } - subTotal += _priceCalculationService.GetSubTotal(shoppingCartItem, true); - } - decimal sqThreshold = _shippingByTotalSettings.SmallQuantityThreshold; - decimal sqSurcharge = _shippingByTotalSettings.SmallQuantitySurcharge; + var itemSubTotalBase = _priceCalculationService.GetSubTotal(shoppingCartItem, true); + var itemSubTotal = _taxService.GetProductPrice( + shoppingCartItem.Item.Product, + itemSubTotalBase, + _shippingByTotalSettings.CalculateTotalIncludingTax, + getShippingOptionRequest.Customer, + out var _); + + subTotal += itemSubTotal; + } + + var sqThreshold = _shippingByTotalSettings.SmallQuantityThreshold; + var sqSurcharge = _shippingByTotalSettings.SmallQuantitySurcharge; - var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest); + var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest, storeId); foreach (var shippingMethod in shippingMethods) { decimal? rate = GetRate(subTotal, shippingMethod.Id, storeId, countryId, stateProvinceId, zip); @@ -198,15 +211,15 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get { if (rate > 0 && sqThreshold > 0 && subTotal <= sqThreshold) { - // add small quantity surcharge (Mindermengenzuschalg) + // Add small quantity surcharge (Mindermengenzuschlag). rate += sqSurcharge; } var shippingOption = new ShippingOption(); shippingOption.ShippingMethodId = shippingMethod.Id; - shippingOption.Name = shippingMethod.Name; - shippingOption.Description = shippingMethod.Description; - shippingOption.Rate = rate.Value; + shippingOption.Name = shippingMethod.GetLocalized(x => x.Name); + shippingOption.Description = shippingMethod.GetLocalized(x => x.Description); + shippingOption.Rate = rate.Value; response.ShippingOptions.Add(shippingOption); } } diff --git a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs index cc303cee6e..3612ec78e1 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs @@ -58,7 +58,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get return response; } - var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest); + var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest, getShippingOptionRequest.StoreId); foreach (var shippingMethod in shippingMethods) { var shippingOption = new ShippingOption(); @@ -82,7 +82,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest == null) throw new ArgumentNullException("getShippingOptionRequest"); - var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest); + var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest, getShippingOptionRequest.StoreId); var rates = new List(); foreach (var shippingMethod in shippingMethods) diff --git a/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs b/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs index a24143413c..c3a94d7e03 100644 --- a/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs +++ b/src/Plugins/SmartStore.Shipping/Services/ShippingByTotalService.cs @@ -163,45 +163,17 @@ public virtual ShippingByTotalRecord FindShippingByTotalRecord(int shippingMetho .Where(sbt => sbt.ShippingMethodId == shippingMethodId && subtotal >= sbt.From && (sbt.To == null || subtotal <= sbt.To.Value)) .ToList(); - //filter by store - var matchedByStore = new List(); - foreach (var sbw in existingRates) - { - if (sbw.StoreId == 0 || storeId == sbw.StoreId) - { - matchedByStore.Add(sbw); - } - } + //filter by store + var matchedByStore = existingRates.Where(x => x.StoreId == storeId || x.StoreId == 0).ToList(); //filter by country - var matchedByCountry = new List(); - foreach (var sbt in matchedByStore) - { - if (sbt.CountryId.GetValueOrDefault() == 0 || countryId == sbt.CountryId.GetValueOrDefault()) - { - matchedByCountry.Add(sbt); - } - } + var matchedByCountry = matchedByStore.Where(x => x.CountryId.GetValueOrDefault() == countryId || x.CountryId.GetValueOrDefault() == 0).ToList(); //filter by state/province - var matchedByStateProvince = new List(); - foreach (var sbt in matchedByCountry) - { - if (sbt.StateProvinceId.GetValueOrDefault() == 0 || stateProvinceId == sbt.StateProvinceId.GetValueOrDefault()) - { - matchedByStateProvince.Add(sbt); - } - } + var matchedByStateProvince = matchedByCountry.Where(x => x.StateProvinceId.GetValueOrDefault() == stateProvinceId || x.StateProvinceId.GetValueOrDefault() == 0).ToList(); //filter by zip - var matchedByZip = new List(); - foreach (var sbt in matchedByStateProvince) - { - if ((zip.IsEmpty() && sbt.Zip.IsEmpty()) || (ZipMatches(zip, sbt.Zip))) - { - matchedByZip.Add(sbt); - } - } + var matchedByZip = matchedByStateProvince.Where(x => (zip.IsEmpty() && x.Zip.IsEmpty()) || ZipMatches(zip, x.Zip)).ToList(); return matchedByZip.LastOrDefault(); } @@ -221,7 +193,7 @@ private bool ZipMatches(string zip, string pattern) { foreach (var entry in patterns) { - var wildcard = new Wildcard(entry); + var wildcard = new Wildcard(entry, true); if (wildcard.IsMatch(zip)) return true; } diff --git a/src/Plugins/SmartStore.Shipping/Settings/ShippingByTotalSettings.cs b/src/Plugins/SmartStore.Shipping/Settings/ShippingByTotalSettings.cs index 3ba3e94ee9..ce7f882691 100644 --- a/src/Plugins/SmartStore.Shipping/Settings/ShippingByTotalSettings.cs +++ b/src/Plugins/SmartStore.Shipping/Settings/ShippingByTotalSettings.cs @@ -4,7 +4,13 @@ namespace SmartStore.Shipping { public class ShippingByTotalSettings : ISettings { + public ShippingByTotalSettings() + { + CalculateTotalIncludingTax = true; + } + public bool LimitMethodsToCreated { get; set; } + public bool CalculateTotalIncludingTax { get; set; } public decimal SmallQuantityThreshold { get; set; } public decimal SmallQuantitySurcharge { get; set; } diff --git a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj index 3a9b8863dd..8d59d2df62 100644 --- a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj +++ b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.Shipping SmartStore.Shipping - v4.5.1 + v4.6.1 512 @@ -43,6 +44,7 @@ + true @@ -82,22 +84,25 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + False ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False @@ -107,36 +112,37 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll + False diff --git a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml index ae05d59b64..ee33ab492d 100644 --- a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml +++ b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml @@ -1,103 +1,149 @@ -@{ - Layout = ""; -} -@model ByTotalListModel - +@model ByTotalListModel @using SmartStore.Shipping.Models; @using SmartStore.Web.Framework; @using Telerik.Web.Mvc.UI; @using SmartStore.Web.Framework.UI +@{ + Layout = ""; +} - - - @T("Plugins.Shipping.ByTotal.AddNewRecordTitle") - +
    + +
    - - - - -
    - @(Html.Telerik().Grid() - .Name("shipping-bytotal-grid") - .DataKeys(keys => keys.Add(x => x.Id).RouteKey("Id")) - .DataBinding(dataBinding => dataBinding - .Ajax() - .Select("RatesList", "ByTotal", new RouteValueDictionary() { { "area", "SmartStore.Shipping" } }) - .Update("RateUpdate", "ByTotal", new RouteValueDictionary() { { "area", "SmartStore.Shipping" } }) - .Delete("RateDelete", "ByTotal", new RouteValueDictionary() { { "area", "SmartStore.Shipping" } }) - ) - .Columns(columns => - { - columns.Bound(x => x.StoreName).ReadOnly(); - columns.Bound(x => x.CountryName).ReadOnly(); - columns.Bound(x => x.StateProvinceName).ReadOnly(); - columns.Bound(x => x.Zip); - columns.Bound(x => x.ShippingMethodName).ReadOnly(); - columns.Bound(x => x.From) - .Format("{0:0.00}"); - columns.Bound(x => x.To) - .Format("{0:0.00}"); - columns.Bound(x => x.UsePercentage) - .Centered() - .Template(item => @Html.SymbolForBool(item.UsePercentage)) - .ClientTemplate(@Html.SymbolForBool("UsePercentage")); - columns.Bound(x => x.ShippingChargePercentage) - .Format("{0:0.00}"); - columns.Bound(x => x.ShippingChargeAmount) - .Format("{0:0.00}"); - columns.Bound(x => x.BaseCharge) - .Format("{0:0.00}"); - columns.Bound(x => x.MaxCharge) - .Format("{0:0.00}"); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(190); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .Editable(x => x.Mode(GridEditMode.InLine)) - .PreserveGridState() - .EnableCustomBinding(true)) -
    +@using (Html.BeginForm()) +{ + @Html.SmartStore().TabStrip().Name("shipping-by-weight-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Plugins.Shipping.ByTotal.TabTitleGrid").Text).Content(TabGrid()).Selected(true); + x.Add().Text(T("Plugins.Shipping.ByTotal.TabTitleSettings").Text).Content(TabSettings()); + }) +} - +@helper ShippingByTotalGridCommands(Grid grid) +{ + + + @T("Plugins.Shipping.ByTotal.AddNewRecordTitle") + +} + + - - {Html.SmartStore().Window() - .Name("addrecord-window") - .Title(T("Plugins.Shipping.ByTotal.AddNewRecordTitle")) - .Content( - @ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.AddStoreId) - - @Html.DropDownListFor(model => model.AddStoreId, Model.AvailableStores) - @Html.ValidationMessageFor(model => model.AddStoreId) -
    - @Html.SmartLabelFor(model => model.AddCountryId) - - @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries, "*") - @Html.ValidationMessageFor(model => model.AddCountryId) -
    - @Html.SmartLabelFor(model => model.AddStateProvinceId) - - @Html.DropDownListFor(model => model.AddStateProvinceId, Model.AvailableStates, "*") - @Html.ValidationMessageFor(model => model.AddStateProvinceId) -
    - @Html.SmartLabelFor(model => model.AddZip) - - @Html.EditorFor(model => model.AddZip) - @Html.ValidationMessageFor(model => model.AddZip) -
    - @Html.SmartLabelFor(model => model.AddShippingMethodId) - - @Html.DropDownListFor(model => model.AddShippingMethodId, Model.AvailableShippingMethods) - @Html.ValidationMessageFor(model => model.AddShippingMethodId) -
    - @Html.SmartLabelFor(model => model.AddFrom) - - @Html.EditorFor(model => model.AddFrom) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddFrom) -
    - @Html.SmartLabelFor(model => model.AddTo) - - @Html.EditorFor(model => model.AddTo) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddTo) -
    - @Html.SmartLabelFor(model => model.AddUsePercentage) - - @Html.EditorFor(model => model.AddUsePercentage) - @Html.ValidationMessageFor(model => model.AddUsePercentage) -
    - @Html.SmartLabelFor(model => model.AddShippingChargePercentage) - - @Html.EditorFor(model => model.AddShippingChargePercentage) - @Html.ValidationMessageFor(model => model.AddShippingChargePercentage) -
    - @Html.SmartLabelFor(model => model.AddBaseCharge) - - @Html.EditorFor(model => model.AddBaseCharge) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddBaseCharge) -
    - @Html.SmartLabelFor(model => model.AddMaxCharge) - - @Html.EditorFor(model => model.AddMaxCharge) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddMaxCharge) -
    - @Html.SmartLabelFor(model => model.AddShippingChargeAmount) - - @Html.EditorFor(model => model.AddShippingChargeAmount) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddShippingChargeAmount) -
    -
    ) - .FooterContent(@ - - - ) - .Modal(true) - .Visible(false) - .Render(); - } - -
    + }); + -
    - @T("Plugins.Shipping.ByTotal.SettingsTitle") - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SmallQuantityThreshold) - - @Html.EditorFor(model => model.SmallQuantityThreshold) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.SmallQuantityThreshold) -
    - @Html.SmartLabelFor(model => model.SmallQuantitySurcharge) - - @Html.EditorFor(model => model.SmallQuantitySurcharge) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.SmallQuantitySurcharge) -
    - @Html.SmartLabelFor(model => model.LimitMethodsToCreated) - - @Html.EditorFor(model => model.LimitMethodsToCreated) - @Html.ValidationMessageFor(model => model.LimitMethodsToCreated) -
    -   - - -
    -
    +@{Html.SmartStore().Window() + .Name("addrecord-window") + .Size(WindowSize.Large) + .Title(T("Plugins.Shipping.ByTotal.AddNewRecordTitle")) + .Content( + @ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.AddStoreId) + + @Html.DropDownListFor(model => model.AddStoreId, Model.AvailableStores) + @Html.ValidationMessageFor(model => model.AddStoreId) +
    + @Html.SmartLabelFor(model => model.AddCountryId) + + @Html.DropDownList("AddCountryId", Model.AvailableCountries, "*", + new + { + @class = "form-control country-input country-selector", + data_region_control_selector = "#AddStateProvinceId", + data_states_ajax_url = Url.Action("GetStatesByCountryId", "Country", new RouteValueDictionary { { "area", "Admin" } }), + data_addAsterisk = "true", + data_addEmptyStateIfRequired = "false" + }) + + @Html.ValidationMessageFor(model => model.AddCountryId) +
    + @Html.SmartLabelFor(model => model.AddStateProvinceId) + + @Html.DropDownListFor(model => model.AddStateProvinceId, Model.AvailableStates, "*") + @Html.ValidationMessageFor(model => model.AddStateProvinceId) +
    + @Html.SmartLabelFor(model => model.AddZip) + + @Html.EditorFor(model => model.AddZip) + @Html.ValidationMessageFor(model => model.AddZip) +
    + @Html.SmartLabelFor(model => model.AddShippingMethodId) + + @Html.DropDownListFor(model => model.AddShippingMethodId, Model.AvailableShippingMethods) + @Html.ValidationMessageFor(model => model.AddShippingMethodId) +
    + @Html.SmartLabelFor(model => model.AddFrom) + + @Html.EditorFor(model => model.AddFrom, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AddFrom) +
    + @Html.SmartLabelFor(model => model.AddTo) + + @Html.EditorFor(model => model.AddTo, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AddTo) +
    + @Html.SmartLabelFor(model => model.AddUsePercentage) + + @Html.EditorFor(model => model.AddUsePercentage) + @Html.ValidationMessageFor(model => model.AddUsePercentage) +
    + @Html.SmartLabelFor(model => model.AddShippingChargePercentage) + + @Html.EditorFor(model => model.AddShippingChargePercentage) + @Html.ValidationMessageFor(model => model.AddShippingChargePercentage) +
    + @Html.SmartLabelFor(model => model.AddBaseCharge) + + @Html.EditorFor(model => model.AddBaseCharge, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AddBaseCharge) +
    + @Html.SmartLabelFor(model => model.AddMaxCharge) + + @Html.EditorFor(model => model.AddMaxCharge, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AddMaxCharge) +
    + @Html.SmartLabelFor(model => model.AddShippingChargeAmount) + + @Html.EditorFor(model => model.AddShippingChargeAmount, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AddShippingChargeAmount) +
    +
    +
    ) + .FooterContent(@ + + + ) + .Render(); } \ No newline at end of file diff --git a/src/Plugins/SmartStore.Shipping/Views/FixedRate/Configure.cshtml b/src/Plugins/SmartStore.Shipping/Views/FixedRate/Configure.cshtml index db753c9db9..04876565d0 100644 --- a/src/Plugins/SmartStore.Shipping/Views/FixedRate/Configure.cshtml +++ b/src/Plugins/SmartStore.Shipping/Views/FixedRate/Configure.cshtml @@ -1,39 +1,34 @@ -@{ - Layout = ""; -} -@model Telerik.Web.Mvc.GridModel +@model Telerik.Web.Mvc.GridModel @using Telerik.Web.Mvc.UI; -@using System.Linq; +@using SmartStore.Shipping.Models; +@{ + Layout = ""; +} - - - - -
    - @(Html.Telerik().Grid(Model.Data) - .Name("Grid") - .DataKeys(keys => keys.Add(x => x.ShippingMethodId).RouteKey("ShippingMethodId")) - .Columns(columns => - { - columns.Bound(x => x.ShippingMethodName) - .ReadOnly(); - columns.Bound(x => x.Rate) - .Format("{0:0.00}") - .Width(180); - columns.Command(commands => - { - commands.Edit(); - }).Width(180); - }) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("Configure", "FixedRate", new RouteValueDictionary() { { "Namespaces", "SmartStore.Shipping.Controllers" }, { "area", "SmartStore.Shipping" } }) - .Update("ShippingRateUpdate", "FixedRate", new RouteValueDictionary() { { "Namespaces", "SmartStore.Shipping.Controllers" }, { "area", "SmartStore.Shipping" } }); - }) - .EnableCustomBinding(true)) -
    +
    + @(Html.Telerik().Grid(Model.Data) + .Name("Grid") + .DataKeys(keys => keys.Add(x => x.ShippingMethodId).RouteKey("ShippingMethodId")) + .Columns(columns => + { + columns.Bound(x => x.ShippingMethodName) + .ReadOnly() + .Width("80%"); ; + columns.Bound(x => x.Rate) + .Format("{0:0.00}") + .Width("8%"); + columns.Command(commands => + { + commands.Edit(); + }).Width("12%") + .HtmlAttributes(new { align = "right", @class = "omega" }); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("Configure", "FixedRate", new RouteValueDictionary() { { "Namespaces", "SmartStore.Shipping.Controllers" }, { "area", "SmartStore.Shipping" } }) + .Update("ShippingRateUpdate", "FixedRate", new RouteValueDictionary() { { "Namespaces", "SmartStore.Shipping.Controllers" }, { "area", "SmartStore.Shipping" } }); + }) + .Editable(x => { x.Mode(GridEditMode.InLine); }) + .EnableCustomBinding(true)) +
    diff --git a/src/Plugins/SmartStore.Shipping/Views/Web.config b/src/Plugins/SmartStore.Shipping/Views/Web.config index e9d36a3c51..26c177a9ab 100644 --- a/src/Plugins/SmartStore.Shipping/Views/Web.config +++ b/src/Plugins/SmartStore.Shipping/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.Shipping/packages.config b/src/Plugins/SmartStore.Shipping/packages.config index 1a1ea4e95b..fadff4720e 100644 --- a/src/Plugins/SmartStore.Shipping/packages.config +++ b/src/Plugins/SmartStore.Shipping/packages.config @@ -1,10 +1,10 @@  - - - - - - + + + + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index c0f82db177..fbe6b4c179 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -1,115 +1,147 @@ - + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs index a147a1855d..680abbbe79 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs @@ -10,9 +10,11 @@ using SmartStore.Services.Localization; using SmartStore.Services.Shipping; using SmartStore.Services.Shipping.Tracking; +using SmartStore.Services.Tax; using SmartStore.ShippingByWeight.Data; using SmartStore.ShippingByWeight.Data.Migrations; using SmartStore.ShippingByWeight.Services; +using SmartStore.Core.Domain.Tax; namespace SmartStore.ShippingByWeight { @@ -29,6 +31,7 @@ public class ByWeightShippingComputationMethod : BasePlugin, IShippingRateComput private readonly ILocalizationService _localizationService; private readonly IPriceFormatter _priceFormatter; private readonly ICommonServices _services; + private readonly ITaxService _taxService; #endregion @@ -42,7 +45,8 @@ public ByWeightShippingComputationMethod(IShippingService shippingService, ShippingByWeightObjectContext objectContext, ILocalizationService localizationService, IPriceFormatter priceFormatter, - ICommonServices services) + ICommonServices services, + ITaxService taxService) { this._shippingService = shippingService; this._storeContext = storeContext; @@ -53,6 +57,7 @@ public ByWeightShippingComputationMethod(IShippingService shippingService, this._localizationService = localizationService; this._priceFormatter = priceFormatter; this._services = services; + _taxService = taxService; T = NullLocalizer.Instance; } @@ -132,7 +137,10 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest req } int storeId = request.StoreId > 0 ? request.StoreId : _storeContext.CurrentStore.Id; - decimal subTotal = decimal.Zero; + var taxRate = decimal.Zero; + decimal subTotalInclTax = decimal.Zero; + decimal subTotalExclTax = decimal.Zero; + decimal currentSubTotal = decimal.Zero; int countryId = 0; string zip = null; @@ -144,25 +152,36 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest req foreach (var shoppingCartItem in request.Items) { - if (shoppingCartItem.Item.IsFreeShipping || !shoppingCartItem.Item.IsShipEnabled) - continue; - subTotal += _priceCalculationService.GetSubTotal(shoppingCartItem, true); + if (shoppingCartItem.Item.IsFreeShipping || !shoppingCartItem.Item.IsShipEnabled) + { + continue; + } + + var itemSubTotal = _priceCalculationService.GetSubTotal(shoppingCartItem, true); + + var itemSubTotalInclTax = _taxService.GetProductPrice(shoppingCartItem.Item.Product, itemSubTotal, true, request.Customer, out taxRate); + subTotalInclTax += itemSubTotalInclTax; + + var itemSubTotalExclTax = _taxService.GetProductPrice(shoppingCartItem.Item.Product, itemSubTotal, false, request.Customer, out taxRate); + subTotalExclTax += itemSubTotalExclTax; } - decimal weight = _shippingService.GetShoppingCartTotalWeight(request.Items); - var shippingMethods = _shippingService.GetAllShippingMethods(request); + var weight = _shippingService.GetShoppingCartTotalWeight(request.Items, _shippingByWeightSettings.IncludeWeightOfFreeShippingProducts); + var shippingMethods = _shippingService.GetAllShippingMethods(request, storeId); + currentSubTotal = _services.WorkContext.TaxDisplayType == TaxDisplayType.ExcludingTax ? subTotalExclTax : subTotalInclTax; + foreach (var shippingMethod in shippingMethods) { var record = _shippingByWeightService.FindRecord(shippingMethod.Id, storeId, countryId, weight, zip); - decimal? rate = GetRate(subTotal, weight, shippingMethod.Id, storeId, countryId, zip); + decimal? rate = GetRate(subTotalInclTax, weight, shippingMethod.Id, storeId, countryId, zip); if (rate.HasValue) { var shippingOption = new ShippingOption(); shippingOption.ShippingMethodId = shippingMethod.Id; shippingOption.Name = shippingMethod.GetLocalized(x => x.Name); - if (record != null && record.SmallQuantityThreshold > subTotal) + if (record != null && record.SmallQuantityThreshold > currentSubTotal) { shippingOption.Description = shippingMethod.GetLocalized(x => x.Description) + _localizationService.GetResource("Plugin.Shipping.ByWeight.SmallQuantitySurchargeNotReached").FormatWith( diff --git a/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs b/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs index 383d77dfa2..d68d99d695 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs @@ -8,13 +8,11 @@ using SmartStore.ShippingByWeight.Models; using SmartStore.ShippingByWeight.Services; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.ShippingByWeight.Controllers { - [AdminAuthorize] public class ShippingByWeightController : PluginControllerBase { @@ -79,6 +77,7 @@ public ActionResult Configure() model.LimitMethodsToCreated = _shippingByWeightSettings.LimitMethodsToCreated; model.CalculatePerWeightUnit = _shippingByWeightSettings.CalculatePerWeightUnit; + model.IncludeWeightOfFreeShippingProducts = _shippingByWeightSettings.IncludeWeightOfFreeShippingProducts; model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; model.BaseWeightIn = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name; model.GridPageSize = _adminAreaSettings.GridPageSize; @@ -130,15 +129,9 @@ public ActionResult RateDelete(int id, GridCommand command) return RatesList(command); } - [HttpPost, ActionName("Configure")] - [FormValueRequired("addshippingbyweightrecord")] + [HttpPost] public ActionResult AddShippingByWeightRecord(ShippingByWeightListModel model) { - if (!ModelState.IsValid) - { - return Configure(); - } - var sbw = new ShippingByWeightRecord() { StoreId = model.AddStoreId, @@ -154,22 +147,26 @@ public ActionResult AddShippingByWeightRecord(ShippingByWeightListModel model) SmallQuantitySurcharge = model.SmallQuantitySurcharge, SmallQuantityThreshold = model.SmallQuantityThreshold, }; + _shippingByWeightService.InsertShippingByWeightRecord(sbw); - return Configure(); - } + NotifySuccess(T("Plugins.Shipping.ByWeight.AddNewRecord.Success")); - [HttpPost, ActionName("Configure")] - [FormValueRequired("savegeneralsettings")] - public ActionResult SaveGeneralSettings(ShippingByWeightListModel model) + return Json(new { Result = true }); + } + + [HttpPost] + public ActionResult Configure(ShippingByWeightListModel model) { - //save settings _shippingByWeightSettings.LimitMethodsToCreated = model.LimitMethodsToCreated; _shippingByWeightSettings.CalculatePerWeightUnit = model.CalculatePerWeightUnit; + _shippingByWeightSettings.IncludeWeightOfFreeShippingProducts = model.IncludeWeightOfFreeShippingProducts; + _services.Settings.SaveSetting(_shippingByWeightSettings); - - return Configure(); - } - + + NotifySuccess(T("Admin.Configuration.Updated")); + + return Configure(); + } } } diff --git a/src/Plugins/SmartStore.ShippingByWeight/Description.txt b/src/Plugins/SmartStore.ShippingByWeight/Description.txt index 218a277c2b..f73070d64c 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Description.txt +++ b/src/Plugins/SmartStore.ShippingByWeight/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Shipping by weight SystemName: SmartStore.ShippingByWeight Group: Shipping -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 1 FileName: SmartStore.ShippingByWeight.dll ResourceRootKey: Plugins.Shipping.ByWeight \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml index 1cb41d9656..a81cc70122 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.de-de.xml @@ -63,6 +63,12 @@ Legt fest, ob die Gebühr mit dem Gesamt-Warengewicht multipliziert werden soll. Diese Option wird ignoriert, wenn eine prozentuale Gebühr erhoben wird. + + Gewicht versandkostenbefreiter Produkte berücksichtigen + + + Legt fest, ob das Gewicht von versandkostenbefreiten Produkten bei der Berechnung berücksichtigt werden soll. + Shop @@ -81,14 +87,22 @@ Warenwert, bis zu dem ein Mindermengenzuschlag erhoben werden soll. Der Zuschlag wird ignoriert, wenn keine Versandkosten anfallen. Verwenden Sie "0", wenn kein Zuschlag erhoben werden soll. - - Es wird ein Mindermengenzuschlag von {0} erhoben, da Ihr Bestellwert unter {1} liegt.]]> + + + Sie werden mit einem Zuschlag von {0} belastet, weil die Summe Ihrer Bestellung nicht {1} erreicht hat.]]> + Neue Versandbedingung hinzufügen - + + Die neue Versandbedingung wurde erfolgreich zugefügt. + + Optionen + + Versandbedingungen + \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml index c168a3ef5f..a3097a2517 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.ShippingByWeight/Localization/resources.en-us.xml @@ -61,6 +61,12 @@ If you check this option, then rates are multiplied per weight unit (lb, kg, etc). This option is used for the fixed rates (without percents). + + + Include weight of free shipping products + + + Specifies whether to include the weight of free shipping products in the calculation. Store @@ -81,16 +87,21 @@ Subtotal up to which a "small quantity surcharge" should be added. The surcharge will be ignored if no shipping fee is applied. Use "0" if no fee will be charged. - + You're charged with a surcharge of {0} because the total of your order hasn't reached {1}.]]> - - + Add new record - - Settings + + The new record was successfully added. + + + Options + + + Shipping records \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs b/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs index eb7710b070..a29747baa9 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Models/ShippingByWeightListModel.cs @@ -44,7 +44,10 @@ public ShippingByWeightListModel() [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.CalculatePerWeightUnit")] public bool CalculatePerWeightUnit { get; set; } - public string PrimaryStoreCurrencyCode { get; set; } + [SmartResourceDisplayName("Plugins.Shipping.ByWeight.Fields.IncludeWeightOfFreeShippingProducts")] + public bool IncludeWeightOfFreeShippingProducts { get; set; } + + public string PrimaryStoreCurrencyCode { get; set; } public string BaseWeightIn { get; set; } public int GridPageSize { get; set; } diff --git a/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs b/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs index f60212f80c..186f71ac26 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs @@ -115,49 +115,14 @@ public virtual ShippingByWeightRecord FindRecord(int shippingMethodId, int store .Where(x => x.ShippingMethodId == shippingMethodId && weight >= x.From && weight <= x.To) .ToList(); - //filter by store - var matchedByStore = new List(); - foreach (var sbw in existingRecords) - { - if (storeId == sbw.StoreId) - matchedByStore.Add(sbw); - } + //filter by store + var matchedByStore = existingRecords.Where(x => x.StoreId == storeId || x.StoreId == 0).ToList(); - if (matchedByStore.Count == 0) - { - foreach (var sbw in existingRecords) - { - if (sbw.StoreId == 0) - matchedByStore.Add(sbw); - } - } - - //filter by country - var matchedByCountry = new List(); - foreach (var sbw in matchedByStore) - { - if (countryId == sbw.CountryId) - matchedByCountry.Add(sbw); - } - - if (matchedByCountry.Count == 0) - { - foreach (var sbw in matchedByStore) - { - if (sbw.CountryId == 0) - matchedByCountry.Add(sbw); - } - } + //filter by country + var matchedByCountry = matchedByStore.Where(x => x.CountryId == countryId || x.CountryId == 0).ToList(); //filter by zip - var matchedByZip = new List(); - foreach (var sbt in matchedByCountry) - { - if ((zip.IsEmpty() && sbt.Zip.IsEmpty()) || (ZipMatches(zip, sbt.Zip))) - { - matchedByZip.Add(sbt); - } - } + var matchedByZip = matchedByCountry.Where(x => (zip.IsEmpty() && x.Zip.IsEmpty()) || ZipMatches(zip, x.Zip)).ToList(); return matchedByZip.LastOrDefault(); } @@ -210,7 +175,7 @@ private bool ZipMatches(string zip, string pattern) { foreach (var entry in patterns) { - var wildcard = new Wildcard(entry); + var wildcard = new Wildcard(entry, true); if (wildcard.IsMatch(zip)) return true; } diff --git a/src/Plugins/SmartStore.ShippingByWeight/ShippingByWeightSettings.cs b/src/Plugins/SmartStore.ShippingByWeight/ShippingByWeightSettings.cs index 4d0c9050df..42eced25af 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/ShippingByWeightSettings.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/ShippingByWeightSettings.cs @@ -1,12 +1,21 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; namespace SmartStore.ShippingByWeight { public class ShippingByWeightSettings : ISettings { - public bool LimitMethodsToCreated { get; set; } + public ShippingByWeightSettings() + { + IncludeWeightOfFreeShippingProducts = true; + } + + public bool LimitMethodsToCreated { get; set; } public bool CalculatePerWeightUnit { get; set; } - } + + /// + /// Whether to include the weight of free shipping products in shipping calculation + /// + public bool IncludeWeightOfFreeShippingProducts { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj b/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj index 990c0f2f61..30c483ab7c 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj +++ b/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.ShippingByWeight SmartStore.ShippingByWeight - v4.5.1 + v4.6.1 512 @@ -43,6 +44,7 @@ + true @@ -82,19 +84,21 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + False ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -108,37 +112,38 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - False ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll + False diff --git a/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml b/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml index 3da335400c..ccb6c906ee 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml +++ b/src/Plugins/SmartStore.ShippingByWeight/Views/ShippingByWeight/Configure.cshtml @@ -6,211 +6,25 @@ Layout = ""; } - - - - -
    - @(Html.Telerik().Grid() - .Name("Grid") - .DataKeys(keys => keys.Add(x => x.Id).RouteKey("Id")) - .Columns(columns => - { - columns.Bound(x => x.StoreName).ReadOnly(); - columns.Bound(x => x.CountryName).ReadOnly(); - columns.Bound(x => x.Zip); - columns.Bound(x => x.ShippingMethodName).ReadOnly(); - columns.Bound(x => x.From) - .Format("{0:0.00}"); - columns.Bound(x => x.To) - .Format("{0:0.00}"); - columns.Bound(x => x.UsePercentage) - .Centered() - .Template(item => @Html.SymbolForBool(item.UsePercentage)) - .ClientTemplate(@Html.SymbolForBool("UsePercentage")); - columns.Bound(x => x.ShippingChargePercentage) - .Format("{0:0.00}"); - columns.Bound(x => x.ShippingChargeAmount) - .Format("{0:0.00}"); - columns.Bound(x => x.SmallQuantitySurcharge) - .Format("{0:0.00}"); - columns.Bound(x => x.SmallQuantityThreshold) - .Format("{0:0.00}"); - columns.Command(commands => - { - commands.Edit(); - commands.Delete(); - }).Width(180); - - }) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("RatesList", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }) - .Update("RateUpdate", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }) - .Delete("RateDelete", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .PreserveGridState() - .EnableCustomBinding(true)) -
    -

    +
    + +
    @using (Html.BeginForm()) -{ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    @T("Plugins.Shipping.ByWeight.AddNewRecordTitle")
    -
    -
    - @Html.SmartLabelFor(model => model.AddStoreId) - - @Html.DropDownListFor(model => model.AddStoreId, Model.AvailableStores) - @Html.ValidationMessageFor(model => model.AddStoreId) -
    - @Html.SmartLabelFor(model => model.AddCountryId) - - @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) - @Html.ValidationMessageFor(model => model.AddCountryId) -
    - @Html.SmartLabelFor(model => model.AddZip) - - @Html.EditorFor(model => model.AddZip) - @Html.ValidationMessageFor(model => model.AddZip) -
    - @Html.SmartLabelFor(model => model.AddShippingMethodId) - - @Html.DropDownListFor(model => model.AddShippingMethodId, Model.AvailableShippingMethods) - @Html.ValidationMessageFor(model => model.AddShippingMethodId) -
    - @Html.SmartLabelFor(model => model.AddFrom) - - @Html.EditorFor(model => model.AddFrom) [@Model.BaseWeightIn] - @Html.ValidationMessageFor(model => model.AddFrom) -
    - @Html.SmartLabelFor(model => model.AddTo) - - @Html.EditorFor(model => model.AddTo) [@Model.BaseWeightIn] - @Html.ValidationMessageFor(model => model.AddTo) -
    - @Html.SmartLabelFor(model => model.AddUsePercentage) - - @Html.EditorFor(model => model.AddUsePercentage) - @Html.ValidationMessageFor(model => model.AddUsePercentage) -
    - @Html.SmartLabelFor(model => model.AddShippingChargePercentage) - - @Html.EditorFor(model => model.AddShippingChargePercentage) - @Html.ValidationMessageFor(model => model.AddShippingChargePercentage) -
    - @Html.SmartLabelFor(model => model.AddShippingChargeAmount) - - @Html.EditorFor(model => model.AddShippingChargeAmount) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddShippingChargeAmount) -
    - @Html.SmartLabelFor(model => model.SmallQuantitySurcharge) - - @Html.EditorFor(model => model.SmallQuantitySurcharge) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.SmallQuantitySurcharge) -
    - @Html.SmartLabelFor(model => model.SmallQuantityThreshold) - - @Html.EditorFor(model => model.SmallQuantityThreshold) - @Html.ValidationMessageFor(model => model.SmallQuantityThreshold) -
    -   - - -
    +{ + @Html.SmartStore().TabStrip().Name("shipping-by-weight-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Plugins.Shipping.ByWeight.TabTitleGrid").Text).Content(TabGrid()).Selected(true); + x.Add().Text(T("Plugins.Shipping.ByWeight.TabTitleSettings").Text).Content(TabSettings()); + }) +} - - - - +@helper TabSettings() +{ +
    -
    -
    @T("Plugins.Shipping.ByWeight.SettingsTitle")
    -
    -
    @Html.SmartLabelFor(model => model.CalculatePerWeightUnit) @@ -231,13 +45,250 @@
    -   + @Html.SmartLabelFor(model => model.IncludeWeightOfFreeShippingProducts) - + @Html.EditorFor(model => model.IncludeWeightOfFreeShippingProducts) + @Html.ValidationMessageFor(model => model.IncludeWeightOfFreeShippingProducts)
    +} + +@helper TabGrid() +{ +
    + @(Html.Telerik().Grid() + .Name("shipping-byweight-grid") + .DataKeys(keys => keys.Add(x => x.Id).RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.StoreName) + .Width("8%") + .ReadOnly(); + columns.Bound(x => x.CountryName) + .Width("8%") + .ReadOnly(); + columns.Bound(x => x.Zip) + .Width("8%"); + columns.Bound(x => x.ShippingMethodName) + .Width("8%") + .ReadOnly(); + columns.Bound(x => x.From) + .Width("8%") + .Format("{0:0.00}"); + columns.Bound(x => x.To) + .Width("8%") + .Format("{0:0.00}"); + columns.Bound(x => x.UsePercentage) + .Width("8%") + .Centered() + .Template(item => @Html.SymbolForBool(item.UsePercentage)) + .ClientTemplate(@Html.SymbolForBool("UsePercentage")); + columns.Bound(x => x.ShippingChargePercentage) + .Width("8%") + .Format("{0:0.00}"); + columns.Bound(x => x.ShippingChargeAmount) + .Width("8%") + .Format("{0:0.00}"); + columns.Bound(x => x.SmallQuantitySurcharge) + .Width("8%") + .Format("{0:0.00}"); + columns.Bound(x => x.SmallQuantityThreshold) + .Width("8%") + .Format("{0:0.00}"); + columns.Command(commands => + { + commands.Edit(); + commands.Delete(); + }).Width("12%").HtmlAttributes(new { align = "right" }); + + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("RatesList", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }) + .Update("RateUpdate", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }) + .Delete("RateDelete", "ShippingByWeight", new RouteValueDictionary() { { "area", "SmartStore.ShippingByWeight" } }); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .Editable(x => x.Mode(GridEditMode.InLine)) + .ToolBar(commands => commands.Template(ShippingByWeightGridCommands)) + .PreserveGridState() + .EnableCustomBinding(true)) +
    +} + +@helper ShippingByWeightGridCommands(Grid grid) +{ + + + @T("Plugins.Shipping.ByTotal.AddNewRecordTitle") + +} + + + +@{Html.SmartStore().Window() + .Name("addrecord-window") + .Size(WindowSize.Large) + .Title(T("Plugins.Shipping.ByWeight.AddNewRecordTitle")) + .Content( + @ +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.AddStoreId) + + @Html.DropDownListFor(model => model.AddStoreId, Model.AvailableStores) + @Html.ValidationMessageFor(model => model.AddStoreId) +
    + @Html.SmartLabelFor(model => model.AddCountryId) + + @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) + @Html.ValidationMessageFor(model => model.AddCountryId) +
    + @Html.SmartLabelFor(model => model.AddZip) + + @Html.EditorFor(model => model.AddZip) + @Html.ValidationMessageFor(model => model.AddZip) +
    + @Html.SmartLabelFor(model => model.AddShippingMethodId) + + @Html.DropDownListFor(model => model.AddShippingMethodId, Model.AvailableShippingMethods) + @Html.ValidationMessageFor(model => model.AddShippingMethodId) +
    + @Html.SmartLabelFor(model => model.AddFrom) + + @Html.EditorFor(model => model.AddFrom, new { postfix = Model.BaseWeightIn }) + @Html.ValidationMessageFor(model => model.AddFrom) +
    + @Html.SmartLabelFor(model => model.AddTo) + + @Html.EditorFor(model => model.AddTo, new { postfix = Model.BaseWeightIn }) + @Html.ValidationMessageFor(model => model.AddTo) +
    + @Html.SmartLabelFor(model => model.AddUsePercentage) + + @Html.EditorFor(model => model.AddUsePercentage) + @Html.ValidationMessageFor(model => model.AddUsePercentage) +
    + @Html.SmartLabelFor(model => model.AddShippingChargePercentage) + + @Html.EditorFor(model => model.AddShippingChargePercentage) + @Html.ValidationMessageFor(model => model.AddShippingChargePercentage) +
    + @Html.SmartLabelFor(model => model.AddShippingChargeAmount) + + @Html.EditorFor(model => model.AddShippingChargeAmount, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AddShippingChargeAmount) +
    + @Html.SmartLabelFor(model => model.SmallQuantitySurcharge) + + @Html.EditorFor(model => model.SmallQuantitySurcharge, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.SmallQuantitySurcharge) +
    + @Html.SmartLabelFor(model => model.SmallQuantityThreshold) + + @Html.EditorFor(model => model.SmallQuantityThreshold) + @Html.ValidationMessageFor(model => model.SmallQuantityThreshold) +
    +
    +
    ) + .FooterContent(@ + + + ) + .Render(); } \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Views/Web.config b/src/Plugins/SmartStore.ShippingByWeight/Views/Web.config index e9d36a3c51..26c177a9ab 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Views/Web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.ShippingByWeight/packages.config b/src/Plugins/SmartStore.ShippingByWeight/packages.config index 1a1ea4e95b..bbe9260aec 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/packages.config +++ b/src/Plugins/SmartStore.ShippingByWeight/packages.config @@ -1,10 +1,10 @@  - - - - - - + + + + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index c0f82db177..fbe6b4c179 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -1,115 +1,147 @@ - + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs b/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs index ca43d31f14..afba5e48b9 100644 --- a/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs +++ b/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs @@ -1,82 +1,104 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Threading; +using System.Linq; using System.Web.Mvc; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Tax; +using SmartStore.Services.Directory; +using SmartStore.Services.Tax; using SmartStore.Tax.Domain; using SmartStore.Tax.Models; using SmartStore.Tax.Services; -using SmartStore.Services.Directory; -using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; using Telerik.Web.Mvc; namespace SmartStore.Tax.Controllers { - [AdminAuthorize] public class TaxByRegionController : PluginControllerBase { - private readonly ITaxCategoryService _taxCategoryService; + private readonly ITaxRateService _taxRateService; + private readonly ITaxCategoryService _taxCategoryService; private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; - private readonly ITaxRateService _taxRateService; - public TaxByRegionController(ITaxCategoryService taxCategoryService, - ICountryService countryService, IStateProvinceService stateProvinceService, - ITaxRateService taxRateService) + public TaxByRegionController( + ITaxRateService taxRateService, + ITaxCategoryService taxCategoryService, + ICountryService countryService, + IStateProvinceService stateProvinceService) { - this._taxCategoryService = taxCategoryService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._taxRateService = taxRateService; + _taxRateService = taxRateService; + _taxCategoryService = taxCategoryService; + _countryService = countryService; + _stateProvinceService = stateProvinceService; } - - public ActionResult Configure() + private void PrepareModel(ByRegionTaxRateListModel model) + { + var taxCategories = _taxCategoryService.GetAllTaxCategories().ToDictionary(x => x.Id); + var taxRates = _taxRateService.GetAllTaxRates(); + var countries = _countryService.GetAllCountries(true).ToDictionary(x => x.Id); + var stateProvinces = _stateProvinceService.GetAllStateProvinces(true).ToDictionary(x => x.Id); + var stateProvincesOfFirstCountry = stateProvinces.Values.Where(x => x.CountryId == countries.Values.FirstOrDefault().Id).ToList(); + var unavailable = T("Common.Unavailable").Text; + + model.AvailableTaxCategories = taxCategories.Values.Select(x => new SelectListItem + { + Text = x.Name, + Value = x.Id.ToString() + }) + .ToList(); + + model.AvailableCountries = countries.Values.Select(x => new SelectListItem + { + Text = x.Name, + Value = x.Id.ToString() + }) + .ToList(); + + model.AvailableStates = stateProvincesOfFirstCountry.Select(x => new SelectListItem + { + Text = x.Name, + Value = x.Id.ToString() + }) + .ToList(); + model.AvailableStates.Insert(0, new SelectListItem { Text = "*", Value = "0" }); + + model.TaxRates = taxRates.Select(x => + { + var m = new ByRegionTaxRateModel + { + Id = x.Id, + TaxCategoryId = x.TaxCategoryId, + CountryId = x.CountryId, + StateProvinceId = x.StateProvinceId, + Zip = x.Zip.HasValue() ? x.Zip : "*", + Percentage = x.Percentage + }; + + taxCategories.TryGetValue(x.TaxCategoryId, out TaxCategory tc); + m.TaxCategoryName = tc?.Name.EmptyNull(); + + countries.TryGetValue(x.CountryId, out Country c); + m.CountryName = c?.Name ?? unavailable; + + stateProvinces.TryGetValue(x.StateProvinceId, out StateProvince s); + m.StateProvinceName = s?.Name ?? "*"; + + return m; + }) + .ToList(); + } + + public ActionResult Configure() { - var taxCategories = _taxCategoryService.GetAllTaxCategories(); - if (taxCategories.Count == 0) - return Content("No tax categories can be loaded"); - - var model = new ByRegionTaxRateListModel(); - foreach (var tc in taxCategories) - model.AvailableTaxCategories.Add(new SelectListItem() { Text = tc.Name, Value = tc.Id.ToString() }); - var countries = _countryService.GetAllCountries(true); - foreach (var c in countries) - model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString() }); - model.AvailableStates.Add(new SelectListItem() { Text = "*", Value = "0" }); - var states = _stateProvinceService.GetStateProvincesByCountryId(countries.FirstOrDefault().Id); - if (states.Count > 0) - { - foreach (var s in states) - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString() }); - } + var model = new ByRegionTaxRateListModel(); + PrepareModel(model); - model.TaxRates = _taxRateService.GetAllTaxRates() - .Select(x => - { - var m = new ByRegionTaxRateModel() - { - Id = x.Id, - TaxCategoryId = x.TaxCategoryId, - CountryId = x.CountryId, - StateProvinceId = x.StateProvinceId, - Zip = x.Zip, - Percentage = x.Percentage, - }; - var tc = _taxCategoryService.GetTaxCategoryById(x.TaxCategoryId); - m.TaxCategoryName = (tc != null) ? tc.Name : ""; - var c = _countryService.GetCountryById(x.CountryId); - m.CountryName = (c != null) ? c.Name : T("Common.Unavailable").Text; - var s = _stateProvinceService.GetStateProvinceById(x.StateProvinceId); - m.StateProvinceName = (s != null) ? s.Name : "*"; - m.Zip = (!String.IsNullOrEmpty(x.Zip)) ? x.Zip : "*"; - return m; - }) - .ToList(); + if (!model.AvailableTaxCategories.Any()) + { + NotifyWarning(T("Plugins.Tax.CountryStateZip.NoTaxCategoriesFound")); + } return View(model); } @@ -84,37 +106,18 @@ public ActionResult Configure() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult RatesList(GridCommand command) { - var taxRatesModel = _taxRateService.GetAllTaxRates() - .Select(x => - { - var m = new ByRegionTaxRateModel() - { - Id = x.Id, - TaxCategoryId = x.TaxCategoryId, - CountryId = x.CountryId, - StateProvinceId = x.StateProvinceId, - Zip = x.Zip, - Percentage = x.Percentage, - }; - var tc = _taxCategoryService.GetTaxCategoryById(x.TaxCategoryId); - m.TaxCategoryName = (tc != null) ? tc.Name : ""; - var c = _countryService.GetCountryById(x.CountryId); - m.CountryName = (c != null) ? c.Name : T("Common.Unavailable").Text; - var s = _stateProvinceService.GetStateProvinceById(x.StateProvinceId); - m.StateProvinceName = (s != null) ? s.Name : "*"; - m.Zip = (!String.IsNullOrEmpty(x.Zip)) ? x.Zip : "*"; - return m; - }) - .ToList(); - var model = new GridModel + var model = new ByRegionTaxRateListModel(); + PrepareModel(model); + + var data = new GridModel { - Data = taxRatesModel, - Total = taxRatesModel.Count + Data = model.TaxRates, + Total = model.TaxRates.Count }; return new JsonResult { - Data = model + Data = data }; } @@ -138,16 +141,21 @@ public ActionResult RateDelete(int id, GridCommand command) return RatesList(command); } - [HttpPost, ActionName("Configure")] - [FormValueRequired("addtaxrate")] - public ActionResult AddTaxRate(ByRegionTaxRateListModel model) + [HttpPost] + public ActionResult Configure(ByRegionTaxRateListModel model) + { + return Configure(); + } + + [HttpPost] + public ActionResult AddTaxByRegionRecord(ByRegionTaxRateListModel model) { if (!ModelState.IsValid) { return Configure(); } - var taxRate = new TaxRate() + var taxRate = new TaxRate { TaxCategoryId = model.AddTaxCategoryId, CountryId = model.AddCountryId, @@ -155,10 +163,11 @@ public ActionResult AddTaxRate(ByRegionTaxRateListModel model) Zip = model.AddZip, Percentage = model.AddPercentage }; - _taxRateService.InsertTaxRate(taxRate); - return Configure(); - } + _taxRateService.InsertTaxRate(taxRate); + NotifySuccess(T("Plugins.Tax.CountryStateZip.AddNewRecord.Success")); + return Json(new { Result = true }); + } } } diff --git a/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs b/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs index 1340565989..3d7f1eb680 100644 --- a/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs +++ b/src/Plugins/SmartStore.Tax/Controllers/TaxFixedRateController.cs @@ -13,7 +13,6 @@ namespace SmartStore.Tax.Controllers { - [AdminAuthorize] public class TaxFixedRateController : PluginControllerBase { diff --git a/src/Plugins/SmartStore.Tax/Description.txt b/src/Plugins/SmartStore.Tax/Description.txt index ec211f9efe..66eb94aab2 100644 --- a/src/Plugins/SmartStore.Tax/Description.txt +++ b/src/Plugins/SmartStore.Tax/Description.txt @@ -2,8 +2,8 @@ Description: Contains default tax providers like FixedRate, ByRegion etc. Group: Tax SystemName: SmartStore.Tax -Version: 2.6.0 -MinAppVersion: 2.5.0 +Version: 3.2.2 +MinAppVersion: 3.2.2 DisplayOrder: 0 FileName: SmartStore.Tax.dll ResourceRootKey: Plugins.SmartStore.Tax diff --git a/src/Plugins/SmartStore.Tax/Localization/resources.de-de.xml b/src/Plugins/SmartStore.Tax/Localization/resources.de-de.xml index 1229c24787..ca9511d7ff 100644 --- a/src/Plugins/SmartStore.Tax/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.Tax/Localization/resources.de-de.xml @@ -1,63 +1,67 @@  - - Standard Steuerberechnung - - - Stellt Standard Steuerberechnungsmodule zur Verfügung - u.A. Fester Steuersatz, Steuersatz nach Region etc. - - - - - Fester Steuersatz - - - - - Steuerklasse - - - Steuersatz - - - + + Standard Steuerberechnung + + + Stellt Standard Steuerberechnungsmodule zur Verfügung - u.A. Fester Steuersatz, Steuersatz nach Region etc. + + + + Fester Steuersatz + + + + + Steuerklasse + + + Steuersatz + + + - - - Steuer nach Region - - - - - Land - - - Bundesland/Region - - - Bei Angabe des Asterisks (*) wird der Steuersatz ungeachtet der Region im gesamten Land angewandt. - - - PLZ - - - Wenn leer, wird der Steuersatz ungeachtet des PLZ-Gebietes im gesamten Land bzw. in der gesamten Region angewandt. - - - Steuerklasse - - - Prozentsatz - - - Steuersatz anlegen - - - Neuen Steuersatz hinzufügen - - - - + + + Steuer nach Region + + + + + Es wurden keine Steuerklassen gefunden. + + + Land + + + Bundesland/Region + + + Bei Angabe des Asterisks (*) wird der Steuersatz ungeachtet der Region im gesamten Land angewandt. + + + PLZ + + + Wenn leer, wird der Steuersatz ungeachtet des PLZ-Gebietes im gesamten Land bzw. in der gesamten Region angewandt. + + + Steuerklasse + + + Prozentsatz + + + Steuersatz anlegen + + + Neuen Steuersatz hinzufügen + + + Der neue Steuersatz wurde erfolgreich zugefügt. + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.Tax/Localization/resources.en-us.xml b/src/Plugins/SmartStore.Tax/Localization/resources.en-us.xml index 86bde57c04..50f40ee983 100644 --- a/src/Plugins/SmartStore.Tax/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.Tax/Localization/resources.en-us.xml @@ -1,64 +1,67 @@  - - Standard Steuerberechnung - - - Stellt Standard Steuerberechnungsmodule zur Verfügung - u.A. Fester Steuersatz, Steuersatz nach Region etc. - + + Standard Steuerberechnung + + + Stellt Standard Steuerberechnungsmodule zur Verfügung - u.A. Fester Steuersatz, Steuersatz nach Region etc. + - - - - Fixed Tax Rate - - - - - Tax category - - - Rate - - - - - - - - Tax By Region - - - - - Country - - - State/Province - - - If an asterisk (*) is selected, this tax rate will apply to all customers from the given country, regardless of the state. - - - Zip - - - Zip/Postal code. If zip is empty, then this tax rate will apply to all customers from the given country or state, regardless of the zip code. - - - Tax category - - - Percentage - - - Add tax rate - - - Adding a new tax rate - - - + + + Fixed Tax Rate + + + + + Tax category + + + Rate + + + + + + Tax By Region + + + + + No tax categories can be found. + + + Country + + + State/Province + + + If an asterisk (*) is selected, this tax rate will apply to all customers from the given country, regardless of the state. + + + Zip + + + Zip/Postal code. If zip is empty, then this tax rate will apply to all customers from the given country or state, regardless of the zip code. + + + Tax category + + + Percentage + + + Add tax rate + + + Adding a new tax rate + + + The new tax rate was successfully added. + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs b/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs index 35cd18a325..d11a39a7f9 100644 --- a/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs +++ b/src/Plugins/SmartStore.Tax/Models/ByRegionTaxRateListModel.cs @@ -5,7 +5,7 @@ namespace SmartStore.Tax.Models { - public class ByRegionTaxRateListModel : ModelBase + public class ByRegionTaxRateListModel : ModelBase { public ByRegionTaxRateListModel() { @@ -17,19 +17,23 @@ public ByRegionTaxRateListModel() [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.Country")] public int AddCountryId { get; set; } + [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.StateProvince")] public int AddStateProvinceId { get; set; } - [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.Zip")] + + [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.Zip")] public string AddZip { get; set; } - [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.TaxCategory")] - public int AddTaxCategoryId { get; set; } + + [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.TaxCategory")] + public int AddTaxCategoryId { get; set; } + [SmartResourceDisplayName("Plugins.Tax.CountryStateZip.Fields.Percentage")] public decimal AddPercentage { get; set; } + public IList AvailableCountries { get; set; } public IList AvailableStates { get; set; } public IList AvailableTaxCategories { get; set; } - public IList TaxRates { get; set; } - + public IList TaxRates { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.Tax/Providers/ByRegionTaxProvider.cs b/src/Plugins/SmartStore.Tax/Providers/ByRegionTaxProvider.cs index 5752d63f6a..bd1765149e 100644 --- a/src/Plugins/SmartStore.Tax/Providers/ByRegionTaxProvider.cs +++ b/src/Plugins/SmartStore.Tax/Providers/ByRegionTaxProvider.cs @@ -1,37 +1,32 @@ -using System.Data.Entity.Migrations; +using System.Linq; using System.Web.Routing; +using SmartStore.Core.Domain.Tax; using SmartStore.Core.Plugins; -using SmartStore.Tax.Data; -using SmartStore.Tax.Data.Migrations; -using SmartStore.Tax.Services; -using SmartStore.Services.Localization; +using SmartStore.Services.Configuration; using SmartStore.Services.Tax; +using SmartStore.Tax.Services; namespace SmartStore.Tax { - [SystemName("Tax.CountryStateZip")] + [SystemName("Tax.CountryStateZip")] [FriendlyName("Tax By Region")] [DisplayOrder(10)] public class ByRegionTaxProvider : ITaxProvider, IConfigurable { private readonly ITaxRateService _taxRateService; - private readonly TaxRateObjectContext _objectContext; - private readonly ILocalizationService _localizationService; + private readonly ISettingService _settingService; + private readonly TaxSettings _taxSettings; - public ByRegionTaxProvider(ITaxRateService taxRateService, - TaxRateObjectContext objectContext, - ILocalizationService localizationService) + public ByRegionTaxProvider( + ITaxRateService taxRateService, + ISettingService settingService, + TaxSettings taxSettings) { - this._taxRateService = taxRateService; - this._objectContext = objectContext; - _localizationService = localizationService; + _taxRateService = taxRateService; + _settingService = settingService; + _taxSettings = taxSettings; } - /// - /// Gets tax rate - /// - /// Tax calculation request - /// Tax public CalculateTaxResult GetTaxRate(CalculateTaxRequest calculateTaxRequest) { var result = new CalculateTaxResult(); @@ -42,12 +37,26 @@ public CalculateTaxResult GetTaxRate(CalculateTaxRequest calculateTaxRequest) return result; } - var taxRates = _taxRateService.GetAllTaxRates(calculateTaxRequest.TaxCategoryId, - calculateTaxRequest.Address.Country != null ? calculateTaxRequest.Address.Country.Id: 0, - calculateTaxRequest.Address.StateProvince != null ? calculateTaxRequest.Address.StateProvince.Id : 0, + if (_taxSettings.EuVatEnabled) + { + if (!(calculateTaxRequest.Address.Country?.SubjectToVat ?? false)) + { + // Fallback to fixed rate (merchant country VAT rate). + result.TaxRate = _settingService.GetSettingByKey($"Tax.TaxProvider.FixedRate.TaxCategoryId{calculateTaxRequest.TaxCategoryId}"); + return result; + } + } + + var taxRates = _taxRateService.GetAllTaxRates( + calculateTaxRequest.TaxCategoryId, + calculateTaxRequest.Address.Country?.Id ?? 0, + calculateTaxRequest.Address.StateProvince?.Id ?? 0, calculateTaxRequest.Address.ZipPostalCode); - if (taxRates.Count > 0) + + if (taxRates.Any()) + { result.TaxRate = taxRates[0].Percentage; + } return result; } @@ -56,8 +65,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "TaxByRegion"; - routeValues = new RouteValueDictionary() { { "area", "SmartStore.Tax" } }; + routeValues = new RouteValueDictionary { { "area", "SmartStore.Tax" } }; } - } } diff --git a/src/Plugins/SmartStore.Tax/Providers/FixedRateTaxProvider.cs b/src/Plugins/SmartStore.Tax/Providers/FixedRateTaxProvider.cs index 55d3ab87b7..4fabac4b04 100644 --- a/src/Plugins/SmartStore.Tax/Providers/FixedRateTaxProvider.cs +++ b/src/Plugins/SmartStore.Tax/Providers/FixedRateTaxProvider.cs @@ -1,37 +1,35 @@ using System.Web.Routing; using SmartStore.Core.Plugins; using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; using SmartStore.Services.Tax; namespace SmartStore.Tax { - [SystemName("Tax.FixedRate")] + [SystemName("Tax.FixedRate")] [FriendlyName("Fixed Tax Rate")] [DisplayOrder(5)] public class FixedRateTaxProvider : ITaxProvider, IConfigurable { private readonly ISettingService _settingService; - private readonly ILocalizationService _localizationService; - public FixedRateTaxProvider(ISettingService settingService, ILocalizationService localizationService) + public FixedRateTaxProvider(ISettingService settingService) { - this._settingService = settingService; - _localizationService = localizationService; + _settingService = settingService; } public CalculateTaxResult GetTaxRate(CalculateTaxRequest calculateTaxRequest) { - var result = new CalculateTaxResult() + var result = new CalculateTaxResult { TaxRate = GetTaxRate(calculateTaxRequest.TaxCategoryId) }; + return result; } protected decimal GetTaxRate(int taxCategoryId) { - decimal rate = this._settingService.GetSettingByKey(string.Format("Tax.TaxProvider.FixedRate.TaxCategoryId{0}", taxCategoryId)); + var rate = _settingService.GetSettingByKey($"Tax.TaxProvider.FixedRate.TaxCategoryId{taxCategoryId}"); return rate; } @@ -39,8 +37,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "TaxFixedRate"; - routeValues = new RouteValueDictionary() { { "area", "SmartStore.Tax" } }; + routeValues = new RouteValueDictionary { { "area", "SmartStore.Tax" } }; } - } } diff --git a/src/Plugins/SmartStore.Tax/Services/TaxRateService.cs b/src/Plugins/SmartStore.Tax/Services/TaxRateService.cs index b928fba74a..f31f2dd7b7 100644 --- a/src/Plugins/SmartStore.Tax/Services/TaxRateService.cs +++ b/src/Plugins/SmartStore.Tax/Services/TaxRateService.cs @@ -1,81 +1,39 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; +using SmartStore.Data.Caching; using SmartStore.Tax.Domain; namespace SmartStore.Tax.Services { - /// - /// Tax rate service - /// public partial class TaxRateService : ITaxRateService { - #region Constants - private const string TAXRATE_ALL_KEY = "SmartStore.taxrate.all"; - private const string TAXRATE_BY_ID_KEY = "SmartStore.taxrate.id-{0}"; - private const string TAXRATE_PATTERN_KEY = "SmartStore.taxrate."; - #endregion - - #region Fields - private readonly IRepository _taxRateRepository; - private readonly IRequestCache _requestCache; - - #endregion - #region Ctor - - public TaxRateService(IRequestCache requestCache, IRepository taxRateRepository) + public TaxRateService(IRepository taxRateRepository) { - this._requestCache = requestCache; this._taxRateRepository = taxRateRepository; } - #endregion - - #region Methods - - /// - /// Deletes a tax rate - /// - /// Tax rate public virtual void DeleteTaxRate(TaxRate taxRate) { if (taxRate == null) throw new ArgumentNullException("taxRate"); _taxRateRepository.Delete(taxRate); - - _requestCache.RemoveByPattern(TAXRATE_PATTERN_KEY); } - /// - /// Gets all tax rates - /// - /// Tax rates public virtual IList GetAllTaxRates() { - string key = TAXRATE_ALL_KEY; - return _requestCache.Get(key, () => - { - var query = from tr in _taxRateRepository.Table - orderby tr.CountryId, tr.StateProvinceId, tr.Zip, tr.TaxCategoryId - select tr; - var taxRates = query.ToList(); - return taxRates; - }); - } + var query = from tr in _taxRateRepository.Table + orderby tr.CountryId, tr.StateProvinceId, tr.Zip, tr.TaxCategoryId + select tr; + + var taxRates = query.ToListCached("db.taxrate.all"); + return taxRates; + } - /// - /// Gets all tax rates - /// - /// The tax category identifier - /// The country identifier - /// The state/province identifier - /// The zip - /// Tax rates public virtual IList GetAllTaxRates(int taxCategoryId, int countryId, int stateProvinceId, string zip) { @@ -113,51 +71,29 @@ public virtual IList GetAllTaxRates(int taxCategoryId, int countryId, return matchedByZip; } - /// - /// Gets a tax rate - /// - /// Tax rate identifier - /// Tax rate public virtual TaxRate GetTaxRateById(int taxRateId) { if (taxRateId == 0) return null; - string key = string.Format(TAXRATE_BY_ID_KEY, taxRateId); - return _requestCache.Get(key, () => - { - var taxRate = _taxRateRepository.GetById(taxRateId); - return taxRate; - }); - } + var taxRate = _taxRateRepository.GetByIdCached(taxRateId, "db.taxrate.id-" + taxRateId); + return taxRate; + } - /// - /// Inserts a tax rate - /// - /// Tax rate public virtual void InsertTaxRate(TaxRate taxRate) { if (taxRate == null) throw new ArgumentNullException("taxRate"); _taxRateRepository.Insert(taxRate); - - _requestCache.RemoveByPattern(TAXRATE_PATTERN_KEY); } - /// - /// Updates the tax rate - /// - /// Tax rate public virtual void UpdateTaxRate(TaxRate taxRate) { if (taxRate == null) throw new ArgumentNullException("taxRate"); _taxRateRepository.Update(taxRate); - - _requestCache.RemoveByPattern(TAXRATE_PATTERN_KEY); } - #endregion } } diff --git a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj index 3f4f325f97..21dbe5eef1 100644 --- a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj +++ b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj @@ -1,5 +1,6 @@  - + + @@ -20,7 +21,7 @@ Properties SmartStore.Tax SmartStore.Tax - v4.5.1 + v4.6.1 512 @@ -43,6 +44,7 @@ + true @@ -82,23 +84,25 @@ MinimumRecommendedRules.ruleset - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll + False - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll + False ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + False @@ -108,37 +112,38 @@ + + - 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 - - 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 - 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 - 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 - 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 - 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 + False - - False ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll + False diff --git a/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml b/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml index f3b670b3b2..24c99334a1 100644 --- a/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml +++ b/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml @@ -1,143 +1,161 @@ @using SmartStore.Web.Framework; @using Telerik.Web.Mvc.UI; -@using System.Linq; -@model SmartStore.Tax.Models.ByRegionTaxRateListModel +@using SmartStore.Tax.Models; +@model ByRegionTaxRateListModel @{ Layout = ""; } - - - - -
    - @(Html.Telerik().Grid(Model.TaxRates) - .Name("Grid") - .DataKeys(keys => keys.Add(x => x.Id).RouteKey("Id")) - .Columns(columns => - { - columns.Bound(x => x.CountryName) - .ReadOnly(); - columns.Bound(x => x.StateProvinceName) - .ReadOnly(); - columns.Bound(x => x.Zip) - .Width(140); - columns.Bound(x => x.TaxCategoryName) - .ReadOnly(); - columns.Bound(x => x.Percentage) - .Width(140) - .Format("{0:0.00}"); - columns.Command(commands => - { - commands.Edit(); - commands.Delete(); - }).Width(180); +
    + @(Html.Telerik().Grid(Model.TaxRates) + .Name("tax-byregion-grid") + .DataKeys(keys => keys.Add(x => x.Id).RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.CountryName) + .Width("30%") + .ReadOnly(); + columns.Bound(x => x.StateProvinceName) + .Width("30%") + .ReadOnly(); + columns.Bound(x => x.Zip) + .Width("10%"); + columns.Bound(x => x.TaxCategoryName) + .Width("10%") + .ReadOnly(); + columns.Bound(x => x.Percentage) + .Width("8%") + .Format("{0:0.00}"); + columns.Command(commands => + { + commands.Edit(); + commands.Delete(); + }).Width("12%") + .HtmlAttributes(new { align = "right", @class = "omega" }); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("RatesList", "TaxByRegion", new RouteValueDictionary { { "area", "SmartStore.Tax" } }) + .Update("RateUpdate", "TaxByRegion", new RouteValueDictionary { { "area", "SmartStore.Tax" } }) + .Delete("RateDelete", "TaxByRegion", new RouteValueDictionary { { "area", "SmartStore.Tax" } }); + }) + .Editable(x => { x.Mode(GridEditMode.InLine); }) + .ToolBar(commands => commands.Template(TaxByRegionGridCommands)) + .EnableCustomBinding(true)) +
    - }) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("RatesList", "TaxByRegion", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }) - .Update("RateUpdate", "TaxByRegion", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }) - .Delete("RateDelete", "TaxByRegion", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }); - }) - .EnableCustomBinding(true)) -
    - -

    +@helper TaxByRegionGridCommands(Grid grid) +{ + + + @T("Plugins.Tax.CountryStateZip.AddRecord.Hint") + +} -@using (Html.BeginForm()) -{ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    @T("Plugins.Tax.CountryStateZip.AddRecord.Hint")
    -
    -
    - @Html.SmartLabelFor(model => model.AddCountryId) - - @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) - @Html.ValidationMessageFor(model => model.AddCountryId) -
    - @Html.SmartLabelFor(model => model.AddStateProvinceId) - - @Html.DropDownListFor(model => model.AddStateProvinceId, Model.AvailableStates) - @Html.ValidationMessageFor(model => model.AddStateProvinceId) -
    - @Html.SmartLabelFor(model => model.AddZip) - - @Html.EditorFor(model => model.AddZip) - @Html.ValidationMessageFor(model => model.AddZip) -
    - @Html.SmartLabelFor(model => model.AddTaxCategoryId) - - @Html.DropDownListFor(model => model.AddTaxCategoryId, Model.AvailableTaxCategories) - @Html.ValidationMessageFor(model => model.AddTaxCategoryId) -
    - @Html.SmartLabelFor(model => model.AddPercentage) - - @Html.EditorFor(model => model.AddPercentage) - @Html.ValidationMessageFor(model => model.AddPercentage) -
    -   - - -
    -} \ No newline at end of file + +@{Html.SmartStore().Window() + .Name("addrecord-window") + .Size(WindowSize.Large) + .Title(T("Plugins.Tax.CountryStateZip.AddRecord.Hint")) + .Content( + @ +
    + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.AddCountryId) + + @Html.DropDownList("AddCountryId", Model.AvailableCountries, + new + { + @class = "form-control country-input country-selector", + data_region_control_selector = "#AddStateProvinceId", + data_states_ajax_url = Url.Action("GetStatesByCountryId", "Country", new RouteValueDictionary { { "area", "Admin" } }), + data_addAsterisk = "true", + data_addEmptyStateIfRequired = "false" + }) + @Html.ValidationMessageFor(model => model.AddCountryId) +
    + @Html.SmartLabelFor(model => model.AddStateProvinceId) + + @Html.DropDownListFor(model => model.AddStateProvinceId, Model.AvailableStates) + @Html.ValidationMessageFor(model => model.AddStateProvinceId) +
    + @Html.SmartLabelFor(model => model.AddZip) + + @Html.EditorFor(model => model.AddZip) + @Html.ValidationMessageFor(model => model.AddZip) +
    + @Html.SmartLabelFor(model => model.AddTaxCategoryId) + + @Html.DropDownListFor(model => model.AddTaxCategoryId, Model.AvailableTaxCategories) + @Html.ValidationMessageFor(model => model.AddTaxCategoryId) +
    + @Html.SmartLabelFor(model => model.AddPercentage) + + @Html.EditorFor(model => model.AddPercentage) + @Html.ValidationMessageFor(model => model.AddPercentage) +
    +
    +
    ) + .FooterContent(@ + + + ) + .Render(); +} diff --git a/src/Plugins/SmartStore.Tax/Views/TaxFixedRate/Configure.cshtml b/src/Plugins/SmartStore.Tax/Views/TaxFixedRate/Configure.cshtml index 76fea4dd94..88fa0fd009 100644 --- a/src/Plugins/SmartStore.Tax/Views/TaxFixedRate/Configure.cshtml +++ b/src/Plugins/SmartStore.Tax/Views/TaxFixedRate/Configure.cshtml @@ -1,41 +1,33 @@ -@{ - Layout = ""; -} - -@model Telerik.Web.Mvc.GridModel +@model Telerik.Web.Mvc.GridModel @using Telerik.Web.Mvc.UI; -@using System.Linq; - - - - - -
    - @(Html.Telerik().Grid(Model.Data) - .Name("Grid") - .DataKeys(keys => keys.Add(x => x.TaxCategoryId).RouteKey("TaxCategoryId")) - .Columns(columns => - { - columns.Bound(x => x.TaxCategoryName) - .ReadOnly(); - columns.Bound(x => x.Rate) - .Width(400) - .Format("{0:0.00}"); - columns.Command(commands => - { - commands.Edit(); - }).Width(120); +@using SmartStore.Tax.Models; +@{ + Layout = ""; +} - }) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("Configure", "TaxFixedRate", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }) - .Update("TaxRateUpdate", "TaxFixedRate", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }); - }) - .EnableCustomBinding(true)) -
    +
    + @(Html.Telerik().Grid(Model.Data) + .Name("Grid") + .DataKeys(keys => keys.Add(x => x.TaxCategoryId).RouteKey("TaxCategoryId")) + .Columns(columns => + { + columns.Bound(x => x.TaxCategoryName) + .Width("70%") + .ReadOnly(); + columns.Bound(x => x.Rate) + .Width("18%") + .Format("{0:0.00}"); + columns.Command(commands => + { + commands.Edit(); + }).Width("12%").HtmlAttributes(new { align = "right", @class = "omega" }); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("Configure", "TaxFixedRate", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }) + .Update("TaxRateUpdate", "TaxFixedRate", new RouteValueDictionary() { { "area", "SmartStore.Tax" } }); + }) + .Editable(x => { x.Mode(GridEditMode.InLine); }) + .EnableCustomBinding(true)) +
    diff --git a/src/Plugins/SmartStore.Tax/Views/Web.config b/src/Plugins/SmartStore.Tax/Views/Web.config index e9d36a3c51..26c177a9ab 100644 --- a/src/Plugins/SmartStore.Tax/Views/Web.config +++ b/src/Plugins/SmartStore.Tax/Views/Web.config @@ -22,7 +22,6 @@ - diff --git a/src/Plugins/SmartStore.Tax/packages.config b/src/Plugins/SmartStore.Tax/packages.config index 7f794ac209..0c1560333c 100644 --- a/src/Plugins/SmartStore.Tax/packages.config +++ b/src/Plugins/SmartStore.Tax/packages.config @@ -1,10 +1,10 @@  - - - - - + + + + + - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.Tax/web.config b/src/Plugins/SmartStore.Tax/web.config index ba87d0f098..522d79a602 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -1,116 +1,148 @@ - + - - + + + - + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Plugins/SmartStore.WebApi/Content/icon.png b/src/Plugins/SmartStore.WebApi/Content/icon.png deleted file mode 100644 index c3785e9a39..0000000000 Binary files a/src/Plugins/SmartStore.WebApi/Content/icon.png and /dev/null differ diff --git a/src/Plugins/SmartStore.WebApi/Controllers/Api/HomeController.cs b/src/Plugins/SmartStore.WebApi/Controllers/Api/HomeController.cs deleted file mode 100644 index 019aa5066e..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/Api/HomeController.cs +++ /dev/null @@ -1,10 +0,0 @@ -using SmartStore.Web.Framework.WebApi.Security; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.Api -{ - [WebApiAuthenticate] - public class HomeController : ApiController - { - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/Api/PaymentsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/Api/PaymentsController.cs deleted file mode 100644 index 5530250f3f..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/Api/PaymentsController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using SmartStore.Core.Plugins; -using SmartStore.Services.Payments; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Linq; -using System.Web.Http; -using System; - -namespace SmartStore.WebApi.Controllers.Api -{ - [WebApiAuthenticate(Permission = "ManagePaymentMethods")] - public class PaymentsController : ApiController - { - private readonly Lazy _providerManager; - - public PaymentsController(Lazy providerManager) - { - this._providerManager = providerManager; - } - - [WebApiQueryable(PagingOptional = true)] - public IQueryable GetMethods() - { - if (!ModelState.IsValid) - throw this.ExceptionInvalidModelState(); - - var query = _providerManager.Value - .GetAllProviders() - .Select(x => x.Metadata) - .AsQueryable(); - - return query; - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs deleted file mode 100644 index d2b92900b9..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using System.Web.Http; -using SmartStore.Core; -using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.IO; -using SmartStore.Services.Catalog; -using SmartStore.Services.DataExchange.Import; -using SmartStore.Services.Media; -using SmartStore.Utilities; -using SmartStore.Utilities.Threading; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using SmartStore.WebApi.Models.Api; - -namespace SmartStore.WebApi.Controllers.Api -{ - /// - public class UploadsController : ApiController - { - private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); - - private readonly Lazy _productService; - private readonly Lazy _pictureService; - private readonly Lazy _importProfileService; - private readonly Lazy _storeContext; - private readonly Lazy _mediaSettings; - - public UploadsController( - Lazy productService, - Lazy pictureService, - Lazy importProfileService, - Lazy storeContext, - Lazy mediaSettings) - { - _productService = productService; - _pictureService = pictureService; - _importProfileService = importProfileService; - _storeContext = storeContext; - _mediaSettings = mediaSettings; - } - - #region Utilities - - private StringContent CloneHeaderContent(string path, MultipartFileData origin) - { - var content = new StringContent(path); - - ContentDispositionHeaderValue disposition; - ContentDispositionHeaderValue.TryParse(origin.Headers.ContentDisposition.ToString(), out disposition); - - content.Headers.ContentDisposition = disposition; - - content.Headers.ContentDisposition.Name = origin.Headers.ContentDisposition.Name.ToUnquoted(); - content.Headers.ContentDisposition.FileName = Path.GetFileName(path); - - content.Headers.ContentType.MediaType = MimeTypes.MapNameToMimeType(path); - - return content; - } - - #endregion - - [HttpPost] - [WebApiAuthenticate(Permission = "ManageCatalog")] - [WebApiQueryable(PagingOptional = true)] - public async Task> ProductImages() - { - if (!Request.Content.IsMimeMultipartContent()) - { - throw this.ExceptionUnsupportedMediaType(); - } - - Product entity = null; - string identifier = null; - string tempDir = FileSystemHelper.TempDir(); - var provider = new MultipartFormDataStreamProvider(tempDir); - - try - { - await Request.Content.ReadAsMultipartAsync(provider); - } - catch (Exception exception) - { - provider.DeleteLocalFiles(); - throw this.ExceptionInternalServerError(exception); - } - - // find product entity - if (provider.FormData.AllKeys.Contains("Id")) - { - identifier = provider.FormData.GetValues("Id").FirstOrDefault(); - entity = _productService.Value.GetProductById(identifier.ToInt()); - } - else if (provider.FormData.AllKeys.Contains("Sku")) - { - identifier = provider.FormData.GetValues("Sku").FirstOrDefault(); - entity = _productService.Value.GetProductBySku(identifier); - } - else if (provider.FormData.AllKeys.Contains("Gtin")) - { - identifier = provider.FormData.GetValues("Gtin").FirstOrDefault(); - entity = _productService.Value.GetProductByGtin(identifier); - } - - if (entity == null) - { - provider.DeleteLocalFiles(); - throw this.ExceptionNotFound(WebApiGlobal.Error.EntityNotFound.FormatInvariant(identifier.NaIfEmpty())); - } - - // process images - var equalPictureId = 0; - var displayOrder = 0; - var result = new List(); - var storeUrl = _storeContext.Value.CurrentStore.Url; - var pictures = entity.ProductPictures.Select(x => x.Picture); - - if (entity.ProductPictures.Count > 0) - displayOrder = entity.ProductPictures.Max(x => x.DisplayOrder); - - foreach (var file in provider.FileData) - { - var image = new UploadImage(file.Headers); - - if (image.FileName.IsEmpty()) - image.FileName = entity.Name; - - var pictureBinary = File.ReadAllBytes(file.LocalFileName); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - pictureBinary = _pictureService.Value.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.Value.FindEqualPicture(pictureBinary, pictures, out equalPictureId); - - if (pictureBinary != null) - { - var seoName = _pictureService.Value.GetPictureSeName(Path.GetFileNameWithoutExtension(image.FileName)); - - var newPicture = _pictureService.Value.InsertPicture(pictureBinary, image.MediaType, seoName, true, false, false); - - if (newPicture != null) - { - _productService.Value.InsertProductPicture(new ProductPicture - { - PictureId = newPicture.Id, - ProductId = entity.Id, - DisplayOrder = ++displayOrder - }); - - image.Inserted = true; - image.Picture = newPicture; - } - } - else - { - image.Exists = true; - image.Picture = pictures.FirstOrDefault(x => x.Id == equalPictureId); - } - - if (image.Picture != null) - { - image.ImageUrl = _pictureService.Value.GetPictureUrl(image.Picture, _mediaSettings.Value.ProductDetailsPictureSize, false, storeUrl); - image.ThumbImageUrl = _pictureService.Value.GetPictureUrl(image.Picture, _mediaSettings.Value.ProductThumbPictureSize, false, storeUrl); - image.FullSizeImageUrl = _pictureService.Value.GetPictureUrl(image.Picture, 0, false, storeUrl); - } - } - - result.Add(image); - } - - provider.DeleteLocalFiles(); - return result.AsQueryable(); - } - - [HttpPost] - [WebApiAuthenticate(Permission = "ManageImports")] - [WebApiQueryable(PagingOptional = true)] - public async Task> ImportFiles() - { - if (!Request.Content.IsMimeMultipartContent()) - { - throw this.ExceptionUnsupportedMediaType(); - } - - ImportProfile profile = null; - string identifier = null; - var tempDir = FileSystemHelper.TempDir(Guid.NewGuid().ToString()); - var provider = new MultipartFormDataStreamProvider(tempDir); - - try - { - await Request.Content.ReadAsMultipartAsync(provider); - } - catch (Exception exception) - { - FileSystemHelper.ClearDirectory(tempDir, true); - throw this.ExceptionInternalServerError(exception); - } - - // find import profile - if (provider.FormData.AllKeys.Contains("Id")) - { - identifier = provider.FormData.GetValues("Id").FirstOrDefault(); - profile = _importProfileService.Value.GetImportProfileById(identifier.ToInt()); - } - else if (provider.FormData.AllKeys.Contains("Name")) - { - identifier = provider.FormData.GetValues("Name").FirstOrDefault(); - profile = _importProfileService.Value.GetImportProfileByName(identifier); - } - - if (profile == null) - { - FileSystemHelper.ClearDirectory(tempDir, true); - throw this.ExceptionNotFound(WebApiGlobal.Error.EntityNotFound.FormatInvariant(identifier.NaIfEmpty())); - } - - var deleteExisting = false; - var result = new List(); - var unzippedFiles = new List(); - var importFolder = profile.GetImportFolder(true, true); - var csvTypes = new string[] { ".csv", ".txt", ".tab" }; - - if (provider.FormData.AllKeys.Contains("deleteExisting")) - { - var strDeleteExisting = provider.FormData.GetValues("deleteExisting").FirstOrDefault(); - deleteExisting = (strDeleteExisting.HasValue() && strDeleteExisting.ToBool()); - } - - // unzip files - foreach (var file in provider.FileData) - { - var import = new UploadImportFile(file.Headers); - - if (import.FileExtension.IsCaseInsensitiveEqual(".zip")) - { - var subDir = Path.Combine(tempDir, Guid.NewGuid().ToString()); - ZipFile.ExtractToDirectory(file.LocalFileName, subDir); - FileSystemHelper.Delete(file.LocalFileName); - - foreach (var unzippedFile in Directory.GetFiles(subDir, "*.*")) - { - var content = CloneHeaderContent(unzippedFile, file); - unzippedFiles.Add(new MultipartFileData(content.Headers, unzippedFile)); - } - } - else - { - unzippedFiles.Add(new MultipartFileData(file.Headers, file.LocalFileName)); - } - } - - // copy files to import folder - if (unzippedFiles.Any()) - { - using (_rwLock.GetWriteLock()) - { - if (deleteExisting) - { - FileSystemHelper.ClearDirectory(importFolder, false); - } - - foreach (var file in unzippedFiles) - { - var import = new UploadImportFile(file.Headers); - var destPath = Path.Combine(importFolder, import.FileName); - - import.Exists = File.Exists(destPath); - - switch (profile.FileType) - { - case ImportFileType.XLSX: - import.IsSupportedByProfile = import.FileExtension.IsCaseInsensitiveEqual(".xlsx"); - break; - case ImportFileType.CSV: - import.IsSupportedByProfile = csvTypes.Contains(import.FileExtension, StringComparer.OrdinalIgnoreCase); - break; - } - - import.Inserted = FileSystemHelper.Copy(file.LocalFileName, destPath); - - result.Add(import); - } - } - } - - FileSystemHelper.ClearDirectory(tempDir, true); - return result.AsQueryable(); - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs deleted file mode 100644 index 3b7e734583..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Linq; -using System.Web.Http; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Events; -using SmartStore.Services.Common; -using SmartStore.Services.Orders; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCustomers")] - public class AddressesController : WebApiEntityController - { - private readonly Lazy> _orderRepository; - private readonly Lazy _eventPublisher; - - public AddressesController( - Lazy> orderRepository, - Lazy eventPublisher) - { - _orderRepository = orderRepository; - _eventPublisher = eventPublisher; - } - - private void PublishOrderUpdated(int addressId) - { - this.ProcessEntity(() => - { - if (addressId != 0) - { - var orders = _orderRepository.Value.TableUntracked - .Where(x => x.BillingAddressId == addressId || x.ShippingAddressId == addressId) - .ToList(); - - foreach (var order in orders) - { - _eventPublisher.Value.PublishOrderUpdated(order); - } - } - return null; - }); - } - - protected override void Insert(Address entity) - { - Service.InsertAddress(entity); - PublishOrderUpdated(entity.Id); - } - protected override void Update(Address entity) - { - Service.UpdateAddress(entity); - PublishOrderUpdated(entity.Id); - } - protected override void Delete(Address entity) - { - int entityId = (entity == null ? 0 : entity.Id); - - Service.DeleteAddress(entity); - PublishOrderUpdated(entityId); - } - - [WebApiQueryable] - public SingleResult
    GetAddress(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetCountry(int key) - { - return GetRelatedEntity(key, x => x.Country); - } - - [WebApiQueryable] - public SingleResult GetStateProvince(int key) - { - return GetRelatedEntity(key, x => x.StateProvince); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs deleted file mode 100644 index 9d252befa9..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Linq; -using System.Web.Http; -using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Services.Catalog; -using SmartStore.Services.Seo; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class CategoriesController : WebApiEntityController - { - private readonly Lazy _urlRecordService; - - public CategoriesController(Lazy urlRecordService) - { - _urlRecordService = urlRecordService; - } - - protected override IQueryable GetEntitySet() - { - var query = - from x in this.Repository.Table - where !x.Deleted - select x; - - return query; - } - protected override void Insert(Category entity) - { - Service.InsertCategory(entity); - - this.ProcessEntity(() => - { - _urlRecordService.Value.SaveSlug(entity, x => x.Name); - return null; - }); - } - protected override void Update(Category entity) - { - Service.UpdateCategory(entity); - - this.ProcessEntity(() => - { - _urlRecordService.Value.SaveSlug(entity, x => x.Name); - return null; - }); - } - protected override void Delete(Category entity) - { - Service.DeleteCategory(entity); - } - - [WebApiQueryable] - public SingleResult GetCategory(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public IQueryable GetAppliedDiscounts(int key) - { - return GetRelatedCollection(key, x => x.AppliedDiscounts); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs deleted file mode 100644 index 15f58522a4..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Directory; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCountries")] - public class CountriesController : WebApiEntityController - { - protected override void Insert(Country entity) - { - Service.InsertCountry(entity); - } - protected override void Update(Country entity) - { - Service.UpdateCountry(entity); - } - protected override void Delete(Country entity) - { - Service.DeleteCountry(entity); - } - - [WebApiQueryable] - public SingleResult GetCountry(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public IQueryable GetStateProvinces(int key) - { - return GetRelatedCollection(key, x => x.StateProvinces); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CurrenciesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CurrenciesController.cs deleted file mode 100644 index be4cff76e4..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CurrenciesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Directory; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCurrencies")] - public class CurrenciesController : WebApiEntityController - { - protected override void Insert(Currency entity) - { - Service.InsertCurrency(entity); - } - protected override void Update(Currency entity) - { - Service.UpdateCurrency(entity); - } - protected override void Delete(Currency entity) - { - Service.DeleteCurrency(entity); - } - - [WebApiQueryable] - public SingleResult GetCurrency(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs deleted file mode 100644 index c811dd60cf..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs +++ /dev/null @@ -1,97 +0,0 @@ -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Customers; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCustomers")] - public class CustomersController : WebApiEntityController - { - protected override IQueryable GetEntitySet() - { - var query = - from x in this.Repository.Table - where !x.Deleted - select x; - - return query; - } - protected override void Insert(Customer entity) - { - Service.InsertCustomer(entity); - } - protected override void Update(Customer entity) - { - Service.UpdateCustomer(entity); - } - protected override void Delete(Customer entity) - { - Service.DeleteCustomer(entity); - } - - [WebApiQueryable] - public SingleResult GetCustomer(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult
    GetBillingAddress(int key) - { - return GetRelatedEntity(key, x => x.BillingAddress); - } - - [WebApiQueryable] - public SingleResult
    GetShippingAddress(int key) - { - return GetRelatedEntity(key, x => x.ShippingAddress); - } - - //public Language GetLanguage(int key) - //{ - // return GetExpandedProperty(key, x => x.Language); - //} - - //public Currency GetCurrency(int key) - //{ - // return GetExpandedProperty(key, x => x.Currency); - //} - - [WebApiQueryable] - public IQueryable GetOrders(int key) - { - return GetRelatedCollection(key, x => x.Orders); - } - - [WebApiQueryable] - public IQueryable GetReturnRequests(int key) - { - return GetRelatedCollection(key, x => x.ReturnRequests); - } - - [WebApiQueryable] - public IQueryable
    GetAddresses(int key) - { - return GetRelatedCollection(key, x => x.Addresses); - } - - // actions - - //[HttpGet, Queryable] - //public IQueryable GetGenericAttributes(int key) - //{ - // var query = GenericAttributes(key, "Customer"); - - // return query; - //} - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/DeliveryTimesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/DeliveryTimesController.cs deleted file mode 100644 index a76a495298..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/DeliveryTimesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Directory; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageDeliveryTimes")] - public class DeliveryTimesController : WebApiEntityController - { - protected override void Insert(DeliveryTime entity) - { - Service.InsertDeliveryTime(entity); - } - protected override void Update(DeliveryTime entity) - { - Service.UpdateDeliveryTime(entity); - } - protected override void Delete(DeliveryTime entity) - { - Service.DeleteDeliveryTime(entity); - } - - [WebApiQueryable] - public SingleResult GetDeliveryTime(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs deleted file mode 100644 index eeb0d3d448..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Services.Discounts; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageDiscounts")] - public class DiscountsController : WebApiEntityController - { - protected override void Insert(Discount entity) - { - Service.InsertDiscount(entity); - } - protected override void Update(Discount entity) - { - Service.UpdateDiscount(entity); - } - protected override void Delete(Discount entity) - { - Service.DeleteDiscount(entity); - } - - [WebApiQueryable] - public SingleResult GetDiscount(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public IQueryable GetAppliedToCategories(int key) - { - return GetRelatedCollection(key, x => x.AppliedToCategories); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/DownloadsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/DownloadsController.cs deleted file mode 100644 index 30ba4c972b..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/DownloadsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Media; -using SmartStore.Services.Media; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class DownloadsController : WebApiEntityController - { - protected override void Insert(Download entity) - { - Service.InsertDownload(entity); - } - protected override void Update(Download entity) - { - Service.UpdateDownload(entity); - } - protected override void Delete(Download entity) - { - Service.DeleteDownload(entity); - } - - [WebApiQueryable] - public SingleResult GetDownload(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/GenericAttributesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/GenericAttributesController.cs deleted file mode 100644 index 20939a3695..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/GenericAttributesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Common; -using SmartStore.Services.Common; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageMaintenance")] - public class GenericAttributesController : WebApiEntityController - { - protected override void Insert(GenericAttribute entity) - { - Service.InsertAttribute(entity); - } - protected override void Update(GenericAttribute entity) - { - Service.UpdateAttribute(entity); - } - protected override void Delete(GenericAttribute entity) - { - Service.DeleteAttribute(entity); - } - - [WebApiQueryable] - public SingleResult GetGenericAttribute(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/LanguagesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/LanguagesController.cs deleted file mode 100644 index 2ca970bebc..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/LanguagesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Localization; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageLanguages")] - public class LanguagesController : WebApiEntityController - { - protected override void Insert(Language entity) - { - Service.InsertLanguage(entity); - } - protected override void Update(Language entity) - { - Service.UpdateLanguage(entity); - } - protected override void Delete(Language entity) - { - Service.DeleteLanguage(entity); - } - - [WebApiQueryable] - public SingleResult GetLanguage(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs deleted file mode 100644 index 9f9c52edca..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Localization; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageLanguages")] - public class LocalizedPropertysController : WebApiEntityController - { - protected override void Insert(LocalizedProperty entity) - { - Service.InsertLocalizedProperty(entity); - } - protected override void Update(LocalizedProperty entity) - { - Service.UpdateLocalizedProperty(entity); - } - protected override void Delete(LocalizedProperty entity) - { - Service.DeleteLocalizedProperty(entity); - } - - [WebApiQueryable] - public SingleResult GetLocalizedProperty(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetLanguage(int key) - { - return GetRelatedEntity(key, x => x.Language); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ManufacturersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ManufacturersController.cs deleted file mode 100644 index e126c6db6c..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ManufacturersController.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Linq; -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Services.Seo; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.Security; -using SmartStore.Web.Framework.WebApi.OData; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ManufacturersController : WebApiEntityController - { - private readonly Lazy _urlRecordService; - - public ManufacturersController(Lazy urlRecordService) - { - _urlRecordService = urlRecordService; - } - - protected override IQueryable GetEntitySet() - { - var query = - from x in this.Repository.Table - where !x.Deleted - select x; - - return query; - } - protected override void Insert(Manufacturer entity) - { - Service.InsertManufacturer(entity); - - this.ProcessEntity(() => - { - _urlRecordService.Value.SaveSlug(entity, x => x.Name); - return null; - }); - } - protected override void Update(Manufacturer entity) - { - Service.UpdateManufacturer(entity); - - this.ProcessEntity(() => - { - _urlRecordService.Value.SaveSlug(entity, x => x.Name); - return null; - }); - } - protected override void Delete(Manufacturer entity) - { - Service.DeleteManufacturer(entity); - } - - [WebApiQueryable] - public SingleResult GetManufacturer(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs deleted file mode 100644 index 2e608f15bc..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs +++ /dev/null @@ -1,56 +0,0 @@ -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Orders; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Linq; -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageOrders")] - public class OrderItemsController : WebApiEntityController - { - protected override IQueryable GetEntitySet() - { - var query = - from x in this.Repository.Table - select x; - - return query; - } - protected override void Insert(OrderItem entity) - { - throw this.ExceptionNotImplemented(); - } - protected override void Update(OrderItem entity) - { - throw this.ExceptionNotImplemented(); - } - protected override void Delete(OrderItem entity) - { - Service.DeleteOrderItem(entity); - } - - [WebApiQueryable] - public SingleResult GetOrderItem(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetOrder(int key) - { - return GetRelatedEntity(key, x => x.Order); - } - - [WebApiQueryable] - public SingleResult GetProduct(int key) - { - return GetRelatedEntity(key, x => x.Product); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs deleted file mode 100644 index 354204fb0e..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Orders; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageOrders")] - public class OrderNotesController : WebApiEntityController - { - protected override void Delete(OrderNote entity) - { - Service.DeleteOrderNote(entity); - } - - [WebApiQueryable] - public SingleResult GetOrderNote(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetOrder(int key) - { - return GetRelatedEntity(key, x => x.Order); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs deleted file mode 100644 index f0f1c0eb9b..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs +++ /dev/null @@ -1,206 +0,0 @@ -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Services.Orders; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; -using System.Web.Http.OData; -using System; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageOrders")] - public class OrdersController : WebApiEntityController - { - private readonly Lazy _orderProcessingService; - - public OrdersController(Lazy orderProcessingService) - { - _orderProcessingService = orderProcessingService; - } - - protected override IQueryable GetEntitySet() - { - var query = - from x in this.Repository.Table - where !x.Deleted - select x; - - return query; - } - protected override void Insert(Order entity) - { - Service.InsertOrder(entity); - } - protected override void Update(Order entity) - { - Service.UpdateOrder(entity); - } - protected override void Delete(Order entity) - { - Service.DeleteOrder(entity); - } - - [WebApiQueryable] - public SingleResult GetOrder(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetCustomer(int key) - { - return GetRelatedEntity(key, x => x.Customer); - } - - [WebApiQueryable] - public SingleResult
    GetBillingAddress(int key) - { - return GetRelatedEntity(key, x => x.BillingAddress); - } - - [WebApiQueryable] - public SingleResult
    GetShippingAddress(int key) - { - return GetRelatedEntity(key, x => x.ShippingAddress); - } - - [WebApiQueryable] - public IQueryable GetOrderNotes(int key) - { - //var entity = GetEntityByKeyNotNull(key); // if ProxyCreationEnabled = true - return GetRelatedCollection(key, x => x.OrderNotes); - } - - [WebApiQueryable] - public IQueryable GetShipments(int key) - { - return GetRelatedCollection(key, x => x.Shipments); - } - - [WebApiQueryable] - public IQueryable GetOrderItems(int key) - { - return GetRelatedCollection(key, x => x.OrderItems); - } - - // actions - - [HttpPost] - public SingleResult PaymentPending(int key) - { - var result = GetSingleResult(key); - var order = GetExpandedEntity(key, result, null); - - this.ProcessEntity(() => - { - order.PaymentStatus = PaymentStatus.Pending; - Service.UpdateOrder(order); - return null; - }); - - return result; - } - - [HttpPost] - public SingleResult PaymentPaid(int key, ODataActionParameters parameters) - { - var result = GetSingleResult(key); - var order = GetExpandedEntity(key, result, null); - - this.ProcessEntity(() => - { - string paymentMethodName = parameters.GetValue("PaymentMethodName"); - - if (paymentMethodName != null) - { - order.PaymentMethodSystemName = paymentMethodName; - Service.UpdateOrder(order); - } - - _orderProcessingService.Value.MarkOrderAsPaid(order); - - return null; - }); - - return result; - } - - [HttpPost] - public SingleResult PaymentRefund(int key, ODataActionParameters parameters) - { - var result = GetSingleResult(key); - var order = GetExpandedEntity(key, result, null); - - this.ProcessEntity(() => - { - bool online = parameters.GetValue("Online"); - - if (online) - { - var errors = _orderProcessingService.Value.Refund(order); - - if (errors.Count > 0) - return errors[0]; - } - else - { - _orderProcessingService.Value.RefundOffline(order); - } - return null; - }); - - return result; - } - - [HttpPost] - public SingleResult Cancel(int key) - { - var result = GetSingleResult(key); - var order = GetExpandedEntity(key, result, "OrderItems, OrderItems.Product"); - - this.ProcessEntity(() => - { - _orderProcessingService.Value.CancelOrder(order, true); - - return null; - }); - - return result; - } - - [HttpPost] - public SingleResult AddShipment(int key, ODataActionParameters parameters) - { - var result = GetSingleResult(key); - var order = GetExpandedEntity(key, result, "OrderItems, OrderItems.Product, Shipments, Shipments.ShipmentItems"); - - this.ProcessEntity(() => - { - if (order.HasItemsToAddToShipment()) - { - var trackingNumber = parameters.GetValue("TrackingNumber"); - - var shipment = _orderProcessingService.Value.AddShipment(order, trackingNumber, null); - - if (shipment != null) - { - if (parameters.ContainsKey("SetAsShipped") && parameters.GetValue("SetAsShipped")) - _orderProcessingService.Value.Ship(shipment, true); - } - } - return null; - }); - - return result; - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/PaymentMethodsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/PaymentMethodsController.cs deleted file mode 100644 index ccc87cfad6..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/PaymentMethodsController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Payments; -using SmartStore.Services.Payments; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManagePaymentMethods")] - public class PaymentMethodsController : WebApiEntityController - { - protected override void Insert(PaymentMethod entity) - { - Service.InsertPaymentMethod(entity); - } - protected override void Update(PaymentMethod entity) - { - Service.UpdatePaymentMethod(entity); - } - protected override void Delete(PaymentMethod entity) - { - Service.DeletePaymentMethod(entity); - } - - [WebApiQueryable] - public SingleResult GetShippingMethod(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs deleted file mode 100644 index 9e23c2959f..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Media; -using SmartStore.Services.Media; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class PicturesController : WebApiEntityController - { - protected override void Insert(Picture entity) - { - throw this.ExceptionNotImplemented(); - } - protected override void Update(Picture entity) - { - throw this.ExceptionNotImplemented(); - } - protected override void Delete(Picture entity) - { - Service.DeletePicture(entity); - } - - [WebApiQueryable] - public SingleResult GetPicture(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public IQueryable GetProductPictures(int key) - { - return GetRelatedCollection(key, x => x.ProductPictures); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductAttributesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductAttributesController.cs deleted file mode 100644 index 9b9d9803d8..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductAttributesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ProductAttributesController : WebApiEntityController - { - protected override void Insert(ProductAttribute entity) - { - Service.InsertProductAttribute(entity); - } - protected override void Update(ProductAttribute entity) - { - Service.UpdateProductAttribute(entity); - } - protected override void Delete(ProductAttribute entity) - { - Service.DeleteProductAttribute(entity); - } - - [WebApiQueryable] - public SingleResult GetProductAttribute(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs deleted file mode 100644 index a24d207821..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs +++ /dev/null @@ -1,46 +0,0 @@ -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ProductBundleItemsController : WebApiEntityController - { - protected override void Insert(ProductBundleItem entity) - { - Service.InsertBundleItem(entity); - } - protected override void Update(ProductBundleItem entity) - { - Service.UpdateBundleItem(entity); - } - protected override void Delete(ProductBundleItem entity) - { - Service.DeleteBundleItem(entity); - } - - [WebApiQueryable] - public SingleResult GetProductBundleItem(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetProduct(int key) - { - return GetRelatedEntity(key, x => x.Product); - } - - [WebApiQueryable] - public SingleResult GetBundleProduct(int key) - { - return GetRelatedEntity(key, x => x.BundleProduct); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs deleted file mode 100644 index 822b0d4d62..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ProductVariantAttributeCombinationsController : WebApiEntityController - { - protected override void Insert(ProductVariantAttributeCombination entity) - { - Service.InsertProductVariantAttributeCombination(entity); - } - protected override void Update(ProductVariantAttributeCombination entity) - { - Service.UpdateProductVariantAttributeCombination(entity); - } - protected override void Delete(ProductVariantAttributeCombination entity) - { - Service.DeleteProductVariantAttributeCombination(entity); - } - - [WebApiQueryable] - public SingleResult GetProductVariantAttributeCombination(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetDeliveryTime(int key) - { - return GetRelatedEntity(key, x => x.DeliveryTime); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs deleted file mode 100644 index ad1f54efe7..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ProductVariantAttributeValuesController : WebApiEntityController - { - protected override void Insert(ProductVariantAttributeValue entity) - { - Service.InsertProductVariantAttributeValue(entity); - } - protected override void Update(ProductVariantAttributeValue entity) - { - Service.UpdateProductVariantAttributeValue(entity); - } - protected override void Delete(ProductVariantAttributeValue entity) - { - Service.DeleteProductVariantAttributeValue(entity); - } - - [WebApiQueryable] - public SingleResult GetProductVariantAttributeValue(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetProductVariantAttribute(int key) - { - return GetRelatedEntity(key, x => x.ProductVariantAttribute); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs deleted file mode 100644 index 487f636560..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ProductVariantAttributesController : WebApiEntityController - { - protected override void Insert(ProductVariantAttribute entity) - { - Service.InsertProductVariantAttribute(entity); - } - protected override void Update(ProductVariantAttribute entity) - { - Service.UpdateProductVariantAttribute(entity); - } - protected override void Delete(ProductVariantAttribute entity) - { - Service.DeleteProductVariantAttribute(entity); - } - - [WebApiQueryable] - public SingleResult GetProductVariantAttribute(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetProductAttribute(int key) - { - return GetRelatedEntity(key, x => x.ProductAttribute); - } - - [WebApiQueryable] - public IQueryable GetProductVariantAttributeValues(int key) - { - return GetRelatedCollection(key, x => x.ProductVariantAttributeValues); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs deleted file mode 100644 index df8dfd389d..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs +++ /dev/null @@ -1,396 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Web.Http; -using System.Web.Http.OData; -using System.Data.Entity; -using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Media; -using SmartStore.Services.Catalog; -using SmartStore.Services.Seo; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using SmartStore.WebApi.Services; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class ProductsController : WebApiEntityController - { - private readonly Lazy _workContext; - private readonly Lazy _priceCalculationService; - private readonly Lazy _urlRecordService; - private readonly Lazy _productAttributeService; - private readonly Lazy _categoryService; - private readonly Lazy _manufacturerService; - - public ProductsController( - Lazy workContext, - Lazy priceCalculationService, - Lazy urlRecordService, - Lazy productAttributeService, - Lazy categoryService, - Lazy manufacturerService) - { - _workContext = workContext; - _priceCalculationService = priceCalculationService; - _urlRecordService = urlRecordService; - _productAttributeService = productAttributeService; - _categoryService = categoryService; - _manufacturerService = manufacturerService; - } - - protected override IQueryable GetEntitySet() - { - var query = - from x in this.Repository.Table - where !x.Deleted - select x; - - return query; - } - protected override void Insert(Product entity) - { - Service.InsertProduct(entity); - - this.ProcessEntity(() => - { - _urlRecordService.Value.SaveSlug(entity, x => x.Name); - return null; - }); - } - protected override void Update(Product entity) - { - Service.UpdateProduct(entity); - - this.ProcessEntity(() => - { - _urlRecordService.Value.SaveSlug(entity, x => x.Name); - return null; - }); - } - protected override void Delete(Product entity) - { - Service.DeleteProduct(entity); - } - - [WebApiQueryable] - public SingleResult GetProduct(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - public HttpResponseMessage NavigationProductCategories(int key, int relatedKey) - { - var productCategories = _categoryService.Value.GetProductCategoriesByProductId(key, true); - var productCategory = productCategories.FirstOrDefault(x => x.CategoryId == relatedKey); - - if (Request.Method == HttpMethod.Post) - { - if (productCategory == null) - { - productCategory = new ProductCategory { ProductId = key, CategoryId = relatedKey }; - - _categoryService.Value.InsertProductCategory(productCategory); - - return Request.CreateResponse(HttpStatusCode.Created, productCategory); - } - } - else if (Request.Method == HttpMethod.Delete) - { - if (productCategory != null) - _categoryService.Value.DeleteProductCategory(productCategory); - - return Request.CreateResponse(HttpStatusCode.NoContent); - } - - return Request.CreateResponseForEntity(productCategory, relatedKey); - } - - public HttpResponseMessage NavigationProductManufacturers(int key, int relatedKey) - { - var productManufacturers = _manufacturerService.Value.GetProductManufacturersByProductId(key, true); - var productManufacturer = productManufacturers.FirstOrDefault(x => x.ManufacturerId == relatedKey); - - if (Request.Method == HttpMethod.Post) - { - if (productManufacturer == null) - { - productManufacturer = new ProductManufacturer { ProductId = key, ManufacturerId = relatedKey }; - - _manufacturerService.Value.InsertProductManufacturer(productManufacturer); - - return Request.CreateResponse(HttpStatusCode.Created, productManufacturer); - } - } - else if (Request.Method == HttpMethod.Delete) - { - if (productManufacturer != null) - _manufacturerService.Value.DeleteProductManufacturer(productManufacturer); - - return Request.CreateResponse(HttpStatusCode.NoContent); - } - - return Request.CreateResponseForEntity(productManufacturer, relatedKey); - } - - [WebApiQueryable] - public SingleResult GetDeliveryTime(int key) - { - return GetRelatedEntity(key, x => x.DeliveryTime); - } - - [WebApiQueryable] - public SingleResult GetQuantityUnit(int key) - { - return GetRelatedEntity(key, x => x.QuantityUnit); - } - - [WebApiQueryable] - public SingleResult GetSampleDownload(int key) - { - return GetRelatedEntity(key, x => x.SampleDownload); - } - - [WebApiQueryable] - public IQueryable GetProductCategories(int key) - { - return GetRelatedCollection(key, x => x.ProductCategories); - } - - [WebApiQueryable] - public IQueryable GetProductManufacturers(int key) - { - return GetRelatedCollection(key, x => x.ProductManufacturers); - } - - [WebApiQueryable] - public IQueryable GetProductPictures(int key) - { - return GetRelatedCollection(key, x => x.ProductPictures); - } - - [WebApiQueryable] - public IQueryable GetProductSpecificationAttributes(int key) - { - return GetRelatedCollection(key, x => x.ProductSpecificationAttributes); - } - - [WebApiQueryable] - public IQueryable GetProductTags(int key) - { - return GetRelatedCollection(key, x => x.ProductTags); - } - - [WebApiQueryable] - public IQueryable GetTierPrices(int key) - { - return GetRelatedCollection(key, x => x.TierPrices); - } - - [WebApiQueryable] - public IQueryable GetAppliedDiscounts(int key) - { - return GetRelatedCollection(key, x => x.AppliedDiscounts); - } - - [WebApiQueryable] - public IQueryable GetProductVariantAttributes(int key) - { - return GetRelatedCollection(key, x => x.ProductVariantAttributes); - } - - [WebApiQueryable] - public IQueryable GetProductVariantAttributeCombinations(int key) - { - return GetRelatedCollection(key, x => x.ProductVariantAttributeCombinations); - } - - [WebApiQueryable] - public IQueryable GetProductBundleItems(int key) - { - return GetRelatedCollection(key, x => x.ProductBundleItems); - } - - // actions - - private decimal? CalculatePrice(int key, bool lowestPrice) - { - string requiredProperties = "TierPrices, AppliedDiscounts, ProductBundleItems"; - var entity = GetExpandedEntity(key, requiredProperties); - decimal? result = null; - - this.ProcessEntity(() => - { - if (lowestPrice) - { - if (entity.ProductType == ProductType.GroupedProduct) - { - var searchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.Position, - Query = this.GetExpandedEntitySet(requiredProperties), - ParentGroupedProductId = entity.Id, - PageSize = int.MaxValue, - VisibleIndividuallyOnly = false - }; - - Product lowestPriceProduct; - var associatedProducts = Service.PrepareProductSearchQuery(searchContext); - - result = _priceCalculationService.Value.GetLowestPrice(entity, null, associatedProducts, out lowestPriceProduct); - } - else - { - bool displayFromMessage; - result = _priceCalculationService.Value.GetLowestPrice(entity, null, out displayFromMessage); - } - } - else - { - result = _priceCalculationService.Value.GetPreselectedPrice(entity, null); - } - return null; - }); - return result; - } - - [HttpPost] - public decimal? FinalPrice(int key) - { - return CalculatePrice(key, false); - } - - [HttpPost] - public decimal? LowestPrice(int key) - { - return CalculatePrice(key, true); - } - - [HttpPost, WebApiQueryable(PagingOptional = true)] - public IQueryable CreateAttributeCombinations(int key) - { - var entity = GetEntityByKeyNotNull(key); - - this.ProcessEntity(() => - { - _productAttributeService.Value.CreateAllProductVariantAttributeCombinations(entity); - return null; - }); - - return entity.ProductVariantAttributeCombinations.AsQueryable(); - } - - [HttpPost, WebApiQueryable(PagingOptional = true)] - public IQueryable ManageAttributes(int key, ODataActionParameters parameters) - { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributes); - var result = new List(); - - this.ProcessEntity(() => - { - bool synchronize = parameters.GetValue("Synchronize"); - var data = (parameters["Attributes"] as IEnumerable).Where(x => x.Name.HasValue()).ToList(); - - var allAttributes = _productAttributeService.Value.GetAllProductAttributes(); - - foreach (var srcAttr in data) - { - var productAttribute = allAttributes.FirstOrDefault(x => x.Name.IsCaseInsensitiveEqual(srcAttr.Name)); - - if (productAttribute == null) - { - productAttribute = new ProductAttribute() { Name = srcAttr.Name }; - _productAttributeService.Value.InsertProductAttribute(productAttribute); - } - - var attribute = entity.ProductVariantAttributes.FirstOrDefault(x => x.ProductAttribute.Name.IsCaseInsensitiveEqual(srcAttr.Name)); - - if (attribute == null) - { - attribute = new ProductVariantAttribute() - { - ProductId = entity.Id, - ProductAttributeId = productAttribute.Id, - AttributeControlTypeId = srcAttr.ControlTypeId, - DisplayOrder = entity.ProductVariantAttributes.OrderByDescending(x => x.DisplayOrder).Select(x => x.DisplayOrder).FirstOrDefault() + 1, - IsRequired = srcAttr.IsRequired - }; - - entity.ProductVariantAttributes.Add(attribute); - Service.UpdateProduct(entity); - } - else if (synchronize) - { - if (srcAttr.Values.Count <= 0) - { - _productAttributeService.Value.DeleteProductVariantAttribute(attribute); - } - else - { - attribute.AttributeControlTypeId = srcAttr.ControlTypeId; - attribute.IsRequired = srcAttr.IsRequired; - - Service.UpdateProduct(entity); - } - } - - int maxDisplayOrder = attribute.ProductVariantAttributeValues.OrderByDescending(x => x.DisplayOrder).Select(x => x.DisplayOrder).FirstOrDefault(); - - foreach (var srcVal in srcAttr.Values.Where(x => x.Name.HasValue())) - { - var value = attribute.ProductVariantAttributeValues.FirstOrDefault(x => x.Name.IsCaseInsensitiveEqual(srcVal.Name)); - - if (value == null) - { - value = new ProductVariantAttributeValue() - { - ProductVariantAttributeId = attribute.Id, - Name = srcVal.Name, - Alias = srcVal.Alias, - ColorSquaresRgb = srcVal.ColorSquaresRgb, - PriceAdjustment = srcVal.PriceAdjustment, - WeightAdjustment = srcVal.WeightAdjustment, - IsPreSelected = srcVal.IsPreSelected, - DisplayOrder = ++maxDisplayOrder - }; - - attribute.ProductVariantAttributeValues.Add(value); - Service.UpdateProduct(entity); - } - else if (synchronize) - { - value.Alias = srcVal.Alias; - value.ColorSquaresRgb = srcVal.ColorSquaresRgb; - value.PriceAdjustment = srcVal.PriceAdjustment; - value.WeightAdjustment = srcVal.WeightAdjustment; - value.IsPreSelected = srcVal.IsPreSelected; - - Service.UpdateProduct(entity); - } - } - - if (synchronize) - { - foreach (var dstVal in attribute.ProductVariantAttributeValues.ToList()) - { - if (!srcAttr.Values.Any(x => x.Name.IsCaseInsensitiveEqual(dstVal.Name))) - _productAttributeService.Value.DeleteProductVariantAttributeValue(dstVal); - } - } - } - return null; - }); - - return entity.ProductVariantAttributes.AsQueryable(); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/QuantityUnitsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/QuantityUnitsController.cs deleted file mode 100644 index 11868da433..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/QuantityUnitsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Directory; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageMeasures")] - public class QuantityUnitsController : WebApiEntityController - { - protected override void Insert(QuantityUnit entity) - { - Service.InsertQuantityUnit(entity); - } - protected override void Update(QuantityUnit entity) - { - Service.UpdateQuantityUnit(entity); - } - protected override void Delete(QuantityUnit entity) - { - Service.DeleteQuantityUnit(entity); - } - - [WebApiQueryable] - public SingleResult GetQuantityUnit(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/RelatedProductsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/RelatedProductsController.cs deleted file mode 100644 index d625c12022..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/RelatedProductsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class RelatedProductsController : WebApiEntityController - { - protected override void Insert(RelatedProduct entity) - { - Service.InsertRelatedProduct(entity); - } - protected override void Update(RelatedProduct entity) - { - Service.UpdateRelatedProduct(entity); - } - protected override void Delete(RelatedProduct entity) - { - Service.DeleteRelatedProduct(entity); - } - - [WebApiQueryable] - public SingleResult GetRelatedProduct(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs deleted file mode 100644 index fbcdd389b0..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Orders; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageReturnRequests")] - public class ReturnRequestsController : WebApiEntityController - { - protected override void Delete(ReturnRequest entity) - { - Service.DeleteReturnRequest(entity); - } - - [WebApiQueryable] - public SingleResult GetReturnRequest(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetCustomer(int key) - { - return GetRelatedEntity(key, x => x.Customer); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SettingsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SettingsController.cs deleted file mode 100644 index 0faf7e6fce..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SettingsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Configuration; -using SmartStore.Services.Configuration; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageSettings")] - public class SettingsController : WebApiEntityController - { - protected override void Insert(Setting entity) - { - Service.InsertSetting(entity); - } - protected override void Update(Setting entity) - { - Service.UpdateSetting(entity); - } - protected override void Delete(Setting entity) - { - Service.DeleteSetting(entity); - } - - [WebApiQueryable] - public SingleResult GetSetting(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs deleted file mode 100644 index 47fdf9982a..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Services.Shipping; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageOrders")] - public class ShipmentItemsController : WebApiEntityController - { - protected override void Insert(ShipmentItem entity) - { - Service.InsertShipmentItem(entity); - } - protected override void Update(ShipmentItem entity) - { - Service.UpdateShipmentItem(entity); - } - protected override void Delete(ShipmentItem entity) - { - Service.DeleteShipmentItem(entity); - } - - [WebApiQueryable] - public SingleResult GetShipmentItem(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentsController.cs deleted file mode 100644 index 6b34c3821d..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Services.Shipping; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageOrders")] - public class ShipmentsController : WebApiEntityController - { - protected override void Insert(Shipment entity) - { - Service.InsertShipment(entity); - } - protected override void Update(Shipment entity) - { - Service.UpdateShipment(entity); - } - protected override void Delete(Shipment entity) - { - Service.DeleteShipment(entity); - } - - [WebApiQueryable] - public SingleResult GetShipment(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs deleted file mode 100644 index ab13899ff9..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Services.Shipping; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageShippingSettings")] - public class ShippingMethodsController : WebApiEntityController - { - protected override void Insert(ShippingMethod entity) - { - Service.InsertShippingMethod(entity); - } - protected override void Update(ShippingMethod entity) - { - Service.UpdateShippingMethod(entity); - } - protected override void Delete(ShippingMethod entity) - { - Service.DeleteShippingMethod(entity); - } - - [WebApiQueryable] - public SingleResult GetShippingMethod(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public IQueryable GetRestrictedCountries(int key) - { - return GetRelatedCollection(key, x => x.RestrictedCountries); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs deleted file mode 100644 index e377bb9fff..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs +++ /dev/null @@ -1,48 +0,0 @@ -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class SpecificationAttributeOptionsController : WebApiEntityController - { - protected override void Insert(SpecificationAttributeOption entity) - { - Service.InsertSpecificationAttributeOption(entity); - } - protected override void Update(SpecificationAttributeOption entity) - { - Service.UpdateSpecificationAttributeOption(entity); - } - protected override void Delete(SpecificationAttributeOption entity) - { - Service.DeleteSpecificationAttributeOption(entity); - } - - [WebApiQueryable] - public SingleResult GetSpecificationAttributeOption(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetSpecificationAttribute(int key) - { - return GetRelatedEntity(key, x => x.SpecificationAttribute); - } - - [WebApiQueryable] - public IQueryable GetProductSpecificationAttributes(int key) - { - return GetRelatedCollection(key, x => x.ProductSpecificationAttributes); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs deleted file mode 100644 index 3990c280d8..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs +++ /dev/null @@ -1,42 +0,0 @@ -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class SpecificationAttributesController : WebApiEntityController - { - protected override void Insert(SpecificationAttribute entity) - { - Service.InsertSpecificationAttribute(entity); - } - protected override void Update(SpecificationAttribute entity) - { - Service.UpdateSpecificationAttribute(entity); - } - protected override void Delete(SpecificationAttribute entity) - { - Service.DeleteSpecificationAttribute(entity); - } - - [WebApiQueryable] - public SingleResult GetSpecificationAttribute(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public IQueryable GetSpecificationAttributeOptions(int key) - { - return GetRelatedCollection(key, x => x.SpecificationAttributeOptions); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs deleted file mode 100644 index f01dccfec4..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Directory; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCountries")] - public class StateProvincesController : WebApiEntityController - { - protected override void Insert(StateProvince entity) - { - Service.InsertStateProvince(entity); - } - protected override void Update(StateProvince entity) - { - Service.UpdateStateProvince(entity); - } - protected override void Delete(StateProvince entity) - { - Service.DeleteStateProvince(entity); - } - - [WebApiQueryable] - public SingleResult GetStateProvince(int key) - { - return GetSingleResult(key); - } - - // navigation properties - - [WebApiQueryable] - public SingleResult GetCountry(int key) - { - return GetRelatedEntity(key, x => x.Country); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/StoreMappingsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/StoreMappingsController.cs deleted file mode 100644 index eab3b187b1..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/StoreMappingsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Stores; -using SmartStore.Services.Stores; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageStores")] - public class StoreMappingsController : WebApiEntityController - { - protected override void Insert(StoreMapping entity) - { - Service.InsertStoreMapping(entity); - } - protected override void Update(StoreMapping entity) - { - Service.UpdateStoreMapping(entity); - } - protected override void Delete(StoreMapping entity) - { - Service.DeleteStoreMapping(entity); - } - - [WebApiQueryable] - public SingleResult GetStoreMapping(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/StoresController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/StoresController.cs deleted file mode 100644 index b455c76487..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/StoresController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Stores; -using SmartStore.Services.Stores; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageStores")] - public class StoresController : WebApiEntityController - { - protected override void Insert(Store entity) - { - Service.InsertStore(entity); - } - protected override void Update(Store entity) - { - Service.UpdateStore(entity); - } - protected override void Delete(Store entity) - { - Service.DeleteStore(entity); - } - - [WebApiQueryable] - public SingleResult GetStore(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SyncMappingsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SyncMappingsController.cs deleted file mode 100644 index bccd10dcaa..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SyncMappingsController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Stores; -using SmartStore.Services.DataExchange; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageMaintenance")] // TODO: ManageMaintenance... really? - public class SyncMappingsController : WebApiEntityController - { - protected override void Insert(SyncMapping entity) - { - Service.InsertSyncMapping(entity); - } - protected override void Update(SyncMapping entity) - { - Service.UpdateSyncMapping(entity); - } - protected override void Delete(SyncMapping entity) - { - Service.DeleteSyncMapping(entity); - } - - [WebApiQueryable] - public SingleResult GetSyncMapping(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/TierPricesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/TierPricesController.cs deleted file mode 100644 index bceb65fc47..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/TierPricesController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Catalog; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageCatalog")] - public class TierPricesController : WebApiEntityController - { - protected override void Insert(TierPrice entity) - { - Service.InsertTierPrice(entity); - } - protected override void Update(TierPrice entity) - { - Service.UpdateTierPrice(entity); - } - protected override void Delete(TierPrice entity) - { - Service.DeleteTierPrice(entity); - } - - [WebApiQueryable] - public SingleResult GetTierPrice(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/UrlRecordsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/UrlRecordsController.cs deleted file mode 100644 index c2164d7a2e..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/UrlRecordsController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Web.Http; -using SmartStore.Core.Domain.Seo; -using SmartStore.Services.Seo; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.OData; -using SmartStore.Web.Framework.WebApi.Security; - -namespace SmartStore.WebApi.Controllers.OData -{ - [WebApiAuthenticate(Permission = "ManageMaintenance")] - public class UrlRecordsController : WebApiEntityController - { - protected override void Insert(UrlRecord entity) - { - throw this.ExceptionForbidden(); - } - protected override void Update(UrlRecord entity) - { - throw this.ExceptionForbidden(); - } - protected override void Delete(UrlRecord entity) - { - throw this.ExceptionForbidden(); - } - - [WebApiQueryable] - public SingleResult GetUrlRecord(int key) - { - return GetSingleResult(key); - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs b/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs deleted file mode 100644 index 4fb254bbff..0000000000 --- a/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web.Mvc; -using SmartStore.Core.Domain.Common; -using SmartStore.Services; -using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Filters; -using SmartStore.Web.Framework.Security; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.Caching; -using SmartStore.WebApi.Models; -using SmartStore.WebApi.Security; -using SmartStore.WebApi.Services; -using Telerik.Web.Mvc; - -namespace SmartStore.WebApi.Controllers -{ - [AdminAuthorize] - public class WebApiController : PluginControllerBase - { - private readonly WebApiSettings _webApiSettings; - private readonly IWebApiPluginService _webApiPluginService; - private readonly AdminAreaSettings _adminAreaSettings; - private readonly ICommonServices _services; - - public WebApiController( - WebApiSettings settings, - IWebApiPluginService webApiPluginService, - AdminAreaSettings adminAreaSettings, - ICommonServices services) - { - _webApiSettings = settings; - _webApiPluginService = webApiPluginService; - _adminAreaSettings = adminAreaSettings; - _services = services; - } - - private bool HasPermission(bool notify = true) - { - bool hasPermission = _services.Permissions.Authorize(WebApiPermissionProvider.ManageWebApi); - - if (notify && !hasPermission) - NotifyError(_services.Localization.GetResource("Admin.AccessDenied.Description")); - - return hasPermission; - } - - private void AddButtonText() - { - ViewData["ButtonTextEnable"] = _services.Localization.GetResource("Plugins.Api.WebApi.Activate"); - ViewData["ButtonTextDisable"] = _services.Localization.GetResource("Plugins.Api.WebApi.Deactivate"); - ViewData["ButtonTextRemoveKeys"] = _services.Localization.GetResource("Plugins.Api.WebApi.RemoveKeys"); - ViewData["ButtonTextCreateKeys"] = _services.Localization.GetResource("Plugins.Api.WebApi.CreateKeys"); - } - - public ActionResult Configure() - { - if (!HasPermission(false)) - return AccessDeniedPartialView(); - - var model = new WebApiConfigModel(); - model.Copy(_webApiSettings, true); - - var odataUri = new Uri(Request.Url, - WebApiGlobal.MostRecentOdataPath.StartsWith("/") ? WebApiGlobal.MostRecentOdataPath : "/" + WebApiGlobal.MostRecentOdataPath - ); - - model.ApiOdataUrl = odataUri.AbsoluteUri.EnsureEndsWith("/"); - model.ApiOdataMetadataUrl = model.ApiOdataUrl + "$metadata"; - - model.GridPageSize = _adminAreaSettings.GridPageSize; - - AddButtonText(); - - return View(model); - } - - [HttpPost, ActionName("Configure")] - [FormValueRequired("savegeneralsettings")] - public ActionResult SaveGeneralSettings(WebApiConfigModel model) - { - if (!ModelState.IsValid) - return Configure(); - - if (!HasPermission(false)) - return AccessDeniedPartialView(); - - model.Copy(_webApiSettings, false); - _services.Settings.SaveSetting(_webApiSettings); - - WebApiCachingControllingData.Remove(); - - return Configure(); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult GridUserData(GridCommand command) - { - if (!HasPermission()) - return new JsonResult { Data = new GridModel { Data = new List() } }; - - var model = _webApiPluginService.GetGridModel(command.Page - 1, command.PageSize); - - AddButtonText(); - - return new JsonResult { Data = model }; - } - - [HttpPost] - public void ApiButtonCreateKeys(int customerId) - { - if (HasPermission()) - _webApiPluginService.CreateKeys(customerId); - } - - [HttpPost] - public void ApiButtonRemoveKeys(int customerId) - { - if (HasPermission()) - _webApiPluginService.RemoveKeys(customerId); - } - - [HttpPost] - public void ApiButtonEnable(int customerId) - { - if (HasPermission()) - _webApiPluginService.EnableOrDisableUser(customerId, true); - } - - [HttpPost] - public void ApiButtonDisable(int customerId) - { - if (HasPermission()) - _webApiPluginService.EnableOrDisableUser(customerId, false); - } - - } -} diff --git a/src/Plugins/SmartStore.WebApi/DependencyRegistrar.cs b/src/Plugins/SmartStore.WebApi/DependencyRegistrar.cs deleted file mode 100644 index b9316aae52..0000000000 --- a/src/Plugins/SmartStore.WebApi/DependencyRegistrar.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Autofac; -using Autofac.Integration.Mvc; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Infrastructure.DependencyManagement; -using SmartStore.WebApi.Services; - -namespace SmartStore.WebApi -{ - public class DependencyRegistrar : IDependencyRegistrar - { - public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) - { - builder.RegisterType().As().InstancePerRequest(); - } - - public int Order - { - get { return 1; } - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt deleted file mode 100644 index 646ac2fd0d..0000000000 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ /dev/null @@ -1,10 +0,0 @@ -FriendlyName: SmartStore.NET Web Api -SystemName: SmartStore.WebApi -Version: 2.6.0 -Group: Api -MinAppVersion: 2.5.0 -Author: SmartStore AG -DisplayOrder: 1 -FileName: SmartStore.WebApi.dll -ResourceRootKey: Plugins.Api.WebApi -Url: http://community.smartstore.com/marketplace/file/27-smartstorenet-web-api/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs deleted file mode 100644 index 3e754343db..0000000000 --- a/src/Plugins/SmartStore.WebApi/Extensions/MiscExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Text; -using System.Web.Mvc; - -namespace SmartStore.WebApi -{ - public static class PublicMiscExtensions - { - public static string ApiGridButtons(this HtmlHelper helper) - { - var sb = new StringBuilder(); - - sb.Append("
    \">"); - - sb.AppendFormat("", - helper.ViewData["ButtonTextRemoveKeys"]); - - sb.AppendFormat("", - helper.ViewData["ButtonTextCreateKeys"]); - - sb.AppendFormat("", - helper.ViewData["ButtonTextEnable"]); - - sb.AppendFormat("", - helper.ViewData["ButtonTextDisable"]); - - sb.Append("
    "); - - return sb.ToString(); - } - } - - internal static class MiscExtensions - { - public static string ToUnquoted(this string value) - { - if (value.HasValue() && value.Length > 1) - { - if ((value.StartsWith("\"") && value.EndsWith("\"")) || (value.StartsWith("'") && value.EndsWith("'"))) - return value.Substring(1, value.Length - 2); - } - return value; - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml b/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml deleted file mode 100644 index 650e50d1ab..0000000000 --- a/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - Web-API - - - - -
  • Nur registrierte Kunden (siehe Liste unten) mit einem öffentlichen und einem geheimen Schlüssel zur API haben Zugriff.
  • -
  • Sie können den Zugriff für einen Kunden durch Löschen der Schlüssel endgültig oder durch Deaktivieren zeitweise unterbinden.
  • -
  • Die Rechte der jeweiligen Kundengruppe gelten auch bei Zugriffen über die API.
  • -]]> -
    -
    - - OData API URL - - - Die URL der OData Web API. - - - OData Metadaten URL - - - Die URL der OData Metadaten. Sie beschreiben das gesamte Entitäten-Daten-Modell (EDM), auf dem die Web API basiert. - - - Anfragezeitfenster (Minuten) - - - Aus Sicherheitsgründen darf die Zeit, zu der die Anfrage an die API gesendet wurde, nicht zu weit von der aktuellen Server-Zeit abweichen. Mit dieser Einstellung legen Sie dieses Zeitfenster in Minuten fest. - - - Unautorisierte Zugriffe speichern - - - Jeder unautorisierte Zugriff wird in der Ereignisliste gespeichert. - - - Keine Zeitstempelprüfung - - - Legt fest, ob geprüft werden soll, ob der Zeitstempel der letzten Anfrage kleiner gleich der der aktuellen ist. Verhindert das Auftreten von HmacResult.TimestampOlderThanLastRequest. - - - Authentifizierung ohne MD5-Hash erlauben - - - Legt fest, ob Authentifizierungen ohne MD5 Inhalts-Hash erlaubt sind. - - - Öffentlicher Schlüssel - - - Geheimer Schlüssel - - - Letzter Zugriff - - - Für API zugelassen - - - Zulassen - - - Nicht zulassen - - - Schlüssel löschen - - - Schlüssel erstellen - -
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml b/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml deleted file mode 100644 index 7aafa97635..0000000000 --- a/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - Web-API - - - - -
  • Only registered customers (see list below) with a public and a secret key for the API have access to it.
  • -
  • You can remove the access for a customer permanently by deleting the keys or temporary by deactivating them.
  • -
  • The rights of the respective customer group are valid for the API too.
  • -]]> -
    -
    - - OData API URL - - - URL of the OData Web API. - - - OData metadata URL - - - URL of the OData metadata. It describes the complete entity data model (EDM) the Web API is based on. - - - Request time window (minutes) - - - For security reasons should the time at which the request was sent to the API differ not too far from the current server time. With this setting, you set this time window in minutes. - - - Log unauthorized accesses - - - Each unauthorized access will be saved in the log list. - - - No timestamp validation - - - Specifies whether to validate that the timestamp of the previous request is less or equal than the one of the current. Prevents the occurrence of HmacResult.TimestampOlderThanLastRequest. - - - Allow authentification without MD5 hash - - - Specifies whether to allow authentifications without MD5 content hash. - - - Public key - - - Secret key - - - Last access - - - API enabled - - - Activate - - - Deactivate - - - Delete keys - - - Generate keys - -
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Models/Api/UploadFileBase.cs b/src/Plugins/SmartStore.WebApi/Models/Api/UploadFileBase.cs deleted file mode 100644 index d0884fa6a3..0000000000 --- a/src/Plugins/SmartStore.WebApi/Models/Api/UploadFileBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Net.Http.Headers; -using System.Runtime.Serialization; - -namespace SmartStore.WebApi.Models.Api -{ - [DataContract] - public abstract partial class UploadFileBase - { - public UploadFileBase() - { - } - - public UploadFileBase(HttpContentHeaders headers) - { - Name = headers.ContentDisposition.Name.ToUnquoted(); - FileName = headers.ContentDisposition.FileName.ToUnquoted(); - ContentDisposition = headers.ContentDisposition.Parameters; - - if (headers.ContentType != null) - { - MediaType = headers.ContentType.MediaType.ToUnquoted(); - } - - if (FileName.HasValue()) - { - FileExtension = Path.GetExtension(FileName); - } - } - - /// - /// Unquoted name attribute of content-disposition multipart header - /// - [DataMember] - public string Name { get; set; } - - /// - /// Unquoted filename attribute of content-disposition multipart header - /// - [DataMember] - public string FileName { get; set; } - - /// - /// Extension of FileName - /// - [DataMember] - public string FileExtension { get; set; } - - /// - /// Media (mime) type of content-type multipart header - /// - [DataMember] - public string MediaType { get; set; } - - /// - /// Indicates whether the uploaded file already exist - /// - [DataMember] - public bool Exists { get; set; } - - /// - /// Indicates whether the uploaded file has been inserted - /// - [DataMember] - public bool Inserted { get; set; } - - /// - /// Raw custom parameters of the content-disposition multipart header - /// - [DataMember] - public ICollection ContentDisposition { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Models/Api/UploadImage.cs b/src/Plugins/SmartStore.WebApi/Models/Api/UploadImage.cs deleted file mode 100644 index cca701246b..0000000000 --- a/src/Plugins/SmartStore.WebApi/Models/Api/UploadImage.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Net.Http.Headers; -using System.Runtime.Serialization; -using SmartStore.Core.Domain.Media; - -namespace SmartStore.WebApi.Models.Api -{ - [DataContract] - public partial class UploadImage : UploadFileBase - { - public UploadImage() - { - } - - public UploadImage(HttpContentHeaders headers) : base(headers) - { - } - - /// - /// Url of the default size image - /// - [DataMember] - public string ImageUrl { get; set; } - - /// - /// Url of the thumbnail image - /// - [DataMember] - public string ThumbImageUrl { get; set; } - - /// - /// Url of the full size image - /// - [DataMember] - public string FullSizeImageUrl { get; set; } - - /// - /// The picture entity. Can be null. - /// - [DataMember] - public Picture Picture { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Models/Api/UploadImportFile.cs b/src/Plugins/SmartStore.WebApi/Models/Api/UploadImportFile.cs deleted file mode 100644 index ac43524577..0000000000 --- a/src/Plugins/SmartStore.WebApi/Models/Api/UploadImportFile.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Net.Http.Headers; -using System.Runtime.Serialization; - -namespace SmartStore.WebApi.Models.Api -{ - [DataContract] - public partial class UploadImportFile : UploadFileBase - { - public UploadImportFile() - { - } - - public UploadImportFile(HttpContentHeaders headers) : base(headers) - { - } - - /// - /// Whether the file type is supported by the import profile - /// - [DataMember] - public bool IsSupportedByProfile { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs b/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs deleted file mode 100644 index 23882c149e..0000000000 --- a/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs +++ /dev/null @@ -1,47 +0,0 @@ -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Modelling; -using SmartStore.Web.Framework.WebApi; - -namespace SmartStore.WebApi.Models -{ - public class WebApiConfigModel : ModelBase - { - [SmartResourceDisplayName("Plugins.Api.WebApi.ApiOdataUrl")] - public string ApiOdataUrl { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.ApiOdataMetadataUrl")] - public string ApiOdataMetadataUrl { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.ValidMinutePeriod")] - public int ValidMinutePeriod { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.NoRequestTimestampValidation")] - public bool NoRequestTimestampValidation { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.AllowEmptyMd5Hash")] - public bool AllowEmptyMd5Hash { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.LogUnauthorized")] - public bool LogUnauthorized { get; set; } - - public int GridPageSize { get; set; } - - public void Copy(WebApiSettings settings, bool fromSettings) - { - if (fromSettings) - { - ValidMinutePeriod = settings.ValidMinutePeriod; - NoRequestTimestampValidation = settings.NoRequestTimestampValidation; - AllowEmptyMd5Hash = settings.AllowEmptyMd5Hash; - LogUnauthorized = settings.LogUnauthorized; - } - else - { - settings.ValidMinutePeriod = ValidMinutePeriod; - settings.NoRequestTimestampValidation = NoRequestTimestampValidation; - settings.AllowEmptyMd5Hash = AllowEmptyMd5Hash; - settings.LogUnauthorized = LogUnauthorized; - } - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Models/WebApiUserModel.cs b/src/Plugins/SmartStore.WebApi/Models/WebApiUserModel.cs deleted file mode 100644 index 2694f9166b..0000000000 --- a/src/Plugins/SmartStore.WebApi/Models/WebApiUserModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -using SmartStore.Web.Framework; -using System; -using SmartStore.Web.Framework.Modelling; - -namespace SmartStore.WebApi.Models -{ - public class WebApiUserModel : EntityModelBase - { - [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Username")] - public string Username { get; set; } - - [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Email")] - public string Email { get; set; } - - [SmartResourceDisplayName("Admin.Customers.Customers.Fields.AdminComment")] - public string AdminComment { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.PublicKey")] - public string PublicKey { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.SecretKey")] - public string SecretKey { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.ApiEnabled")] - public bool Enabled { get; set; } - - [SmartResourceDisplayName("Plugins.Api.WebApi.LastRequest")] - public DateTime? LastRequest { get; set; } - - public string ButtonDisplayEnable - { - get - { - return (PublicKey.HasValue() && !Enabled) ? "inline-block" : "none"; - } - } - public string ButtonDisplayDisable - { - get - { - return Enabled ? "inline-block" : "none"; - } - } - public string ButtonDisplayRemoveKeys - { - get - { - return PublicKey.HasValue() ? "inline-block" : "none"; - } - } - public string ButtonDisplayCreateKeys - { - get - { - return !PublicKey.HasValue() ? "inline-block" : "none"; - } - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Properties/AssemblyInfo.cs b/src/Plugins/SmartStore.WebApi/Properties/AssemblyInfo.cs deleted file mode 100644 index c0af45c817..0000000000 --- a/src/Plugins/SmartStore.WebApi/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,2 +0,0 @@ -using System.Reflection; -[assembly: AssemblyTitle("SmartStore.WebApi")] diff --git a/src/Plugins/SmartStore.WebApi/RouteProvider.cs b/src/Plugins/SmartStore.WebApi/RouteProvider.cs deleted file mode 100644 index 1ce5fb95cb..0000000000 --- a/src/Plugins/SmartStore.WebApi/RouteProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Web.Mvc; -using System.Web.Routing; -using SmartStore.Web.Framework.Routing; -using SmartStore.Web.Framework.WebApi; - -namespace SmartStore.WebApi -{ - public partial class RouteProvider : IRouteProvider - { - public void RegisterRoutes(RouteCollection routes) - { - routes.MapRoute("SmartStore.WebApi.Action", - "Plugins/SmartStore.WebApi/{action}", - new { controller = "WebApi" }, - new[] { "SmartStore.WebApi.Controllers" } - ) - .DataTokens["area"] = WebApiGlobal.PluginSystemName; - } - public int Priority - { - get - { - return 0; - } - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Scripts/smartstore.web.api.js b/src/Plugins/SmartStore.WebApi/Scripts/smartstore.web.api.js deleted file mode 100644 index 0826cbc151..0000000000 --- a/src/Plugins/SmartStore.WebApi/Scripts/smartstore.web.api.js +++ /dev/null @@ -1,26 +0,0 @@ - -(function (webApi, $, undefined) { - - webApi.init = function () { - - // grid button clicked - $('#apiuser-grid').on('click', '.api-grid-button', function () { - var container = $('#apiuser-grid-container'); - var url = container.attr('data-url') + '/' + $(this).attr('name') + '?customerId=' + $(this).parent().attr('data-id'); - - container.doAjax({ - url: url, - callbackSuccess: function (resp) { - - var grid = $('#apiuser-grid').data('tGrid'); - grid.currentPage = 1; - grid.ajaxRequest(); - - } - }); - return false; - }); - }; - - -}(window.webApi = window.webApi || {}, jQuery)); \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Security/WebApiPermissionProvider.cs b/src/Plugins/SmartStore.WebApi/Security/WebApiPermissionProvider.cs deleted file mode 100644 index bae7d89565..0000000000 --- a/src/Plugins/SmartStore.WebApi/Security/WebApiPermissionProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Security; -using SmartStore.Services.Security; -using System.Collections.Generic; - -namespace SmartStore.WebApi.Security -{ - public partial class WebApiPermissionProvider : IPermissionProvider - { - public static readonly PermissionRecord ManageWebApi = new PermissionRecord { Name = "Plugins. Manage Web-API.", SystemName = "ManageWebApi", Category = "Plugin" }; - - public virtual IEnumerable GetPermissions() - { - return new[] { ManageWebApi }; - } - - public virtual IEnumerable GetDefaultPermissions() - { - //return Enumerable.Empty(); - - //uncomment code below in order to give appropriate permissions to admin by default - return new[] - { - new DefaultPermissionRecord - { - CustomerRoleSystemName = SystemCustomerRoleNames.Administrators, - PermissionRecords = new[] { ManageWebApi } - }, - }; - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/Services/IWebApiPluginService.cs b/src/Plugins/SmartStore.WebApi/Services/IWebApiPluginService.cs deleted file mode 100644 index 0b8092aca3..0000000000 --- a/src/Plugins/SmartStore.WebApi/Services/IWebApiPluginService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SmartStore.Core; -using SmartStore.WebApi.Models; -using Telerik.Web.Mvc; - -namespace SmartStore.WebApi.Services -{ - public partial interface IWebApiPluginService - { - IPagedList GetUsers(int pageIndex, int pageSize); - GridModel GetGridModel(int pageIndex, int pageSize); - - bool CreateKeys(int customerId); - void RemoveKeys(int customerId); - void EnableOrDisableUser(int customerId, bool enable); - } -} diff --git a/src/Plugins/SmartStore.WebApi/Services/WebApiCore.cs b/src/Plugins/SmartStore.WebApi/Services/WebApiCore.cs deleted file mode 100644 index 838a26faa9..0000000000 --- a/src/Plugins/SmartStore.WebApi/Services/WebApiCore.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using SmartStore.Core.Domain.Catalog; - -namespace SmartStore.WebApi.Services -{ - [Serializable] - internal class ManageAttributeType - { - public ManageAttributeType() - { - Values = new List(); - ControlTypeId = (int)AttributeControlType.DropdownList; - IsRequired = true; - } - - public string Name { get; set; } - public int ControlTypeId { get; set; } - public bool IsRequired { get; set; } - - public IList Values { get; set; } - - public class ManageAttributeValue - { - public string Name { get; set; } - public string Alias { get; set; } - public string ColorSquaresRgb { get; set; } - public decimal PriceAdjustment { get; set; } - public decimal WeightAdjustment { get; set; } - public bool IsPreSelected { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs b/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs deleted file mode 100644 index 8fb2385a8a..0000000000 --- a/src/Plugins/SmartStore.WebApi/Services/WebApiPluginService.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Linq; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Services.Common; -using SmartStore.Services.Customers; -using SmartStore.Web.Framework.WebApi.Caching; -using SmartStore.Web.Framework.WebApi.Security; -using SmartStore.WebApi.Models; -using Telerik.Web.Mvc; - -namespace SmartStore.WebApi.Services -{ - public class WebApiPluginService : IWebApiPluginService - { - private readonly IRepository _genericAttributes; - private readonly IRepository _customers; - private readonly ICustomerService _customerService; - private readonly IGenericAttributeService _genericAttributeService; - - public WebApiPluginService( - IRepository genericAttributes, - IRepository customers, - ICustomerService customerService, - IGenericAttributeService genericAttributeService) - { - _genericAttributes = genericAttributes; - _customers = customers; - _customerService = customerService; - _genericAttributeService = genericAttributeService; - } - - public IPagedList GetUsers(int pageIndex, int pageSize) - { - var registeredRoleId = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered).Id; - - var query = - from c in _customers.Table - join a in - ( - from a in _genericAttributes.Table - where a.KeyGroup == "Customer" && a.Key == WebApiCachingUserData.Key - select a - ) - on c.Id equals a.EntityId into ga - from a in ga.DefaultIfEmpty() - where !c.Deleted && c.CustomerRoles.Select(r => r.Id).Contains(registeredRoleId) - orderby a.Value descending - select new WebApiUserModel - { - Id = c.Id, - Username = c.Username, - Email = c.Email, - AdminComment = c.AdminComment - }; - - var lst = new PagedList(query, pageIndex, pageSize); - - var cacheData = WebApiCachingUserData.Data(); - - foreach (var itm in lst) - { - var cacheItem = cacheData.FirstOrDefault(x => x.CustomerId == itm.Id); - if (cacheItem != null) - { - itm.PublicKey = cacheItem.PublicKey; - itm.SecretKey = cacheItem.SecretKey; - itm.Enabled = cacheItem.Enabled; - if (cacheItem.LastRequest.HasValue) - itm.LastRequest = cacheItem.LastRequest.ToLocalTime(); - else - itm.LastRequest = null; - } - } - - return lst; - } - public GridModel GetGridModel(int pageIndex, int pageSize) - { - var apiUsers = GetUsers(pageIndex, pageSize); - - var model = new GridModel - { - Data = apiUsers, - Total = apiUsers.TotalCount - }; - - return model; - } - - public bool CreateKeys(int customerId) - { - if (customerId != 0) - { - var hmac = new HmacAuthentication(); - var userData = WebApiCachingUserData.Data(); - string key1, key2; - - for (int i = 0; i < 9999; ++i) - { - if (hmac.CreateKeys(out key1, out key2) && !userData.Exists(x => x.PublicKey.IsCaseInsensitiveEqual(key1))) - { - var apiUser = new WebApiUserCacheData - { - CustomerId = customerId, - PublicKey = key1, - SecretKey = key2, - Enabled = true - }; - - RemoveKeys(customerId); - - var attribute = new GenericAttribute - { - EntityId = customerId, - KeyGroup = "Customer", - Key = WebApiCachingUserData.Key, - Value = apiUser.ToString() - }; - - _genericAttributeService.InsertAttribute(attribute); - - WebApiCachingUserData.Remove(); - return true; - } - } - } - return false; - } - public void RemoveKeys(int customerId) - { - if (customerId != 0) - { - var data = ( - from a in _genericAttributes.Table - where a.EntityId == customerId && a.KeyGroup == "Customer" && a.Key == WebApiCachingUserData.Key - select a).ToList(); - - if (data.Count > 0) - { - foreach (var itm in data) - _genericAttributeService.DeleteAttribute(itm); - - WebApiCachingUserData.Remove(); - } - } - } - public void EnableOrDisableUser(int customerId, bool enable) - { - if (customerId != 0) - { - var cacheData = WebApiCachingUserData.Data(); - var apiUser = cacheData.FirstOrDefault(x => x.CustomerId == customerId); - - if (apiUser != null) - { - apiUser.Enabled = enable; - - var attribute = _genericAttributeService.GetAttributeById(apiUser.GenericAttributeId); - if (attribute != null) - { - attribute.Value = apiUser.ToString(); - _genericAttributeService.UpdateAttribute(attribute); - } - } - } - } - } -} diff --git a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj deleted file mode 100644 index 73e315200d..0000000000 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ /dev/null @@ -1,333 +0,0 @@ - - - - - - False - - - False - - - - true - bin\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - Debug - AnyCPU - {D8947A85-F0D7-4FF5-8E77-ED4EE18447C8} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - SmartStore.WebApi - SmartStore.WebApi - v4.5.1 - 512 - - - - - - - - - ..\..\ - true - - - 4.0 - true - - - - - - - - - - true - full - false - ..\..\Presentation\SmartStore.Web\Plugins\SmartStore.WebApi\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\Presentation\SmartStore.Web\Plugins\SmartStore.WebApi\ - TRACE - prompt - 4 - - - true - bin\EFMigrations\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll - - - ..\..\packages\Autofac.WebApi.3.1.0\lib\net40\Autofac.Integration.WebApi.dll - - - ..\..\packages\AutoMapper.4.1.1\lib\net45\AutoMapper.dll - - - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - - False - ..\..\packages\Microsoft.Data.Edm.5.6.1\lib\net40\Microsoft.Data.Edm.dll - - - False - ..\..\packages\Microsoft.Data.OData.5.6.1\lib\net40\Microsoft.Data.OData.dll - - - ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True - - - - - - - - False - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.1.2\lib\net45\System.Net.Http.Formatting.dll - - - - - - - False - ..\..\packages\System.Spatial.5.6.1\lib\net40\System.Spatial.dll - - - - - - ..\..\packages\Microsoft.AspNet.Cors.5.1.2\lib\net45\System.Web.Cors.dll - - - - - - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - - - False - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.1.2\lib\net45\System.Web.Http.dll - - - ..\..\packages\Microsoft.AspNet.WebApi.Cors.5.1.1\lib\net45\System.Web.Http.Cors.dll - - - ..\..\packages\Microsoft.AspNet.WebApi.OData.5.1.2\lib\net45\System.Web.Http.OData.dll - - - False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - - - False - ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - - - - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - - - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - - - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - - - - - - ..\..\..\lib\Telerik\Telerik.Web.Mvc.dll - - - - - Properties\AssemblySharedInfo.cs - - - Properties\AssemblyVersionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - Designer - Always - - - Designer - Always - - - Always - - - - - {6bda8332-939f-45b7-a25e-7a797260ae59} - SmartStore.Core - - - {210541ad-f659-47da-8763-16f36c5cd2f4} - SmartStore.Services - - - {ccd7f2c9-6a2c-4cf0-8e89-076b8fc0f144} - SmartStore.Data - - - {75fd4163-333c-4dd5-854d-2ef294e45d94} - SmartStore.Web.Framework - - - - - PreserveNewest - - - Designer - - - PreserveNewest - - - Designer - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - False - True - 49203 - / - http://localhost:49567/ - False - True - http://www.smartstore.net - False - - - - - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Views/Web.config b/src/Plugins/SmartStore.WebApi/Views/Web.config deleted file mode 100644 index e9d36a3c51..0000000000 --- a/src/Plugins/SmartStore.WebApi/Views/Web.config +++ /dev/null @@ -1,53 +0,0 @@ - - - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml b/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml deleted file mode 100644 index 9c6dbd98ef..0000000000 --- a/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml +++ /dev/null @@ -1,137 +0,0 @@ -@model WebApiConfigModel -@using SmartStore.WebApi; -@using SmartStore.WebApi.Models; -@using SmartStore.Web.Framework; -@using SmartStore.Web.Framework.Plugins; -@using SmartStore.Web.Framework.WebApi; -@using SmartStore.Web.Framework.UI; -@using Telerik.Web.Mvc.UI; - -@{ - Layout = ""; - - Html.AddScriptParts(true, Url.Content("~/Plugins/SmartStore.WebApi/Scripts/smartstore.web.api.js")); -} - - - -
    - @Html.Raw(@T("Plugins.Api.WebApi.AdminInstruction")) -
    - - - - - -
    - - @(Html.Telerik().Grid() - .Name("apiuser-grid") - .DataKeys(keys => keys.Add(x => x.Id).RouteKey("Id")) - .Columns(c => - { - c.Bound(x => x.Id).Width(50); - c.Bound(x => x.Username); - c.Bound(x => x.Email) - .Template(x => Html.ActionLink(x.Email, "Edit", "Customer", new { id = x.Id, area = "admin" }, new { })) - .ClientTemplate("\"><#= Email #>"); - c.Bound(x => x.AdminComment); - c.Bound(x => x.PublicKey); - c.Bound(x => x.SecretKey); - c.Bound(x => x.Enabled).Width(110).Centered() - .Template(x => @Html.SymbolForBool(x.Enabled)) - .ClientTemplate(@Html.SymbolForBool("Enabled")); - c.Bound(x => x.LastRequest); - c.Template(x => @Html.ApiGridButtons()) - .Width(330) - .ClientTemplate(@Html.ApiGridButtons()); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("GridUserData", "WebApi", new { Namespaces = "SmartStore.WebApi.Controllers", area = WebApiGlobal.PluginSystemName }); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ApiUser")) - .PreserveGridState() - .EnableCustomBinding(true) - ) - -
    - -

     

    - -@using (Html.BeginForm()) -{ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.ApiOdataUrl) - - @Model.ApiOdataUrl -
    - @Html.SmartLabelFor(model => model.ApiOdataMetadataUrl) - - @Model.ApiOdataMetadataUrl -
    - @Html.SmartLabelFor(model => model.ValidMinutePeriod) - - @Html.EditorFor(model => model.ValidMinutePeriod) - @Html.ValidationMessageFor(model => model.ValidMinutePeriod) -
    - @Html.SmartLabelFor(model => model.NoRequestTimestampValidation) - - @Html.EditorFor(model => model.NoRequestTimestampValidation) - @Html.ValidationMessageFor(model => model.NoRequestTimestampValidation) -
    - @Html.SmartLabelFor(model => model.AllowEmptyMd5Hash) - - @Html.EditorFor(model => model.AllowEmptyMd5Hash) - @Html.ValidationMessageFor(model => model.AllowEmptyMd5Hash) -
    - @Html.SmartLabelFor(model => model.LogUnauthorized) - - @Html.EditorFor(model => model.LogUnauthorized) - @Html.ValidationMessageFor(model => model.LogUnauthorized) -
      - -
    -} diff --git a/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs b/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs deleted file mode 100644 index 7440806c04..0000000000 --- a/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Web.Http.OData.Builder; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Configuration; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Stores; -using SmartStore.Web.Framework.WebApi.Configuration; -using SmartStore.WebApi.Services; - -namespace SmartStore.WebApi -{ - public partial class WebApiConfigurationProvider : IWebApiConfigurationProvider - { - private void AddActionsToOrder(EntityTypeConfiguration config) - { - config.Action("PaymentPending") - .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders); - - config.Action("PaymentPaid") - .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders) - .Parameter("PaymentMethodName"); - - config.Action("PaymentRefund") - .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders) - .Parameter("Online"); - - config.Action("Cancel") - .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders); - - var addShipment = config.Action("AddShipment") - .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders); - - addShipment.Parameter("TrackingNumber"); - addShipment.Parameter("SetAsShipped"); - } - - private void AddActionsToProduct(EntityTypeConfiguration config) - { - config.Action("FinalPrice") - .Returns(); - - config.Action("LowestPrice") - .Returns(); - - config.Action("CreateAttributeCombinations") - .ReturnsCollectionFromEntitySet(WebApiOdataEntitySet.ProductVariantAttributeCombinations); - - var manageAttributes = config.Action("ManageAttributes") - .ReturnsCollectionFromEntitySet(WebApiOdataEntitySet.ProductVariantAttributes); - - manageAttributes.Parameter("Synchronize"); - manageAttributes.CollectionParameter("Attributes"); - } - - public void Configure(WebApiConfigurationBroadcaster configData) - { - var m = configData.ModelBuilder; - - m.EntitySet
    (WebApiOdataEntitySet.Addresses); - m.EntitySet(WebApiOdataEntitySet.Categories); - m.EntitySet(WebApiOdataEntitySet.Countries); - m.EntitySet(WebApiOdataEntitySet.Currencies); - m.EntitySet(WebApiOdataEntitySet.Customers); - m.EntitySet(WebApiOdataEntitySet.DeliveryTimes); - m.EntitySet(WebApiOdataEntitySet.Discounts); - m.EntitySet(WebApiOdataEntitySet.Downloads); - m.EntitySet(WebApiOdataEntitySet.GenericAttributes); - m.EntitySet(WebApiOdataEntitySet.Languages); - m.EntitySet(WebApiOdataEntitySet.LocalizedPropertys); - m.EntitySet(WebApiOdataEntitySet.Manufacturers); - m.EntitySet(WebApiOdataEntitySet.OrderNotes); - m.EntitySet(WebApiOdataEntitySet.Orders); - m.EntitySet(WebApiOdataEntitySet.OrderItems); - m.EntitySet(WebApiOdataEntitySet.PaymentMethods); - m.EntitySet(WebApiOdataEntitySet.Pictures); - m.EntitySet(WebApiOdataEntitySet.ProductAttributes); - m.EntitySet(WebApiOdataEntitySet.ProductBundleItems); - m.EntitySet(WebApiOdataEntitySet.ProductCategories); - m.EntitySet(WebApiOdataEntitySet.ProductManufacturers); - m.EntitySet(WebApiOdataEntitySet.ProductPictures); - m.EntitySet(WebApiOdataEntitySet.Products); - m.EntitySet(WebApiOdataEntitySet.ProductSpecificationAttributes); - m.EntitySet(WebApiOdataEntitySet.ProductTags); - m.EntitySet(WebApiOdataEntitySet.ProductVariantAttributes); - m.EntitySet(WebApiOdataEntitySet.ProductVariantAttributeValues); - m.EntitySet(WebApiOdataEntitySet.ProductVariantAttributeCombinations); - m.EntitySet(WebApiOdataEntitySet.QuantityUnits); - m.EntitySet(WebApiOdataEntitySet.RelatedProducts); - m.EntitySet(WebApiOdataEntitySet.ReturnRequests); - m.EntitySet(WebApiOdataEntitySet.Settings); - m.EntitySet(WebApiOdataEntitySet.Shipments); - m.EntitySet(WebApiOdataEntitySet.ShipmentItems); - m.EntitySet(WebApiOdataEntitySet.ShippingMethods); - m.EntitySet(WebApiOdataEntitySet.SpecificationAttributeOptions); - m.EntitySet(WebApiOdataEntitySet.SpecificationAttributes); - m.EntitySet(WebApiOdataEntitySet.StateProvinces); - m.EntitySet(WebApiOdataEntitySet.Stores); - m.EntitySet(WebApiOdataEntitySet.StoreMappings); - m.EntitySet(WebApiOdataEntitySet.TierPrices); - m.EntitySet(WebApiOdataEntitySet.UrlRecords); - m.EntitySet(WebApiOdataEntitySet.SyncMappings); - - AddActionsToOrder(m.Entity()); - AddActionsToProduct(m.Entity()); - } - - public int Priority { get { return 0; } } - } - - - public static class WebApiOdataEntitySet - { - public static string Addresses { get { return "Addresses"; } } - public static string Categories { get { return "Categories"; } } - public static string Countries { get { return "Countries"; } } - public static string Currencies { get { return "Currencies"; } } - public static string Customers { get { return "Customers"; } } - public static string DeliveryTimes { get { return "DeliveryTimes"; } } - public static string Discounts { get { return "Discounts"; } } - public static string Downloads { get { return "Downloads"; } } - public static string GenericAttributes { get { return "GenericAttributes"; } } - public static string Languages { get { return "Languages"; } } - public static string LocalizedPropertys { get { return "LocalizedPropertys"; } } - public static string Manufacturers { get { return "Manufacturers"; } } - public static string OrderNotes { get { return "OrderNotes"; } } - public static string Orders { get { return "Orders"; } } - public static string OrderItems { get { return "OrderItems"; } } - public static string PaymentMethods { get { return "PaymentMethods"; } } - public static string Pictures { get { return "Pictures"; } } - public static string ProductAttributes { get { return "ProductAttributes"; } } - public static string ProductBundleItems { get { return "ProductBundleItems"; } } - public static string ProductCategories { get { return "ProductCategories"; } } - public static string ProductManufacturers { get { return "ProductManufacturers"; } } - public static string ProductPictures { get { return "ProductPictures"; } } - public static string Products { get { return "Products"; } } - public static string ProductSpecificationAttributes { get { return "ProductSpecificationAttributes"; } } - public static string ProductTags { get { return "ProductTags"; } } - public static string ProductVariantAttributes { get { return "ProductVariantAttributes"; } } - public static string ProductVariantAttributeValues { get { return "ProductVariantAttributeValues"; } } - public static string ProductVariantAttributeCombinations { get { return "ProductVariantAttributeCombinations"; } } - public static string QuantityUnits { get { return "QuantityUnits"; } } - public static string RelatedProducts { get { return "RelatedProducts"; } } - public static string ReturnRequests { get { return "ReturnRequests"; } } - public static string Settings { get { return "Settings"; } } - public static string Shipments { get { return "Shipments"; } } - public static string ShipmentItems { get { return "ShipmentItems"; } } - public static string ShippingMethods { get { return "ShippingMethods"; } } - public static string SpecificationAttributeOptions { get { return "SpecificationAttributeOptions"; } } - public static string SpecificationAttributes { get { return "SpecificationAttributes"; } } - public static string StateProvinces { get { return "StateProvinces"; } } - public static string Stores { get { return "Stores"; } } - public static string StoreMappings { get { return "StoreMappings"; } } - public static string TierPrices { get { return "TierPrices"; } } - public static string UrlRecords { get { return "UrlRecords"; } } - public static string SyncMappings { get { return "SyncMappings"; } } - } -} diff --git a/src/Plugins/SmartStore.WebApi/WebApiPlugin.cs b/src/Plugins/SmartStore.WebApi/WebApiPlugin.cs deleted file mode 100644 index 43d717c4c1..0000000000 --- a/src/Plugins/SmartStore.WebApi/WebApiPlugin.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Web.Routing; -using SmartStore.Core.Plugins; -using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; -using SmartStore.Services.Security; -using SmartStore.Web.Framework.WebApi; -using SmartStore.Web.Framework.WebApi.Caching; -using SmartStore.WebApi.Security; - -namespace SmartStore.WebApi -{ - public class WebApiPlugin : BasePlugin, IConfigurable - { - private readonly IPermissionService _permissionService; - private readonly ILocalizationService _localizationService; - private readonly ISettingService _settingService; - - public WebApiPlugin(IPermissionService permissionService, ILocalizationService localizationService, ISettingService settingService) - { - _permissionService = permissionService; - _localizationService = localizationService; - _settingService = settingService; - } - - - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) - { - actionName = "Configure"; - controllerName = "WebApi"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.WebApi.Controllers" }, { "area", WebApiGlobal.PluginSystemName } }; - } - - public override void Install() - { - _permissionService.InstallPermissions(new WebApiPermissionProvider()); - - var apiSettings = new WebApiSettings - { - LogUnauthorized = true, - ValidMinutePeriod = WebApiGlobal.DefaultTimePeriodMinutes - }; - - _settingService.SaveSetting(apiSettings); - - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); - - base.Install(); - - WebApiCachingControllingData.Remove(); - WebApiCachingUserData.Remove(); - } - - public override void Uninstall() - { - WebApiCachingControllingData.Remove(); - WebApiCachingUserData.Remove(); - - _settingService.DeleteSetting(); - - _permissionService.UninstallPermissions(new WebApiPermissionProvider()); - - base.Uninstall(); - } - - } -} diff --git a/src/Plugins/SmartStore.WebApi/changelog.md b/src/Plugins/SmartStore.WebApi/changelog.md deleted file mode 100644 index a2f7a528c8..0000000000 --- a/src/Plugins/SmartStore.WebApi/changelog.md +++ /dev/null @@ -1,55 +0,0 @@ -#Release Notes - -##Web Api 2.5.0.1 -###New Features -* Option to allow authentification without MD5 content hash - -##Web Api 2.2.0.5 -###New Features -* Bridge to import framework: uploading import files to import profile directory - -##Web Api 2.2.0.4 -###New Features -* Added OData endpoint for shipment items -* Added OData action to add a shipment to an order and to set it as shipped -###Improvements -* OData actions should return SingleResult (instead of entity instance) to let expand option be recognized - -##Web Api 2.2.0.3 -###New Features -* Added OData endpoint for payment method -* #727 Option to deactivate TimestampOlderThanLastRequest validation -* #731 Allow deletion and inserting of product category and manufacturer assignments -###Improvements -* Using header timestamp as last user request date rather than API server date - -##Web Api 2.2.0.2 -###Improvements -* WebApiAuthenticate attribute can be applied on methods too -* Product image upload requires manage catalog permission - -##Web Api 2.2.0.1 -###New Features -* #652 Support for file upload and multipart mime - -##Web Api 1.32 -###New Features -* #618 Add endpoint for quantity units - -##Web Api 1.31 -###Bugfixes -* WebApiController requires more permission checks - -##Web Api 1.23 -###New Features -* #431 Add support for localized properties - -##Web Api 1.22 -###New Features -* #393 Implement OData actions for simpler working with product attributes -> added ProductsController.ManageAttributes and ProductsController.CreateAttributeCombinations - -##Web Api 1.21 -###Improvements -* #384 Inserting sluged recources like products require an URL record -###Bugfixes -* PUT implementation was incomplete \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/packages.config b/src/Plugins/SmartStore.WebApi/packages.config deleted file mode 100644 index 9e4fbc38a8..0000000000 --- a/src/Plugins/SmartStore.WebApi/packages.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/web.config b/src/Plugins/SmartStore.WebApi/web.config index 95ed427e0a..20134fcc96 100644 --- a/src/Plugins/SmartStore.WebApi/web.config +++ b/src/Plugins/SmartStore.WebApi/web.config @@ -46,11 +46,11 @@ - + - + diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs index 5d11c61de5..f04be13344 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs @@ -1,87 +1,45 @@ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Web.Mvc; -using System.Web.Routing; using SmartStore.Core; -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Localization; using SmartStore.Web.Framework.Filters; -using SmartStore.Web.Framework.Localization; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Theming; namespace SmartStore.Web.Framework.Controllers -{ - [AdminThemed] - [RequireHttpsByConfig(SslRequirement.Yes)] - [AdminValidateIpAddress] - [CustomerLastActivity] - [StoreIpAddress] - public abstract class AdminControllerBase : SmartController - { - /// - /// Initialize controller - /// - /// Request context - protected override void Initialize(RequestContext requestContext) - { - var routeData = requestContext.RouteData; - if (routeData != null && !routeData.DataTokens.ContainsKey("ParentActionViewContext")) - { - EngineContext.Current.Resolve().IsAdmin = true; - } - base.Initialize(requestContext); - } - - /// - /// Add locales for localizable entities - /// - /// Localizable model - /// Language service - /// Locales - protected virtual void AddLocales(ILanguageService languageService, IList locales) where TLocalizedModelLocal : ILocalizedModelLocal - { - AddLocales(languageService, locales, null); - } +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class NonAdminAttribute : FilterAttribute, IActionFilter + { + public Lazy WorkContext { get; set; } - /// - /// Add locales for localizable entities - /// - /// Localizable model - /// Language service - /// Locales - /// Configure action - protected virtual void AddLocales(ILanguageService languageService, IList locales, Action configure) where TLocalizedModelLocal : ILocalizedModelLocal - { - foreach (var language in languageService.GetAllLanguages(true)) - { - var locale = Activator.CreateInstance(); - locale.LanguageId = language.Id; - if (configure != null) - { - configure.Invoke(locale, locale.LanguageId); - } - locales.Add(locale); - } - } + public void OnActionExecuting(ActionExecutingContext filterContext) + { + } - /// - /// Access denied view - /// - /// Access denied view - [SuppressMessage("ReSharper", "Mvc.AreaNotResolved")] - protected ActionResult AccessDeniedView() - { - return RedirectToAction("AccessDenied", "Security", new { pageUrl = this.Request.RawUrl, area = "Admin" }); - } + public void OnActionExecuted(ActionExecutedContext filterContext) + { + if (!filterContext.IsChildAction) + { + WorkContext.Value.IsAdmin = false; + filterContext.HttpContext.Items["IsNonAdmin"] = true; + } + } + } - /// - /// Renders default access denied view as a partial - /// - protected ActionResult AccessDeniedPartialView() + [AdminValidateIpAddress(Order = 100)] + [RewriteUrl(SslRequirement.Yes, Order = 110)] + [CustomerLastActivity(Order = 100)] + [StoreIpAddress(Order = 100)] + [AdminThemed(Order = -1)] + public abstract class AdminControllerBase : ManageController + { + protected override void OnActionExecuting(ActionExecutingContext filterContext) { - return PartialView("~/Administration/Views/Security/AccessDenied.cshtml"); + if (filterContext.IsChildAction) + return; + + var isNonAdmin = filterContext.ActionDescriptor.HasAttribute(true); + Services.WorkContext.IsAdmin = !isNonAdmin; } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs index 1517db8245..1d17a3d790 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/ContollerExtensions.cs @@ -1,25 +1,63 @@ -using System.IO; +using System; +using System.Globalization; +using System.IO; +using System.Text; using System.Web.Mvc; -using SmartStore.Web.Framework.UI; -using System.Collections.Generic; -using SmartStore.Services.Stores; -using SmartStore.Services.Common; +using System.Web.Mvc.Html; +using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Domain.Customers; -using SmartStore.Collections; -using System.Text; -using System; +using SmartStore.Services.Common; +using SmartStore.Services.Stores; using SmartStore.Utilities; +using SmartStore.Web.Framework.Modelling.Results; + #pragma warning disable 1573 namespace SmartStore.Web.Framework.Controllers { - public static class ContollerExtensions + public static class ContollerExtensions { - /// - /// Render partial view to string - /// - /// Result + #region InvokeAction + + public static MvcHtmlString InvokeAction(this ControllerBase controller, string actionName, string controllerName = null, RouteValueDictionary routeValues = null) + { + Guard.NotNull(controller, nameof(controller)); + Guard.NotEmpty(actionName, nameof(actionName)); + + using (StringWriter writer = new StringWriter(CultureInfo.CurrentCulture)) + { + InvokeAction(controller, writer, actionName, controllerName, routeValues); + return MvcHtmlString.Create(writer.ToString()); + } + } + + public static void InvokeAction(this ControllerBase controller, TextWriter writer, string actionName, string controllerName = null, RouteValueDictionary routeValues = null) + { + Guard.NotNull(controller, nameof(controller)); + Guard.NotNull(writer, nameof(writer)); + Guard.NotEmpty(actionName, nameof(actionName)); + + var viewContext = new ViewContext( + controller.ControllerContext, + new WebFormView(controller.ControllerContext, "tmp"), + controller.ViewData, + controller.TempData, + writer + ); + + var htmlHelper = new HtmlHelper(viewContext, new ViewPage()); + htmlHelper.RenderAction(actionName, controllerName, routeValues); + } + + #endregion + + #region Render view + + /// + /// Render partial view to string + /// + /// Result public static string RenderPartialViewToString(this ControllerBase controller) { return RenderPartialViewToString(controller, null, null, null); @@ -65,7 +103,7 @@ public static string RenderPartialViewToString(this ControllerBase controller, s /// /// View name /// Model - /// Additional ViewData + /// Additional view data /// Result public static string RenderPartialViewToString(this ControllerBase controller, string viewName, object model, object additionalViewData) { @@ -77,13 +115,18 @@ public static string RenderPartialViewToString(this ControllerBase controller, s if (additionalViewData != null) { controller.ViewData.AddRange(CommonHelper.ObjectToDictionary(additionalViewData)); + + if (additionalViewData is ViewDataDictionary vdd) + { + controller.ViewData.TemplateInfo.HtmlFieldPrefix = vdd.TemplateInfo.HtmlFieldPrefix; + } } using (var sw = new StringWriter()) { ViewEngineResult viewResult = System.Web.Mvc.ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName.EmptyNull()); - ThrowIfViewNotFound(viewResult, viewName); + ThrowIfViewNotFound(viewResult, viewName); var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw); viewResult.View.Render(viewContext, sw); @@ -176,6 +219,87 @@ private static void ThrowIfViewNotFound(ViewEngineResult viewResult, string view } } + #endregion + + #region RootActionView + + /// + /// Creates a object using the root actions's for view resolution. + /// + public static ViewResult RootActionView(this Controller controller, string viewName) + { + return RootActionView(controller, viewName, (string)null, null); + } + + /// + /// Creates a object using the root actions's for view resolution. + /// + public static ViewResult RootActionView(this Controller controller, string viewName, string masterName) + { + return RootActionView(controller, viewName, masterName, null); + } + + /// + /// Creates a object using the root actions's for view resolution. + /// + public static ViewResult RootActionView(this Controller controller, string viewName, object model) + { + return RootActionView(controller, viewName, (string)null, model); + } + + /// + /// Creates a object using the root actions's for view resolution. + /// + public static ViewResult RootActionView(this Controller controller, string viewName, string masterName, object model) + { + Guard.NotEmpty(viewName, nameof(viewName)); + + if (model != null) + { + controller.ViewData.Model = model; + } + + return new RootActionViewResult + { + ViewName = viewName, + MasterName = masterName, + ViewData = controller.ViewData, + TempData = controller.TempData, + ViewEngineCollection = controller.ViewEngineCollection + }; + } + + /// + /// Creates a object using the root actions's for view resolution. + /// + public static PartialViewResult RootActionPartialView(this Controller controller, string viewName) + { + return RootActionPartialView(controller, viewName, null); + } + + /// + /// Creates a object using the root actions's for view resolution. + /// + public static PartialViewResult RootActionPartialView(this Controller controller, string viewName, object model) + { + Guard.NotEmpty(viewName, nameof(viewName)); + + if (model != null) + { + controller.ViewData.Model = model; + } + + return new RootActionPartialViewResult + { + ViewName = viewName, + ViewData = controller.ViewData, + TempData = controller.TempData, + ViewEngineCollection = controller.ViewEngineCollection + }; + } + + #endregion + /// /// Get active store scope (for multi-store configuration mode) /// @@ -183,7 +307,7 @@ private static void ThrowIfViewNotFound(ViewEngineResult viewResult, string view /// Store service /// Work context /// Store ID; 0 if we are in a shared mode - public static int GetActiveStoreScopeConfiguration(this Controller controller, IStoreService storeService, IWorkContext workContext) + public static int GetActiveStoreScopeConfiguration(this IController controller, IStoreService storeService, IWorkContext workContext) { //ensure that we have 2 (or more) stores if (storeService.GetAllStores().Count < 2) diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/ManageController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/ManageController.cs new file mode 100644 index 0000000000..d49d4d6a5c --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/ManageController.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services; +using SmartStore.Services.Localization; +using SmartStore.Services.Security; +using SmartStore.Services.Stores; +using SmartStore.Web.Framework.Localization; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Web.Framework.Controllers +{ + public abstract class ManageController : SmartController + { + /// + /// Add locales for localizable entities + /// + /// Localizable model + /// Language service + /// Locales + protected virtual void AddLocales(ILanguageService languageService, IList locales) where TLocalizedModelLocal : ILocalizedModelLocal + { + AddLocales(languageService, locales, null); + } + + /// + /// Add locales for localizable entities + /// + /// Localizable model + /// Language service + /// Locales + /// Configure action + protected virtual void AddLocales(ILanguageService languageService, IList locales, Action configure) where TLocalizedModelLocal : ILocalizedModelLocal + { + foreach (var language in languageService.GetAllLanguages(true)) + { + var locale = Activator.CreateInstance(); + locale.LanguageId = language.Id; + if (configure != null) + { + configure.Invoke(locale, locale.LanguageId); + } + locales.Add(locale); + } + } + + /// + /// Save the store mappings for an entity + /// + /// Entity type + /// The entity + /// Model representation of store selection + protected virtual void SaveStoreMappings(T entity, IStoreSelector model) where T : BaseEntity, IStoreMappingSupported + { + entity.LimitedToStores = model.LimitedToStores; + Services.Resolve().SaveStoreMappings(entity, model.SelectedStoreIds); + } + + /// + /// Save the ACL mappings for an entity + /// + /// Entity type + /// The entity + /// Model representation of ACL selection + protected virtual void SaveAclMappings(T entity, IAclSelector model) where T : BaseEntity, IAclSupported + { + entity.SubjectToAcl = model.SubjectToAcl; + Services.Resolve().SaveAclMappings(entity, model.SelectedCustomerRoleIds); + } + + /// + /// Access denied view + /// + /// Access denied view + [SuppressMessage("ReSharper", "Mvc.AreaNotResolved")] + protected ActionResult AccessDeniedView() + { + return RedirectToAction("AccessDenied", "Security", new { pageUrl = this.Request.RawUrl, area = "Admin" }); + } + + /// + /// Renders default access denied view as a partial + /// + protected ActionResult AccessDeniedPartialView() + { + return PartialView("~/Administration/Views/Security/AccessDenied.cshtml"); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/PaymentControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/PaymentControllerBase.cs index 71b1927f2b..afb217a5ac 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/PaymentControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/PaymentControllerBase.cs @@ -1,12 +1,10 @@ using System.Collections.Generic; -using System.Collections.Specialized; using System.Web.Mvc; using SmartStore.Services.Payments; namespace SmartStore.Web.Framework.Controllers { - - public abstract class PaymentControllerBase : SmartController + public abstract class PaymentControllerBase : SmartController { public abstract IList ValidatePaymentForm(FormCollection form); public abstract ProcessPaymentRequest GetPaymentInfo(FormCollection form); @@ -15,17 +13,5 @@ public virtual string GetPaymentSummary(FormCollection form) { return null; } - - protected virtual NameValueCollection GetPaymentData() - { - if (Request.RequestType.IsCaseInsensitiveEqual("POST")) - { - return Request.Form; - } - - var persisted = Session["PaymentData"] as NameValueCollection; - - return persisted ?? Request.Form; - } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/PluginControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/PluginControllerBase.cs index c1163aa7d6..4a3acc5710 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/PluginControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/PluginControllerBase.cs @@ -1,42 +1,9 @@ -using System.Diagnostics.CodeAnalysis; +using System; using System.Web.Mvc; -using System.Web.Routing; -using SmartStore.Core; -using SmartStore.Core.Infrastructure; namespace SmartStore.Web.Framework.Controllers { - public abstract partial class PluginControllerBase : SmartController + public abstract partial class PluginControllerBase : ManageController { - ///// - ///// Initialize controller - ///// - ///// Request context - //protected override void Initialize(RequestContext requestContext) - //{ - // ////set work context to admin mode - // //EngineContext.Current.Resolve().IsAdmin = true; - - // base.Initialize(requestContext); - //} - - /// - /// Access denied view - /// - /// Access denied view - [SuppressMessage("ReSharper", "Mvc.AreaNotResolved")] - protected ActionResult AccessDeniedView() - { - return RedirectToAction("AccessDenied", "Security", new { pageUrl = this.Request.RawUrl, area = "Admin" }); - } - - /// - /// Renders default access denied view as a partial - /// - protected ActionResult AccessDeniedPartialView() - { - return PartialView("~/Administration/Views/Security/AccessDenied.cshtml"); - } - } } diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs index 7c32afd7a7..fee48defb5 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/PublicControllerBase.cs @@ -6,16 +6,15 @@ namespace SmartStore.Web.Framework.Controllers { - [CustomerLastActivity] - [StoreIpAddress] - [StoreLastVisitedPage] - [CheckAffiliate] - [StoreClosed] - [PublicStoreAllowNavigation] - [LanguageSeoCode] - [RequireHttpsByConfigAttribute(SslRequirement.Retain)] - [CanonicalHostName] - public abstract partial class PublicControllerBase : SmartController + [RewriteUrl(SslRequirement.Retain)] + [StoreClosed(Order = -1)] + [PublicStoreAllowNavigation(Order = -1)] + [LanguageSeoCode(Order = -1)] + [CustomerLastActivity(Order = 100)] + [StoreIpAddress(Order = 100)] + [StoreLastVisitedPage(Order = 100)] + [CheckAffiliate(Order = 100)] + public abstract partial class PublicControllerBase : SmartController { - } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index 29fdb90d52..3ca358f11d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -1,5 +1,4 @@ using System; -using System.Text; using System.Web; using System.Web.Mvc; using SmartStore.Core.Localization; @@ -11,9 +10,9 @@ namespace SmartStore.Web.Framework.Controllers { - [SetWorkingCulture] - [Notify] - [JsonNet] + [SetWorkingCulture(Order = -1)] + [JsonNet(Order = -1)] + [Notify(Order = 100)] public abstract partial class SmartController : Controller { protected SmartController() @@ -107,7 +106,7 @@ protected virtual void NotifyAccessDenied(bool durable = true, bool log = true) if (log) { - Logger.Error(message, null, Services.WorkContext.CurrentCustomer); + Logger.Error(message); } Services.Notifier.Error(message, durable); @@ -178,13 +177,24 @@ protected virtual ActionResult RedirectToReferrer(string referrer, Func + /// Redirects to the configuration page of a plugin or a provider. + ///
    + /// The system name of the plugin or the provider. + /// true plugin configuration, false provider configuration. + protected virtual ActionResult RedirectToConfiguration(string systemName, bool isPlugin = true) { - string message = T("Common.RequestProcessingFailed", this.RouteData.Values["controller"], this.RouteData.Values["action"], reason.NaIfEmpty()); + Guard.NotEmpty(systemName, nameof(systemName)); - Services.Notifier.Error(message, durable); + var actionName = isPlugin ? "ConfigurePlugin" : "ConfigureProvider"; + + if (ControllerContext.IsChildAction) + { + var url = Url.Action(actionName, "Plugin", new { systemName, area = "Admin" }); + return new PermissiveRedirectResult(url); + } - return RedirectToRoute("HomePage"); + return RedirectToAction(actionName, "Plugin", new { systemName, area = "Admin" }); } /// @@ -204,11 +214,10 @@ protected override void OnException(ExceptionContext filterContext) /// /// Log exception /// - /// Exception - private void LogException(Exception exc) + /// Exception + private void LogException(Exception ex) { - var customer = Services.WorkContext.CurrentCustomer; - Logger.Error(exc.Message, exc, customer); + Logger.Error(ex); } ///// diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index a4b9e10d07..80526ac5c3 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Web; -using System.Web.Mvc; +using System.Web.Hosting; using Autofac; using Autofac.Builder; using Autofac.Core; @@ -11,6 +12,7 @@ using Autofac.Integration.WebApi; using SmartStore.ComponentModel; using SmartStore.Core; +using SmartStore.Core.Async; using SmartStore.Core.Caching; using SmartStore.Core.Configuration; using SmartStore.Core.Data; @@ -21,43 +23,52 @@ using SmartStore.Core.Fakes; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; -using SmartStore.Core.IO.Media; -using SmartStore.Core.IO.VirtualPath; -using SmartStore.Core.IO.WebSite; +using SmartStore.Core.IO; using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Packaging; using SmartStore.Core.Plugins; +using SmartStore.Core.Search; using SmartStore.Core.Themes; using SmartStore.Data; +using SmartStore.Data.Caching; using SmartStore.Services; using SmartStore.Services.Affiliates; using SmartStore.Services.Authentication; using SmartStore.Services.Authentication.External; using SmartStore.Services.Blogs; using SmartStore.Services.Catalog; +using SmartStore.Services.Catalog.Extensions; +using SmartStore.Services.Catalog.Importer; +using SmartStore.Services.Catalog.Modelling; using SmartStore.Services.Cms; using SmartStore.Services.Common; using SmartStore.Services.Configuration; using SmartStore.Services.Customers; +using SmartStore.Services.Customers.Importer; using SmartStore.Services.DataExchange; using SmartStore.Services.DataExchange.Export; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.Events; -using SmartStore.Services.Filter; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Logging; using SmartStore.Services.Media; +using SmartStore.Services.Media.Storage; using SmartStore.Services.Messages; +using SmartStore.Services.Messages.Importer; using SmartStore.Services.News; using SmartStore.Services.Orders; using SmartStore.Services.Payments; using SmartStore.Services.Pdf; using SmartStore.Services.Polls; +using SmartStore.Services.Search; +using SmartStore.Services.Search.Extensions; +using SmartStore.Services.Search.Modelling; +using SmartStore.Services.Search.Rendering; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Shipping; @@ -66,39 +77,39 @@ using SmartStore.Services.Tax; using SmartStore.Services.Themes; using SmartStore.Services.Topics; +using SmartStore.Templating; +using SmartStore.Templating.Liquid; using SmartStore.Utilities; using SmartStore.Web.Framework.Bundling; +using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Localization; using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Routing; using SmartStore.Web.Framework.Theming; +using SmartStore.Web.Framework.Theming.Assets; using SmartStore.Web.Framework.UI; using SmartStore.Web.Framework.WebApi; using SmartStore.Web.Framework.WebApi.Configuration; using Module = Autofac.Module; -using SmartStore.Services.Catalog.Importer; -using SmartStore.Services.Customers.Importer; -using SmartStore.Services.Messages.Importer; namespace SmartStore.Web.Framework { - public class DependencyRegistrar : IDependencyRegistrar + public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { // plugins - var pluginFinder = new PluginFinder(); - builder.RegisterInstance(pluginFinder).As().SingleInstance(); + var pluginFinder = PluginFinder.Current; + builder.RegisterInstance(pluginFinder); builder.RegisterType(); // modules builder.RegisterModule(new CoreModule(typeFinder)); builder.RegisterModule(new DbModule(typeFinder)); builder.RegisterModule(new CachingModule()); + builder.RegisterModule(new SearchModule()); builder.RegisterModule(new LocalizationModule()); - builder.RegisterModule(new LoggingModule()); - builder.RegisterModule(new EventModule(typeFinder, pluginFinder)); builder.RegisterModule(new MessagingModule()); builder.RegisterModule(new WebModule(typeFinder)); builder.RegisterModule(new WebApiModule(typeFinder)); @@ -108,6 +119,7 @@ public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, b builder.RegisterModule(new ProvidersModule(typeFinder, pluginFinder)); builder.RegisterModule(new TasksModule(typeFinder)); builder.RegisterModule(new DataExchangeModule(typeFinder)); + builder.RegisterModule(new EventModule(typeFinder, pluginFinder)); } public int Order @@ -129,8 +141,17 @@ public CoreModule(ITypeFinder typeFinder) protected override void Load(ContainerBuilder builder) { + builder.RegisterType().As().SingleInstance(); + // sources + builder.RegisterGeneric(typeof(WorkValues<>)).InstancePerRequest(); builder.RegisterSource(new SettingsSource()); + builder.RegisterSource(new WorkSource()); + + // Logging stuff + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); // web helper builder.RegisterType().As().InstancePerRequest(); @@ -164,11 +185,12 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); @@ -177,10 +199,10 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); @@ -198,8 +220,9 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); @@ -228,15 +251,18 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); @@ -246,7 +272,6 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); } @@ -257,15 +282,16 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone if (servicesProperty == null) return; - - var fastProperty = new FastProperty(servicesProperty); + + registration.Metadata.Add("Property.ICommonServices", new FastProperty(servicesProperty)); registration.Activated += (sender, e) => { if (DataSettings.DatabaseIsInstalled()) { + var prop = e.Component.Metadata.Get("Property.ICommonServices") as FastProperty; var services = e.Context.Resolve(); - fastProperty.SetValue(e.Instance, services); + prop.SetValue(e.Instance, services); } }; } @@ -337,11 +363,15 @@ protected override void Load(ContainerBuilder builder) builder.Register(x => x.Resolve().LoadDataProvider()).As().InstancePerDependency(); builder.Register(x => (IEfDataProvider)x.Resolve().LoadDataProvider()).As().InstancePerDependency(); - if (DataSettings.Current.IsValid()) + builder.RegisterType().As().InstancePerRequest(); + + builder.RegisterType().As().SingleInstance(); + + if (DataSettings.DatabaseIsInstalled()) { // register DB Hooks (only when app was installed properly) - Func findHookedType = (t) => + Type findHookedType(Type t) { var x = t; while (x != null) @@ -353,31 +383,48 @@ protected override void Load(ContainerBuilder builder) x = x.BaseType; } - return typeof(object); - }; + return typeof(BaseEntity); + } - var hooks = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true); + var hooks = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true); foreach (var hook in hooks) { var hookedType = findHookedType(hook); var registration = builder.RegisterType(hook) - .As(typeof(IPreActionHook).IsAssignableFrom(hook) ? typeof(IPreActionHook) : typeof(IPostActionHook)) - .InstancePerRequest(); - - registration.WithMetadata(m => - { - m.For(em => em.HookedType, hookedType); - }); + .As() + .InstancePerRequest() + .WithMetadata(m => + { + m.For(em => em.HookedType, hookedType); + m.For(em => em.ImplType, hook); + m.For(em => em.IsLoadHook, false); + m.For(em => em.Important, hook.HasAttribute(false)); + }); } builder.Register(c => new SmartObjectContext(DataSettings.Current.DataConnectionString)) - .PropertiesAutowired(PropertyWiringOptions.None) + //.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies) + .PropertiesAutowired(new DbContextPropertySelector(), false) .InstancePerRequest(); } else { - builder.Register(c => new SmartObjectContext(DataSettings.Current.DataConnectionString)).InstancePerRequest(); + builder.Register(c => + { + try + { + return new SmartObjectContext(DataSettings.Current.DataConnectionString); + } + catch + { + // return new SmartObjectContext(); + return null; + } + + }) + .PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies) + .InstancePerRequest(); } builder.Register>(c => @@ -405,14 +452,17 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone if (querySettingsProperty == null) return; - var fastProperty = new FastProperty(querySettingsProperty); + registration.Metadata.Add("Property.DbQuerySettings", new FastProperty(querySettingsProperty)); registration.Activated += (sender, e) => { if (DataSettings.DatabaseIsInstalled()) { - var querySettings = e.Context.Resolve(); - fastProperty.SetValue(e.Instance, querySettings); + if (e.Component.Metadata.Get("Property.DbQuerySettings") is FastProperty prop) + { + var querySettings = e.Context.Resolve(); + prop.SetValue(e.Instance, querySettings); + } } }; } @@ -421,65 +471,13 @@ private static PropertyInfo FindQuerySettingsProperty(Type type) { return type.GetProperty("QuerySettings", typeof(DbQuerySettings)); } - } - - public class LoggingModule : Module - { - protected override void Load(ContainerBuilder builder) - { - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - } - protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) + private class DbContextPropertySelector : IPropertySelector { - if (!DataSettings.DatabaseIsInstalled()) - return; - - var implementationType = registration.Activator.LimitType; - - // build an array of actions on this type to assign loggers to member properties - var injectors = BuildLoggerInjectors(implementationType).ToArray(); - - // if there are no logger properties, there's no reason to hook the activated event - if (!injectors.Any()) - return; - - // otherwise, whan an instance of this component is activated, inject the loggers on the instance - registration.Activated += (s, e) => - { - foreach (var injector in injectors) - injector(e.Context, e.Instance); - }; - } - - private IEnumerable> BuildLoggerInjectors(Type componentType) - { - // Look for settable properties of type "ILogger" - var loggerProperties = componentType - .GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance) - .Select(p => new - { - PropertyInfo = p, - p.PropertyType, - IndexParameters = p.GetIndexParameters(), - Accessors = p.GetAccessors(false) - }) - .Where(x => x.PropertyType == typeof(ILogger)) // must be a logger - .Where(x => x.IndexParameters.Count() == 0) // must not be an indexer - .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)) //must have get/set, or only set - .Select(x => new FastProperty(x.PropertyInfo)); - - // Return an array of actions that resolve a logger and assign the property - foreach (var prop in loggerProperties) + public bool InjectProperty(PropertyInfo propertyInfo, object instance) { - yield return (ctx, instance) => - { - string component = componentType.ToString(); - var logger = ctx.Resolve(); - prop.SetValue(instance, logger); - }; + // Prevent Autofac circularity exception & never trigger hooks during tooling or tests + return HostingEnvironment.IsHosted && typeof(IDbHookHandler).IsAssignableFrom(propertyInfo.PropertyType); } } } @@ -494,7 +492,10 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); + builder.Register(c => c.Resolve().Get).InstancePerRequest(); + builder.Register(c => c.Resolve().GetEx).InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); } @@ -505,21 +506,37 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone if (userProperty == null) return; - var fastProperty = new FastProperty(userProperty); + registration.Metadata.Add("Property.T", new FastProperty(userProperty)); registration.Activated += (sender, e) => { - if (DataSettings.DatabaseIsInstalled()) + if (DataSettings.DatabaseIsInstalled() && e.Context.Resolve().IsFullyInitialized) { - Localizer localizer = e.Context.Resolve().Get; - fastProperty.SetValue(e.Instance, localizer); + if (e.Component.Metadata.Get("Property.T") is FastProperty prop) + { + try + { + var iText = e.Context.Resolve(); + if (prop.Property.PropertyType == typeof(Localizer)) + { + Localizer localizer = e.Context.Resolve().Get; + prop.SetValue(e.Instance, localizer); + } + else + { + LocalizerEx localizerEx = e.Context.Resolve().GetEx; + prop.SetValue(e.Instance, localizerEx); + } + } + catch { } + } } }; } private static PropertyInfo FindUserProperty(Type type) { - return type.GetProperty("T", typeof(Localizer)); + return type.GetProperty("T", typeof(Localizer)) ?? type.GetProperty("T", typeof(LocalizerEx)); } } @@ -529,16 +546,56 @@ protected override void Load(ContainerBuilder builder) { // Output cache builder.RegisterType().As().InstancePerRequest(); + builder.Register(c => NullOutputCacheInvalidationObserver.Instance).SingleInstance(); + builder.RegisterType().As().InstancePerRequest(); // Request cache builder.RegisterType().As().InstancePerRequest(); // Model/Business cache (application scoped) + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().Named("null").SingleInstance(); + + // Register MemoryCacheManager twice, this time explicitly named. + // We may need this later in decorator classes as a kind of fallback. + builder.RegisterType().Named("memory").SingleInstance(); + + // Asset cache + if (DataSettings.DatabaseIsInstalled()) + { + builder.RegisterType().As().InstancePerRequest(); + } + else + { + builder.Register(c => DefaultAssetCache.Null).SingleInstance(); + } } } + public class SearchModule : Module + { + protected override void Load(ContainerBuilder builder) + { + // General. + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + + // Catalog. + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().Named("linq").InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + + // Forum. + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().Named("linq").InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + } + } + public class EventModule : Module { private readonly ITypeFinder _typeFinder; @@ -552,53 +609,52 @@ public EventModule(ITypeFinder typeFinder, IPluginFinder pluginFinder) protected override void Load(ContainerBuilder builder) { + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); - builder.RegisterGeneric(typeof(DefaultConsumerFactory<>)).As(typeof(IConsumerFactory<>)).InstancePerDependency(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); - // Register event consumers - var consumerTypes = _typeFinder.FindClassesOfType(typeof(IConsumer<>)); - foreach (var consumerType in consumerTypes) + var consumerTypes = _typeFinder.FindClassesOfType(typeof(IConsumer)); + foreach (var type in consumerTypes) { - Type[] implementedInterfaces = consumerType.FindInterfaces(IsConsumerInterface, typeof(IConsumer<>)); - - var registration = builder.RegisterType(consumerType).As(implementedInterfaces); + var registration = builder + .RegisterType(type) + .As() + .Keyed(type) + .InstancePerRequest(); - var pluginDescriptor = _pluginFinder.GetPluginDescriptorByAssembly(consumerType.Assembly); - var isActive = PluginManager.IsActivePluginAssembly(consumerType.Assembly); - var shouldExecuteAsync = consumerType.GetAttribute(false) != null; + var pluginDescriptor = _pluginFinder.GetPluginDescriptorByAssembly(type.Assembly); + var isActive = PluginManager.IsActivePluginAssembly(type.Assembly); registration.WithMetadata(m => { m.For(em => em.IsActive, isActive); - m.For(em => em.ExecuteAsync, shouldExecuteAsync); + m.For(em => em.ContainerType, type); m.For(em => em.PluginDescriptor, pluginDescriptor); }); - - if (!shouldExecuteAsync) - registration.InstancePerRequest(); - } } - - private static bool IsConsumerInterface(Type type, object criteria) - { - var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()); - return isMatch; - } } public class MessagingModule : Module { protected override void Load(ContainerBuilder builder) { + // Templating + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().SingleInstance(); // xxx (http) + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().SingleInstance(); } } @@ -626,12 +682,21 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().InstancePerRequest(); + // Filter provider builder.RegisterFilterProvider(); + // Model binding + builder.RegisterModelBinders(foundAssemblies); + builder.RegisterModelBinderProvider(); + + var pageHelperRegistration = builder.RegisterType().InstancePerRequest(); + // global exception handling if (DataSettings.DatabaseIsInstalled()) { - builder.RegisterType().AsActionFilterFor(); + pageHelperRegistration.PropertiesAutowired(PropertyWiringOptions.None); + builder.RegisterType().AsActionFilterFor(-100); + builder.RegisterType().AsActionFilterFor().InstancePerRequest(); } } @@ -642,8 +707,6 @@ static HttpContextBase HttpContextBaseFactory(IComponentContext ctx) return new HttpContextWrapper(HttpContext.Current); } - // TODO: determine store url - // register FakeHttpContext when HttpContext is not available return new FakeHttpContext("~/"); } @@ -651,19 +714,11 @@ static HttpContextBase HttpContextBaseFactory(IComponentContext ctx) static bool IsRequestValid() { if (HttpContext.Current == null) - return false; - - try - { - // The "Request" property throws at application startup on IIS integrated pipeline mode - var req = HttpContext.Current.Request; - } - catch (Exception) { return false; } - return true; + return HttpContext.Current.SafeGetHttpRequest() != null; } } @@ -693,9 +748,8 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone var baseType = typeof(WebApiEntityController<,>); var type = registration.Activator.LimitType; - Type implementingType; - if (!type.IsSubClass(baseType, out implementingType)) + if (!type.IsSubClass(baseType, out var implementingType)) return; var repoProperty = FindRepositoryProperty(type, implementingType.GetGenericArguments()[0]); @@ -738,16 +792,16 @@ public class UiModule : Module { private readonly ITypeFinder _typeFinder; - public UiModule(ITypeFinder typeFinder) + public UiModule(ITypeFinder typeFinder) { _typeFinder = typeFinder; - } + } protected override void Load(ContainerBuilder builder) { // register theming services - builder.Register(x => new DefaultThemeRegistry(x.Resolve(), null, null, true)).As().SingleInstance(); - builder.RegisterType().As().SingleInstance(); + builder.Register(x => new DefaultThemeRegistry(x.Resolve(), x.Resolve(), null, null, true)).As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); @@ -757,18 +811,61 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().As>(); builder.RegisterType().As>(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - } + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().SingleInstance(); + + // Menus. + builder.RegisterType().As().InstancePerRequest(); + + var menuResolverTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true); + foreach (var type in menuResolverTypes) + { + builder.RegisterType(type).As().PropertiesAutowired(PropertyWiringOptions.None).InstancePerRequest(); + } + + builder.RegisterType().Named("database").InstancePerDependency(); + + var menuTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true); + foreach (var type in menuTypes) + { + builder.RegisterType(type).As().PropertiesAutowired(PropertyWiringOptions.None).InstancePerRequest(); + } + + var menuItemProviderTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true); + foreach (var type in menuItemProviderTypes) + { + var attribute = type.GetAttribute(false); + var registration = builder.RegisterType(type).As().PropertiesAutowired(PropertyWiringOptions.None).InstancePerRequest(); + registration.WithMetadata(m => + { + m.For(em => em.ProviderName, attribute.ProviderName); + m.For(em => em.AppendsMultipleItems, attribute.AppendsMultipleItems); + }); + } + + if (DataSettings.DatabaseIsInstalled()) + { + builder.RegisterType().AsResultFilterFor(0); + } + } } public class IOModule : Module { protected override void Load(ContainerBuilder builder) { - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + + // Register IFileSystem twice, this time explicitly named. + // We may need this later in decorator classes as a kind of fallback. + builder.RegisterType().Named("local").SingleInstance(); + builder.RegisterType().Named("local").SingleInstance(); + + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); } } @@ -846,6 +943,7 @@ protected override void Load(ContainerBuilder builder) RegisterAsSpecificProvider(type, systemName, registration); RegisterAsSpecificProvider(type, systemName, registration); RegisterAsSpecificProvider(type, systemName, registration); + RegisterAsSpecificProvider(type, systemName, registration); } } @@ -1084,25 +1182,23 @@ protected override void Load(ContainerBuilder builder) return keyed => cc.ResolveKeyed(keyed); }); } - } #endregion #region Sources - public class SettingsSource : IRegistrationSource + class SettingsSource : IRegistrationSource { static readonly MethodInfo BuildMethod = typeof(SettingsSource).GetMethod( "BuildRegistration", BindingFlags.Static | BindingFlags.NonPublic); public IEnumerable RegistrationsFor( - Service service, - Func> registrations) + Service service, + Func> registrations) { - var ts = service as TypedService; - if (ts != null && typeof(ISettings).IsAssignableFrom(ts.ServiceType)) + if (service is TypedService ts && typeof(ISettings).IsAssignableFrom(ts.ServiceType)) { var buildMethod = BuildMethod.MakeGenericMethod(ts.ServiceType); yield return (IComponentRegistration)buildMethod.Invoke(null, null); @@ -1115,24 +1211,35 @@ public IEnumerable RegistrationsFor( .ForDelegate((c, p) => { int currentStoreId = 0; - IStoreContext storeContext; - if (c.TryResolve(out storeContext)) + if (EngineContext.Current.IsFullyInitialized) { - var store = storeContext.CurrentStore; - - currentStoreId = store.Id; - //uncomment the code below if you want load settings per store only when you have two stores installed. - //var currentStoreId = c.Resolve().GetAllStores().Count > 1 - // c.Resolve().CurrentStore.Id : 0; - - ////although it's better to connect to your database and execute the following SQL: - //DELETE FROM [Setting] WHERE [StoreId] > 0 + try + { + if (c.TryResolve(out IStoreContext storeContext)) + { + currentStoreId = storeContext.CurrentStore.Id; + //uncomment the code below if you want load settings per store only when you have two stores installed. + //var currentStoreId = c.Resolve().GetAllStores().Count > 1 + // c.Resolve().CurrentStore.Id : 0; + + ////although it's better to connect to your database and execute the following SQL: + //DELETE FROM [Setting] WHERE [StoreId] > 0 + + //return c.Resolve().LoadSetting(currentStoreId); + } + } + catch { } + } + try + { return c.Resolve().LoadSetting(currentStoreId); } - - // Unit tests - return new TSettings(); + catch + { + // Unit tests & tooling + return new TSettings(); + } }) .InstancePerRequest() .CreateRegistration(); @@ -1141,6 +1248,82 @@ public IEnumerable RegistrationsFor( public bool IsAdapterForIndividualComponents { get { return false; } } } + class WorkSource : IRegistrationSource + { + static readonly MethodInfo CreateMetaRegistrationMethod = typeof(WorkSource).GetMethod( + "CreateMetaRegistration", BindingFlags.Static | BindingFlags.NonPublic); + + private static bool IsClosingTypeOf(Type type, Type openGenericType) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType; + } + + public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) + { + if (!(service is IServiceWithType swt) || !IsClosingTypeOf(swt.ServiceType, typeof(Work<>))) + return Enumerable.Empty(); + + var valueType = swt.ServiceType.GetGenericArguments()[0]; + + var valueService = swt.ChangeType(valueType); + + var registrationCreator = CreateMetaRegistrationMethod.MakeGenericMethod(valueType); + + return registrationAccessor(valueService) + .Select(v => registrationCreator.Invoke(null, new object[] { service, v })) + .Cast(); + } + + public bool IsAdapterForIndividualComponents + { + get { return true; } + } + + static IComponentRegistration CreateMetaRegistration(Service providedService, IComponentRegistration valueRegistration) where T : class + { + var rb = RegistrationBuilder.ForDelegate( + (c, p) => { + var accessor = c.Resolve(); + return new Work(w => + { + var scope = accessor.GetLifetimeScope(null); + if (scope == null) + return default(T); + + var workValues = scope.Resolve>(); + + if (!workValues.Values.TryGetValue(w, out T value)) + { + value = (T)workValues.ComponentContext.ResolveComponent(valueRegistration, p); + workValues.Values[w] = value; + } + + return value; + + ////T value = default(T); // accessor.GetLifetimeScope(null).Resolve(); + //return accessor.GetLifetimeScope(null).Resolve(); + }); + }) + .As(providedService) + .Targeting(valueRegistration) + .SingleInstance(); + + return rb.CreateRegistration(); + } + } + + class WorkValues where T : class + { + public WorkValues(IComponentContext componentContext) + { + ComponentContext = componentContext; + Values = new Dictionary, T>(); + } + + public IComponentContext ComponentContext { get; private set; } + public IDictionary, T> Values { get; private set; } + } + #endregion } diff --git a/src/Presentation/SmartStore.Web.Framework/Events/TabStripCreated.cs b/src/Presentation/SmartStore.Web.Framework/Events/TabStripCreated.cs index d86a2d5c9c..30d4d9b9a3 100644 --- a/src/Presentation/SmartStore.Web.Framework/Events/TabStripCreated.cs +++ b/src/Presentation/SmartStore.Web.Framework/Events/TabStripCreated.cs @@ -35,7 +35,7 @@ public TabStripCreated(TabFactory itemFactory, string tabStripName, HtmlHelper h /// Should only be called for admin tabstrips public void AddWidget(RouteInfo routeInfo) { - Guard.ArgumentNotNull(() => routeInfo); + Guard.NotNull(routeInfo, nameof(routeInfo)); if (_widgets == null) { diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/ActionResultExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/ActionResultExtensions.cs new file mode 100644 index 0000000000..b0dbc907ce --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/ActionResultExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Web.Mvc; + +// use base SmartStore Namespace to ensure the extension methods are always available +namespace SmartStore +{ + public static class ActionResultExtensions + { + public static bool IsHtmlViewResult(this ActionResult result) + { + if (result is ViewResultBase) + return true; + + var contentResult = result as ContentResult; + if (contentResult != null) + { + return contentResult.ContentType.IsCaseInsensitiveEqual("text/html"); + } + + return false; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs index 3277e60cb6..53628958a0 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs @@ -105,7 +105,8 @@ public static string RelativeFormat(this DateTime source, bool convertToUserTime result = tmp1.ToString(); } } - return result; + + return result.ReplaceNativeDigits(); } public static string Prettify(this TimeSpan ts) @@ -136,14 +137,18 @@ public static string Prettify(this TimeSpan ts) remainderLabel = T("Time.MinutesAbbr"); } + string result = null; + if (remainder == 0) { - return string.Format("{0:#,##0.#} {1}", secsTemp, label); + result = string.Format("{0:#,##0.#} {1}", secsTemp, label); } else { - return string.Format("{0:#,##0} {1} {2} {3}", secsTemp, label, remainder, remainderLabel); + result = string.Format("{0:#,##0} {1} {2} {3}", secsTemp, label, remainder, remainderLabel); } + + return result.ReplaceNativeDigits(); } catch { diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index 07cba01d0c..b2198a285f 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -12,6 +12,7 @@ using System.Web.WebPages; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Infrastructure; using SmartStore.Services.Localization; using SmartStore.Utilities; @@ -22,7 +23,6 @@ namespace SmartStore.Web.Framework { - public enum InputEditorType { TextBox, Password, @@ -35,8 +35,13 @@ public static class HtmlExtensions { public static MvcHtmlString Hint(this HtmlHelper helper, string value) { - // create a - var a = new TagBuilder("a"); + if (String.IsNullOrEmpty(value)) + { + return MvcHtmlString.Empty; + } + + // create a + var a = new TagBuilder("a"); a.MergeAttribute("href", "#"); a.MergeAttribute("onclick", "return false;"); //a.MergeAttribute("rel", "tooltip"); @@ -54,33 +59,45 @@ public static MvcHtmlString Hint(this HtmlHelper helper, string value) return MvcHtmlString.Create(a.ToString()); } - public static HelperResult LocalizedEditor(this HtmlHelper helper, string name, - Func localizedTemplate, - Func standardTemplate) + public static HelperResult LocalizedEditor(this HtmlHelper helper, string name, Func localizedTemplate, Func standardTemplate) where T : ILocalizedModel where TLocalizedModelLocal : ILocalizedModelLocal { - return new HelperResult(writer => + return new HelperResult(writer => { if (helper.ViewData.Model.Locales.Count > 1) { - writer.Write("
    "); - var tabStrip = helper.SmartStore().TabStrip().Name(name).SmartTabSelection(false).Style(TabsStyle.Pills).Items(x => + var languageService = EngineContext.Current.Resolve(); + + writer.Write("
    "); + var tabStrip = helper.SmartStore().TabStrip().Name(name).SmartTabSelection(false).Style(TabsStyle.Tabs).AddCssClass("nav-locales").Items(x => { - x.Add().Text("Standard").Content(standardTemplate(helper.ViewData.Model).ToHtmlString()).Selected(true); + if (standardTemplate != null) + { + var masterLanguage = languageService.GetLanguageById(languageService.GetDefaultLanguageId()); + x.Add().Text(EngineContext.Current.Resolve().GetResource("Admin.Common.Standard")) + .ContentHtmlAttributes(new { @class = "locale-editor-content", data_lang = masterLanguage.LanguageCulture, data_rtl = masterLanguage.Rtl.ToString().ToLower() }) + .Content(standardTemplate(helper.ViewData.Model).ToHtmlString()) + .Selected(true); + } + for (int i = 0; i < helper.ViewData.Model.Locales.Count; i++) { var locale = helper.ViewData.Model.Locales[i]; - var language = EngineContext.Current.Resolve().GetLanguageById(locale.LanguageId); - x.Add().Text(language.Name) - .Content(localizedTemplate(i).ToHtmlString()) - .ImageUrl("~/Content/images/flags/" + language.FlagImageFileName); + var language = languageService.GetLanguageById(locale.LanguageId); + + x.Add().Text(language.Name) + .LinkHtmlAttributes(new { title = language.Name }) + .ContentHtmlAttributes(new { @class = "locale-editor-content", data_lang = language.LanguageCulture, data_rtl = language.Rtl.ToString().ToLower() }) + .Content(localizedTemplate(i)) + .ImageUrl("~/Content/images/flags/" + language.FlagImageFileName) + .Selected(i == 0 && standardTemplate == null); } }).ToHtmlString(); writer.Write(tabStrip); writer.Write("
    "); } - else + else if (standardTemplate != null) { standardTemplate(helper.ViewData.Model).WriteTo(writer); } @@ -92,20 +109,21 @@ public static MvcHtmlString DeleteConfirmation(this HtmlHelper helper, str return DeleteConfirmation(helper, "", buttonsSelector); } - // Adds an action name parameter for using other delete action names - public static MvcHtmlString DeleteConfirmation(this HtmlHelper helper, string actionName, string buttonsSelector = null) where T : EntityModelBase + /// + /// Adds an action name parameter for using other delete action names + /// + /// + /// + /// + /// + /// + public static MvcHtmlString DeleteConfirmation(this HtmlHelper helper, string actionName, string buttonsSelector = null) where T : EntityModelBase { if (String.IsNullOrEmpty(actionName)) actionName = "Delete"; var modalId = MvcHtmlString.Create(helper.ViewData.ModelMetadata.ModelType.Name.ToLower() + "-delete-confirmation").ToHtmlString(); - string script = ""; - if (!string.IsNullOrEmpty(buttonsSelector)) - { - script = "\n"; - } - var deleteConfirmationModel = new DeleteConfirmationModel { Id = helper.ViewData.Model.Id, @@ -116,31 +134,45 @@ public static MvcHtmlString DeleteConfirmation(this HtmlHelper helper, str EntityType = buttonsSelector.Replace("-delete", "") }; - var window = helper.SmartStore().Window().Name(modalId) - .Title(EngineContext.Current.Resolve().GetResource("Admin.Common.AreYouSure")) - .Modal(true) - .Visible(false) - .Content(helper.Partial("Delete", deleteConfirmationModel).ToHtmlString()) - .ToHtmlString(); + var script = string.Empty; + if (buttonsSelector.HasValue()) + { + script = "\n"; + } + + helper.SmartStore().Window().Name(modalId) + .Title(EngineContext.Current.Resolve().GetResource("Admin.Common.AreYouSure")) + .Content(helper.Partial("Delete", deleteConfirmationModel).ToHtmlString()) + .Show(false) + .Render(); - return MvcHtmlString.Create(script + window); + return new MvcHtmlString(script); } public static MvcHtmlString SmartLabel(this HtmlHelper helper, string expression, string labelText, string hint = null, object htmlAttributes = null) { var result = new StringBuilder(); - var label = helper.Label(expression, labelText, htmlAttributes); - result.Append("
    "); - { - result.Append(label); - if (hint.HasValue()) - { - result.Append(helper.Hint(hint).ToHtmlString()); - } - } - result.Append("
    "); + + if (expression.IsEmpty() && labelText.IsEmpty()) + { + result.Append(""); + } + else + { + var labelAttrs = new RouteValueDictionary(htmlAttributes); + var label = helper.Label(expression, labelText, labelAttrs); + + result.Append(label); + } + + if (hint.HasValue()) + { + result.Append(helper.Hint(hint).ToHtmlString()); + } + + result.Append("
    "); return MvcHtmlString.Create(result.ToString()); } @@ -152,8 +184,7 @@ public static MvcHtmlString SmartLabelFor( object htmlAttributes = null) { var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData); - object resourceDisplayName = null; - metadata.AdditionalValues.TryGetValue("SmartResourceDisplayName", out resourceDisplayName); + metadata.AdditionalValues.TryGetValue("SmartResourceDisplayName", out object resourceDisplayName); return SmartLabelFor(helper, expression, resourceDisplayName as SmartResourceDisplayName, metadata, displayHint, htmlAttributes); } @@ -165,7 +196,7 @@ public static MvcHtmlString SmartLabelFor( bool displayHint = true, object htmlAttributes = null) { - Guard.ArgumentNotEmpty(() => resourceKey); + Guard.NotEmpty(resourceKey, nameof(resourceKey)); var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData); var resourceDisplayName = new SmartResourceDisplayName(resourceKey, metadata.PropertyName); @@ -208,7 +239,10 @@ private static MvcHtmlString SmartLabelFor( labelText = metadata.PropertyName.SplitPascalCase(); } - var label = helper.LabelFor(expression, labelText, htmlAttributes); + var labelAttrs = new RouteValueDictionary(htmlAttributes); + //labelAttrs.AppendCssClass("col-form-label"); + + var label = helper.LabelFor(expression, labelText, labelAttrs); if (displayHint) { @@ -262,12 +296,21 @@ public static MvcHtmlString DatePickerDropDowns(this HtmlHelper html, int? beginYear = null, int? endYear = null, int? selectedDay = null, int? selectedMonth = null, int? selectedYear = null, bool localizeLabels = true, bool disabled = false) { - var daysList = new TagBuilder("select"); - daysList.MergeAttribute("style", "width: 70px"); + var row = new TagBuilder("div"); + row.AddCssClass("row xs-gutters"); + + var daysCol = new TagBuilder("div"); + daysCol.AddCssClass("col"); + + var monthsCol = new TagBuilder("div"); + monthsCol.AddCssClass("col"); + + var yearsCol = new TagBuilder("div"); + yearsCol.AddCssClass("col"); + + var daysList = new TagBuilder("select"); var monthsList = new TagBuilder("select"); - monthsList.MergeAttribute("style", "width: 130px"); var yearsList = new TagBuilder("select"); - yearsList.MergeAttribute("style", "width: 90px"); daysList.Attributes.Add("data-native-menu", "false"); monthsList.Attributes.Add("data-native-menu", "false"); @@ -277,13 +320,13 @@ public static MvcHtmlString DatePickerDropDowns(this HtmlHelper html, monthsList.Attributes.Add("name", monthName); yearsList.Attributes.Add("name", yearName); - daysList.Attributes.Add("class", "date-part"); - monthsList.Attributes.Add("class", "date-part"); - yearsList.Attributes.Add("class", "date-part"); + daysList.Attributes.Add("class", "date-part form-control noskin"); + monthsList.Attributes.Add("class", "date-part form-control noskin"); + yearsList.Attributes.Add("class", "date-part form-control noskin"); - daysList.Attributes.Add("data-select-min-results-for-search", "100"); - monthsList.Attributes.Add("data-select-min-results-for-search", "100"); - //yearsList.Attributes.Add("data-select-min-results-for-search", "100"); + daysList.Attributes.Add("data-minimum-results-for-search", "100"); + monthsList.Attributes.Add("data-minimum-results-for-search", "100"); + //yearsList.Attributes.Add("data-minimum-results-for-search", "100"); if (disabled) { @@ -342,7 +385,13 @@ public static MvcHtmlString DatePickerDropDowns(this HtmlHelper html, monthsList.InnerHtml = months.ToString(); yearsList.InnerHtml = years.ToString(); - return MvcHtmlString.Create(string.Concat(daysList, monthsList, yearsList)); + daysCol.InnerHtml = daysList.ToString(); + monthsCol.InnerHtml = monthsList.ToString(); + yearsCol.InnerHtml = yearsList.ToString(); + + row.InnerHtml = string.Concat(daysCol, monthsCol, yearsCol); + + return MvcHtmlString.Create(row.ToString()); } #region DropDownList Extensions @@ -414,6 +463,31 @@ public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone) public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, object model) { + var routeValues = GetWidgetsByZoneRouteValues(helper, widgetZone, model); + if (routeValues != null) + { + return helper.Action("WidgetsByZone", "Widget", routeValues); + } + + return MvcHtmlString.Empty; + } + + public static void RenderWidget(this HtmlHelper helper, string widgetZone) + { + helper.RenderWidget(widgetZone, null); + } + + public static void RenderWidget(this HtmlHelper helper, string widgetZone, object model) + { + var routeValues = GetWidgetsByZoneRouteValues(helper, widgetZone, model); + if (routeValues != null) + { + helper.RenderAction("WidgetsByZone", "Widget", routeValues); + } + } + + private static object GetWidgetsByZoneRouteValues(HtmlHelper helper, string widgetZone, object model) + { if (widgetZone.HasValue()) { model = model ?? helper.ViewData.Model; @@ -422,26 +496,70 @@ public static MvcHtmlString Widget(this HtmlHelper helper, string widgetZone, ob if (widgets.Any()) { var zoneModel = new WidgetZoneModel { Widgets = widgets, WidgetZone = widgetZone, Model = model }; - var result = helper.Action("WidgetsByZone", "Widget", new { zoneModel = zoneModel, model = model, area = "" }); - return result; + return new { zoneModel = zoneModel, model = model, area = "" }; } } - return MvcHtmlString.Create(""); + return null; } - public static IHtmlString MetaAcceptLanguage(this HtmlHelper html) + public static IHtmlString MetaAcceptLanguage(this HtmlHelper html) { var acceptLang = HttpUtility.HtmlAttributeEncode(Thread.CurrentThread.CurrentUICulture.ToString()); - return new HtmlString(string.Format("", acceptLang)); + return new MvcHtmlString(string.Format("", acceptLang)); } - public static MvcHtmlString ControlGroupFor( + public static IHtmlString LanguageAttributes(this HtmlHelper html, bool omitLTR = false) + { + return LanguageAttributes(html, EngineContext.Current.Resolve().WorkingLanguage, omitLTR); + } + + public static IHtmlString LanguageAttributes(this HtmlHelper html, LocalizedValue localizedValue) + { + if (localizedValue == null || !localizedValue.BidiOverride) + { + return MvcHtmlString.Empty; + } + + return LanguageAttributes(html, localizedValue.CurrentLanguage, false); + } + + public static IHtmlString LanguageAttributes(this HtmlHelper html, Language currentLanguage, bool omitLTR = false) + { + Guard.NotNull(currentLanguage, nameof(currentLanguage)); + + var code = currentLanguage.GetTwoLetterISOLanguageName(); + var rtl = currentLanguage.Rtl; + + var result = "lang=\"" + code + "\""; + if (rtl || !omitLTR) + { + result += " dir=\"" + (rtl ? "rtl" : "ltr") + "\""; + } + + return new MvcHtmlString(result); + } + + public static IHtmlString LanguageAttributes(this HtmlHelper html, bool currentRtl, Language pageLanguage) + { + Guard.NotNull(pageLanguage, nameof(pageLanguage)); + + if (currentRtl == pageLanguage.Rtl) + { + return MvcHtmlString.Empty; + } + + var result = "dir=\"" + (currentRtl ? "rtl" : "ltr") + "\""; + return new MvcHtmlString(result); + } + + public static MvcHtmlString ControlGroupFor( this HtmlHelper html, Expression> expression, InputEditorType editorType = InputEditorType.TextBox, bool required = false, - string helpHint = null) + string helpHint = null, + string breakpoint = "md") { if (editorType == InputEditorType.Hidden) { @@ -450,18 +568,21 @@ public static MvcHtmlString ControlGroupFor( string inputHtml = ""; var htmlAttributes = new RouteValueDictionary(); - var dataTypeName = ModelMetadata.FromLambdaExpression(expression, html.ViewData).DataTypeName.EmptyNull(); - - var sb = new StringBuilder("
    "); + var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); + var dataTypeName = metadata.DataTypeName.EmptyNull(); + var groupClass = "form-group row"; + var labelClass = "col-{0}-3 col-form-label".FormatInvariant(breakpoint.NullEmpty() ?? "md"); + var controlsClass = "col-{0}-9".FormatInvariant(breakpoint.NullEmpty() ?? "md"); + var sb = new StringBuilder("
    ".FormatWith(groupClass)); if (editorType != InputEditorType.Checkbox) { - var className = "control-label" + (required ? " required" : ""); + var className = labelClass + (required ? " required" : ""); var fieldId = html.IdFor(expression).ToString(); sb.AppendLine(html.LabelFor(expression, new { @class = className, @for = fieldId }).ToString()); } - sb.AppendLine("
    "); + sb.AppendLine("
    ".FormatWith(controlsClass)); if (!required && (editorType == InputEditorType.TextBox || editorType == InputEditorType.Password)) { @@ -477,13 +598,17 @@ public static MvcHtmlString ControlGroupFor( htmlAttributes.Add("type", "tel"); break; } - - switch (editorType) + + htmlAttributes.Add("class", "form-control"); + + switch (editorType) { case InputEditorType.Checkbox: - inputHtml = string.Format("", - html.EditorFor(expression).ToString(), - ModelMetadata.FromLambdaExpression(expression, html.ViewData).DisplayName); // TBD: ist das OK so? + CommonHelper.TryConvert(metadata.Model, out var isChecked); + inputHtml = string.Format("
    {0}
    ", + html.CheckBox(ExpressionHelper.GetExpressionText(expression), isChecked, new { @class = "form-check-input" }).ToString(), + html.IdFor(expression), + metadata.DisplayName); break; case InputEditorType.Password: inputHtml = html.PasswordFor(expression, htmlAttributes).ToString(); @@ -492,12 +617,12 @@ public static MvcHtmlString ControlGroupFor( inputHtml = html.TextBoxFor(expression, htmlAttributes).ToString(); break; } - + sb.AppendLine(inputHtml); sb.AppendLine(html.ValidationMessageFor(expression).ToString()); if (helpHint.HasValue()) { - sb.AppendLine(string.Format("
    {0}
    ", helpHint)); + sb.AppendLine(string.Format("
    {0}
    ", helpHint)); } sb.AppendLine("
    "); // div.controls @@ -513,22 +638,17 @@ public static MvcHtmlString ColorBox(this HtmlHelper html, string name, string c public static MvcHtmlString ColorBox(this HtmlHelper html, string name, string color, string defaultColor) { - var sb = new StringBuilder(); + var sb = new StringBuilder(); defaultColor = defaultColor.EmptyNull(); var isDefault = color.IsCaseInsensitiveEqual(defaultColor); - sb.AppendFormat("", color); - - sb.AppendFormat(html.TextBox(name, isDefault ? "" : color, new { @class = "span2 colorval", placeholder = defaultColor }).ToHtmlString()); - sb.AppendFormat("", color); + sb.Append("
    ".FormatInvariant(defaultColor)); - sb.Append(""); + sb.AppendFormat(html.TextBox(name, isDefault ? "" : color, new { @class = "form-control colorval", placeholder = defaultColor }).ToHtmlString()); + sb.AppendFormat("
     
    ", defaultColor.HasValue() ? "background-color: " + defaultColor : ""); - var bootstrapJsRoot = "~/Content/bootstrap/js/"; - html.AppendScriptParts(false, - bootstrapJsRoot + "custom/bootstrap-colorpicker.js", - bootstrapJsRoot + "custom/bootstrap-colorpicker-globalinit.js"); + sb.Append("
    "); return MvcHtmlString.Create(sb.ToString()); } @@ -559,51 +679,126 @@ public static MvcHtmlString TableFormattedVariantAttributes(this HtmlHelper help return MvcHtmlString.Create(sb.ToString()); } - public static MvcHtmlString SettingOverrideCheckbox(this HtmlHelper helper, - Expression> expression, string parentSelector = null) - { - var data = helper.ViewData[StoreDependingSettingHelper.ViewDataKey] as StoreDependingSettingData; + //public static MvcHtmlString SettingEditorFor( + // this HtmlHelper helper, + // Expression> expression, + // string parentSelector = null, + // object additionalViewData = null) + //{ + // var editor = helper.EditorFor(expression, additionalViewData); + + // var data = helper.ViewData[StoreDependingSettingHelper.ViewDataKey] as StoreDependingSettingData; + // if (data == null || data.ActiveStoreScopeConfiguration <= 0) + // return editor; // CONTROL + + // var sb = new StringBuilder("
    "); + // sb.Append("
    "); + // sb.Append(helper.SettingOverrideCheckboxInternal(expression, data, parentSelector)); // CHECK + // sb.Append("
    "); + // sb.Append("
    "); + // sb.Append(editor); // CONTROL + // sb.Append("
    "); + + // return MvcHtmlString.Create(sb.ToString()); + //} - if (data != null && data.ActiveStoreScopeConfiguration > 0) - { - var settingKey = ExpressionHelper.GetExpressionText(expression); - var localizeService = EngineContext.Current.Resolve(); + public static MvcHtmlString SettingEditorFor( + this HtmlHelper helper, + Expression> expression, + string parentSelector = null, + object additionalViewData = null) + { + return SettingEditorFor( + helper, + expression, + helper.EditorFor(expression, additionalViewData), + parentSelector); + } - if (!settingKey.Contains(".")) - settingKey = data.RootSettingClass + "." + settingKey; + public static MvcHtmlString SettingEditorFor( + this HtmlHelper helper, + Expression> expression, + Func editor, + string parentSelector = null) + { + return SettingEditorFor( + helper, + expression, + new MvcHtmlString(editor(helper.ViewData.Model).ToHtmlString()), + parentSelector); + } - var overrideForStore = (data.OverrideSettingKeys.FirstOrDefault(x => x.IsCaseInsensitiveEqual(settingKey)) != null); - var fieldId = settingKey + (settingKey.EndsWith("_OverrideForStore") ? "" : "_OverrideForStore"); + public static MvcHtmlString EnumSettingEditorFor( + this HtmlHelper helper, + Expression> expression, + string parentSelector = null, + object htmlAttributes = null, + string optionLabel = null) where TValue : struct + { + return SettingEditorFor( + helper, + expression, + helper.DropDownListForEnum(expression, htmlAttributes, optionLabel), + parentSelector); + } - var sb = new StringBuilder(); - sb.Append("
    "); + public static MvcHtmlString SettingEditorFor( + this HtmlHelper helper, + Expression> expression, + MvcHtmlString editor, + string parentSelector = null) + { + Guard.NotNull(expression, nameof(expression)); + Guard.NotNull(editor, nameof(editor)); - sb.AppendFormat("", parentSelector.EmptyNull(), overrideForStore ? " checked=\"checked\"" : ""); + var data = helper.ViewData[StoreDependingSettingHelper.ViewDataKey] as StoreDependingSettingData; + if (data == null || data.ActiveStoreScopeConfiguration <= 0) + return editor; // CONTROL - sb.AppendFormat(""); - sb.Append("
    \r\n"); // controls are not floating, so line-break prevents different distances between them + var sb = new StringBuilder("
    "); + sb.Append("
    "); + sb.Append(helper.SettingOverrideCheckboxInternal(expression, data, parentSelector)); // CHECK + sb.Append("
    "); + sb.Append("
    "); + sb.Append(editor.ToHtmlString()); // CONTROL + sb.Append("
    "); - return MvcHtmlString.Create(sb.ToString()); - } - return MvcHtmlString.Empty; + return MvcHtmlString.Create(sb.ToString()); } - public static MvcHtmlString SettingEditorFor( - this HtmlHelper helper, - Expression> expression, - string parentSelector = null, - object additionalViewData = null) + private static MvcHtmlString SettingOverrideCheckboxInternal( + this HtmlHelper helper, + Expression> expression, + StoreDependingSettingData data, + string parentSelector = null) { - var checkbox = helper.SettingOverrideCheckbox(expression, parentSelector); - var editor = helper.EditorFor(expression, additionalViewData); + var fieldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; + var settingKey = ExpressionHelper.GetExpressionText(expression); + var localizeService = EngineContext.Current.Resolve(); + + if (fieldPrefix.HasValue()) + settingKey = string.Concat(fieldPrefix, ".", settingKey); + else if (!settingKey.Contains(".")) + settingKey = string.Concat(data.RootSettingClass, ".", settingKey); - return MvcHtmlString.Create(checkbox.ToString() + editor.ToString()); + var overrideForStore = (data.OverrideSettingKeys.FirstOrDefault(x => x.IsCaseInsensitiveEqual(settingKey)) != null); + var fieldId = settingKey + (settingKey.EndsWith("_OverrideForStore") ? "" : "_OverrideForStore"); + + var sb = new StringBuilder(); + sb.Append(""); + // Controls are not floating, so line-break prevents different distances between them. + sb.Append("\r\n"); + + return MvcHtmlString.Create(sb.ToString()); } public static MvcHtmlString CollapsedText(this HtmlHelper helper, string text) @@ -616,12 +811,8 @@ public static MvcHtmlString CollapsedText(this HtmlHelper helper, string text) if (!catalogSettings.EnableHtmlTextCollapser) return MvcHtmlString.Create(text); - string options = "{{\"adjustheight\":{0}}}".FormatWith( - catalogSettings.HtmlTextCollapsedHeight - ); - - string result = "
    {1}
    ".FormatWith( - options, text + string result = "
    {1}
    ".FormatWith( + catalogSettings.HtmlTextCollapsedHeight, text ); return MvcHtmlString.Create(result); @@ -634,10 +825,10 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string fileExtension, string extraCssClasses = null, bool renderLabel = false) { - Guard.ArgumentNotNull(() => helper); - Guard.ArgumentNotEmpty(() => fileExtension); + Guard.NotNull(helper, nameof(helper)); + Guard.NotEmpty(fileExtension, nameof(fileExtension)); - var icon = "file-o"; + var icon = "far fa-file"; var ext = fileExtension; if (ext != null && ext.StartsWith(".")) @@ -650,7 +841,7 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string switch (ext.ToLowerInvariant()) { case "pdf": - icon = "file-pdf-o"; + icon = "far fa-file-pdf"; break; case "doc": case "docx": @@ -659,18 +850,18 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string case "dot": case "dotx": case "dotm": - icon = "file-word-o"; + icon = "far fa-file-word"; break; case "xls": case "xlsx": case "xlsm": case "xlsb": case "ods": - icon = "file-excel-o"; + icon = "far fa-file-excel"; break; case "csv": case "tab": - icon = "table"; + icon = "fa fa-file-csv"; break; case "ppt": case "pptx": @@ -682,25 +873,25 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string case "potm": case "pps": case "ppsm": - icon = "file-powerpoint-o"; + icon = "far fa-file-powerpoint"; break; case "zip": case "rar": case "7z": - icon = "file-archive-o"; + icon = "far fa-file-archive"; break; case "png": case "jpg": case "jpeg": case "bmp": case "psd": - icon = "file-image-o"; + icon = "far fa-file-image"; break; case "mp3": case "wav": case "ogg": case "wma": - icon = "file-audio-o"; + icon = "far fa-file-audio"; break; case "mp4": case "mkv": @@ -709,25 +900,25 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string case "asf": case "mpg": case "mpeg": - icon = "file-video-o"; + icon = "far fa-file-video"; break; case "txt": - icon = "file-text-o"; + icon = "far fa-file-alt"; break; case "exe": - icon = "gear"; + icon = "fa fa-cog"; break; case "xml": case "html": case "htm": - icon = "file-code-o"; + icon = "far fa-file-code"; break; } } var label = ext.NaIfEmpty().ToUpper(); - var result = "".FormatInvariant( + var result = "".FormatInvariant( icon, extraCssClasses.HasValue() ? " " + extraCssClasses : "", label); @@ -736,11 +927,11 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string { if (ext.IsEmpty()) { - result = "{0}".FormatInvariant("".NaIfEmpty()); + result = "{0}".FormatInvariant("".NaIfEmpty()); } else { - result = result + "{0}".FormatInvariant(label); + result = result + "{0}".FormatInvariant(label); } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs index 33455155a2..6644ac91bc 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs @@ -1,13 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Web.Mvc; using SmartStore.Core; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Plugins; using SmartStore.Services.Localization; +using SmartStore.Services.Payments; +using SmartStore.Web.Framework.Plugins; namespace SmartStore.Web.Framework { @@ -32,21 +34,87 @@ public static SelectList ToSelectList(this TEnum enumObj, bool markCurren } /// - /// Get a list of all stores + /// Get a select list of all stores /// - public static IList ToSelectListItems(this IEnumerable stores) + public static IList ToSelectListItems(this IEnumerable stores, params int[] selectedStoreIds) { - var lst = new List(); + var list = new List(); foreach (var store in stores) { - lst.Add(new SelectListItem + list.Add(new SelectListItem { Text = store.Name, - Value = store.Id.ToString() + Value = store.Id.ToString(), + Selected = selectedStoreIds != null && selectedStoreIds.Contains(store.Id) }); } - return lst; + + return list; + } + + /// + /// Get a select list of all customer roles + /// + public static IList ToSelectListItems(this IEnumerable roles, params int[] selectedCustomerRoleIds) + { + var list = new List(); + + foreach (var role in roles) + { + list.Add(new SelectListItem + { + Text = role.Name, + Value = role.Id.ToString(), + Selected = selectedCustomerRoleIds != null && selectedCustomerRoleIds.Contains(role.Id) + }); + } + + return list; + } + + /// + /// Get a select list of all payment methods. + /// + /// Payment providers. + /// Plugin mediator. + /// System name of selected methods. + /// List of select items + public static IList ToSelectListItems( + this IEnumerable> paymentProviders, + PluginMediator pluginMediator, + params string[] selectedMethods) + { + var list = new List(); + + foreach (var provider in paymentProviders) + { + var systemName = provider.Metadata.SystemName; + var name = pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + + if (name.IsEmpty()) + { + name = provider.Metadata.FriendlyName; + } + + if (name.IsEmpty()) + { + name = provider.Metadata.SystemName; + } + + var item = new ExtendedSelectListItem + { + Text = name.NaIfEmpty(), + Value = systemName, + Selected = selectedMethods != null && selectedMethods.Contains(systemName) + }; + + item.CustomProperties.Add("hint", systemName.Replace("SmartStore.", "").Replace("Payments.", "")); + + list.Add(item); + } + + return list.OrderBy(x => x.Text).ToList(); } public static void SelectValue(this List lst, string value, string defaultValue = null) @@ -63,4 +131,25 @@ public static void SelectValue(this List lst, string value, stri } } } + + + public partial class ExtendedSelectListItem : SelectListItem + { + public ExtendedSelectListItem() + { + CustomProperties = new Dictionary(); + } + + public Dictionary CustomProperties { get; set; } + + public TProperty Get(string key, TProperty defaultValue = default(TProperty)) + { + if (CustomProperties.TryGetValue(key, out object value)) + { + return (TProperty)value; + } + + return defaultValue; + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs new file mode 100644 index 0000000000..fd9b6f3717 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlZoneExtensions.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using SmartStore.Collections; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework +{ + public enum ZoneInjectMode + { + Append, + Prepend, + Replace + } + + public static class HtmlZoneExtensions + { + private class DocumentZone : IDisposable + { + internal const string ItemsKey = "DocumentTail.Snippets"; + private const string UniqueKeysKey = "DocumentTail.UniqueKeys"; + + private readonly ViewContext _viewContext; + private readonly TextWriter _originalViewContextWriter; + private readonly WebViewPage _page; + private readonly string _targetZone; + private readonly ZoneInjectMode _injectMode; + private readonly bool _isVoid; + + public DocumentZone(HtmlHelper html, string targetZone, ZoneInjectMode injectMode, string key) + { + Guard.NotEmpty(targetZone, nameof(targetZone)); + + _viewContext = html.ViewContext; + _originalViewContextWriter = _viewContext.Writer; + _page = (WebViewPage)html.ViewDataContainer; + + var writer = new StringWriter(); + _page.OutputStack.Push(writer); + _viewContext.Writer = writer; + + _targetZone = targetZone; + _injectMode = injectMode; + + if (key.HasValue()) + { + if (HasUniqueKey(key)) + { + _isVoid = true; + } + else + { + UniqueKeys.Add(key); + } + } + + if (_page.Request.IsAjaxRequest()) + { + _isVoid = true; + } + } + + public static IEnumerable GetZoneContent(string zone) + { + Guard.NotEmpty(zone, nameof(zone)); + + var storage = HttpContext.Current?.GetItem>(ItemsKey, forceCreation: false); + return storage?[zone]; + } + + public static bool HasUniqueKey(string key) + { + Guard.NotEmpty(key, nameof(key)); + + var uniqueKeys = HttpContext.Current?.GetItem>(UniqueKeysKey, forceCreation: false); + return uniqueKeys != null && uniqueKeys.Contains(key); + } + + private static HashSet UniqueKeys + { + get + { + return HttpContext.Current?.GetItem>(UniqueKeysKey, () => new HashSet(StringComparer.OrdinalIgnoreCase), true); + } + } + + private Multimap GetStorage() + { + return _page.ViewContext?.HttpContext?.GetItem>(ItemsKey, () => new Multimap(StringComparer.OrdinalIgnoreCase)); + } + + public void Dispose() + { + var storage = GetStorage(); + if (storage == null) + return; + + var writer = ((StringWriter)_page.OutputStack.Pop()); + var content = _isVoid ? string.Empty : writer.ToString(); + + _viewContext.Writer = _originalViewContextWriter; + + if (_injectMode == ZoneInjectMode.Append) + { + storage[_targetZone].Add(content); + } + else if (_injectMode == ZoneInjectMode.Prepend) + { + var list = new List(storage[_targetZone]); + list.Insert(0, content); + storage.RemoveAll(_targetZone); + storage[_targetZone].AddRange(list); + } + else // Replace + { + storage.RemoveAll(_targetZone); + storage[_targetZone].Add(content); + } + } + } + + public static IDisposable BeginZoneContent(this HtmlHelper helper, + string targetZone, + ZoneInjectMode injectMode = ZoneInjectMode.Append, + string key = null) + { + return new DocumentZone(helper, targetZone, injectMode, key); + } + + public static void RenderZone(this HtmlHelper helper, string zone) + { + var snippets = DocumentZone.GetZoneContent(zone); + if (snippets == null) + { + return; + } + + foreach (var snippet in snippets) + { + helper.ViewContext.Writer.WriteLine(snippet); + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs index f01379faec..56dba19bee 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HttpExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.IO; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -12,7 +11,6 @@ namespace SmartStore { public static class HttpExtensions { - public static bool IsAdminArea(this HttpRequest request) { if (request != null) @@ -27,16 +25,17 @@ public static bool IsAdminArea(this HttpRequestBase request) { try { - if (request != null) + // TODO: not really reliable. Change this. + var area = request?.RequestContext?.RouteData?.GetAreaName(); + if (area != null) { - var area = request.RequestContext.RouteData.GetAreaName(); - if (area != null) - { - return area.IsCaseInsensitiveEqual("admin"); - } + return area.IsCaseInsensitiveEqual("admin"); + } + else + { + var paths = new[] { "~/administration/", "~/admin/" }; + return paths.Any(x => request?.AppRelativeCurrentExecutionFilePath?.StartsWith(x, StringComparison.OrdinalIgnoreCase) == true); } - - return false; } catch { @@ -58,13 +57,9 @@ public static bool IsPublicArea(this HttpRequestBase request) { try { - if (request != null) - { - var area = request.RequestContext.RouteData.GetAreaName(); - return area.IsEmpty(); - } - - return false; + // TODO: not really reliable. Change this. + var area = request?.RequestContext?.RouteData?.GetAreaName(); + return area.IsEmpty(); } catch { @@ -133,10 +128,9 @@ public static IEnumerable ToPostedFileResults(this HttpFileCol public static RouteData GetRouteData(this HttpContextBase httpContext) { - Guard.ArgumentNotNull(() => httpContext); + Guard.NotNull(httpContext, nameof(httpContext)); - var handler = httpContext.Handler as MvcHandler; - if (handler != null && handler.RequestContext != null) + if (httpContext.Handler is MvcHandler handler && handler.RequestContext != null) { return handler.RequestContext.RouteData; } @@ -152,8 +146,8 @@ public static bool TryGetRouteData(this HttpContextBase httpContext, out RouteDa public static CheckoutState GetCheckoutState(this HttpContextBase httpContext) { - Guard.ArgumentNotNull(() => httpContext); - + Guard.NotNull(httpContext, nameof(httpContext)); + var state = httpContext.Session.SafeGetValue(CheckoutState.CheckoutStateSessionKey); if (state != null) @@ -167,7 +161,7 @@ public static CheckoutState GetCheckoutState(this HttpContextBase httpContext) public static void RemoveCheckoutState(this HttpContextBase httpContext) { - Guard.ArgumentNotNull(() => httpContext); + Guard.NotNull(httpContext, nameof(httpContext)); httpContext.Session.SafeRemove(CheckoutState.CheckoutStateSessionKey); } @@ -195,9 +189,11 @@ internal static void SetUserThemeChoiceInCookie(this HttpContextBase context, st if (value.HasValue() && cookie == null) { - cookie = new HttpCookie("sm.UserThemeChoice"); - cookie.HttpOnly = true; - cookie.Expires = DateTime.UtcNow.AddYears(1); + cookie = new HttpCookie("sm.UserThemeChoice") + { + HttpOnly = true, + Expires = DateTime.UtcNow.AddYears(1) + }; } if (value.HasValue()) @@ -219,16 +215,13 @@ internal static void SetUserThemeChoiceInCookie(this HttpContextBase context, st internal static HttpCookie GetPreviewModeCookie(this HttpContextBase context, bool createIfMissing) { - if (context == null) - return null; - - var cookie = context.Request.Cookies.Get("sm.PreviewModeOverrides"); + var httpRequest = context.SafeGetHttpRequest(); + var cookie = httpRequest?.Cookies?.Get("sm.PreviewModeOverrides"); - if (cookie == null && createIfMissing) + if (cookie == null && createIfMissing && httpRequest != null) { - cookie = new HttpCookie("sm.PreviewModeOverrides"); - cookie.HttpOnly = true; - context.Request.Cookies.Set(cookie); + cookie = new HttpCookie("sm.PreviewModeOverrides") { HttpOnly = true }; + httpRequest.Cookies.Set(cookie); } if (cookie != null) @@ -261,7 +254,8 @@ internal static void SetPreviewModeValue(this HttpContextBase context, string ke public static IDisposable PreviewModeCookie(this HttpContextBase context) { - var disposable = new ActionDisposable(() => { + var disposable = new ActionDisposable(() => + { var cookie = GetPreviewModeCookie(context, false); if (cookie != null) { @@ -281,5 +275,21 @@ public static IDisposable PreviewModeCookie(this HttpContextBase context) return disposable; } + public static string GetContentUrl(this HttpContextBase context, string path) + { + if (path.HasValue()) + { + if (!path.StartsWith("~")) + { + if (!path.StartsWith("/")) + path = "/" + path; + path = "~" + path; + } + + return UrlHelper.GenerateContentUrl(path, context); + } + + return path; + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/LocalizedValueExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/LocalizedValueExtensions.cs new file mode 100644 index 0000000000..ffc474ff87 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/LocalizedValueExtensions.cs @@ -0,0 +1,27 @@ +using System; +using SmartStore.Services.Localization; + +namespace SmartStore.Web.Framework +{ + /// + /// Wrapper for the most common string extension helpers used in views. + /// Just here to avoid runtime exceptions in views after refactoring GetLocalized() helper. + /// + public static class LocalizedValueExtensions + { + public static bool HasValue(this LocalizedValue value) + { + return value?.Value?.HasValue() == true; + } + + public static bool IsEmpty(this LocalizedValue value) + { + return value?.Value?.IsEmpty() == false; + } + + public static string Truncate(this LocalizedValue value, int maxLength, string suffix = "") + { + return value?.Value?.Truncate(maxLength, suffix); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/NameValueCollectionExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/NameValueCollectionExtensions.cs index 6f065499cf..87ba980283 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/NameValueCollectionExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/NameValueCollectionExtensions.cs @@ -9,7 +9,7 @@ public static class NameValueCollectionExtension { public static IDictionary ToDictionary(this NameValueCollection collection) { - Guard.ArgumentNotNull(() => collection); + Guard.NotNull(collection, nameof(collection)); var query = from key in collection.AllKeys where key != null diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/PostedFileResult.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/PostedFileResult.cs index 9ba2b0ed2d..96e62e96d6 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/PostedFileResult.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/PostedFileResult.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using System.Web; using SmartStore.Core.IO; @@ -20,10 +16,11 @@ public class PostedFileResult private string _fileTitle; private string _fileExt; private bool? _isImage; + private byte[] _buffer; public PostedFileResult(HttpPostedFileBase httpFile) { - Guard.ArgumentNotNull(() => httpFile); + Guard.NotNull(httpFile, nameof(httpFile)); this._httpFile = httpFile; @@ -167,7 +164,12 @@ public byte[] Buffer { get { - return _httpFile.InputStream.ToByteArray(); + if (_buffer == null) + { + _buffer = _httpFile.InputStream.ToByteArray(); + } + + return _buffer; } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs index 70a62d8dd7..898e7a2923 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs @@ -4,15 +4,6 @@ using System.Linq; using System.Reflection; using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Localization; -using SmartStore.Core.Plugins; -using SmartStore.Services.Configuration; -using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Localization; using Telerik.Web.Mvc; using Telerik.Web.Mvc.Extensions; using Telerik.Web.Mvc.UI.Fluent; @@ -22,8 +13,18 @@ namespace SmartStore.Web.Framework [Serializable] public class GridStateInfo { - public GridState State { get; set; } + public GridStateInfo.GridState State { get; set; } public string Path { get; set; } + + [Serializable] + public class GridState + { + public string Filter { get; set; } + public string GroupBy { get; set; } + public string OrderBy { get; set; } + public int Page { get; set; } + public int Size { get; set; } + } } public static class TelerikExtensions @@ -70,6 +71,8 @@ public static GridBuilder PreserveGridState(this GridBuilder builder) w if (grid.Sorting.Enabled) { + grid.Sorting.OrderBy.Clear(); + foreach (var sort in command.SortDescriptors) { var existingSort = grid.Sorting.OrderBy.FirstOrDefault(x => x.Member.IsCaseInsensitiveEqual(sort.Member)); @@ -83,6 +86,8 @@ public static GridBuilder PreserveGridState(this GridBuilder builder) w if (grid.Grouping.Enabled) { + grid.Grouping.Groups.Clear(); + foreach (var group in command.GroupDescriptors) { var existingGroup = grid.Grouping.Groups.FirstOrDefault(x => x.Member.IsCaseInsensitiveEqual(group.Member)); @@ -96,6 +101,8 @@ public static GridBuilder PreserveGridState(this GridBuilder builder) w if (grid.Filtering.Enabled) { + grid.Filtering.Filters.Clear(); + foreach (var filter in command.FilterDescriptors) { var compositeFilter = filter as CompositeFilterDescriptor; @@ -116,20 +123,20 @@ public static GridBuilder PreserveGridState(this GridBuilder builder) w public static IEnumerable ForCommand(this IEnumerable current, GridCommand command) { - var queryable = current.AsQueryable() as IQueryable; + var query = current.AsQueryable() as IQueryable; if (command.FilterDescriptors.Any()) { - queryable = queryable.Where(command.FilterDescriptors.AsEnumerable()).AsQueryable() as IQueryable; + query = query.Where(command.FilterDescriptors.AsEnumerable()).AsQueryable() as IQueryable; } IList temporarySortDescriptors = new List(); - if (!command.SortDescriptors.Any() && queryable.Provider.IsEntityFrameworkProvider()) + if (!command.SortDescriptors.Any() && query.Provider.IsEntityFrameworkProvider()) { // The Entity Framework provider demands OrderBy before calling Skip. SortDescriptor sortDescriptor = new SortDescriptor { - Member = GetFirstSortableProperty(queryable.ElementType) + Member = GetFirstSortableProperty(query.ElementType) }; command.SortDescriptors.Add(sortDescriptor); temporarySortDescriptors.Add(sortDescriptor); @@ -152,10 +159,10 @@ public static IEnumerable ForCommand(this IEnumerable current, GridComm if (command.SortDescriptors.Any()) { - queryable = queryable.Sort(command.SortDescriptors); + query = query.Sort(command.SortDescriptors); } - return queryable as IQueryable; + return query as IQueryable; } public static IEnumerable PagedForCommand(this IEnumerable current, GridCommand command) diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/UrlHelperExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/UrlHelperExtensions.cs index cc1426f8fc..fdb1f1d332 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/UrlHelperExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/UrlHelperExtensions.cs @@ -1,4 +1,9 @@ -using System.Web.Mvc; +using System; +using System.Web.Mvc; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Infrastructure; +using SmartStore.Services.Cms; +using SmartStore.Services.Media; namespace SmartStore.Web.Framework { @@ -7,14 +12,20 @@ public static class UrlHelperExtensions public static string LogOn(this UrlHelper urlHelper, string returnUrl) { if (!string.IsNullOrEmpty(returnUrl)) - return urlHelper.Action("Login", "Customer", new { ReturnUrl = returnUrl, area = "" }); + { + return urlHelper.Action("Login", "Customer", new { returnUrl, area = "" }); + } + return urlHelper.Action("Login", "Customer", new { area = "" }); } public static string LogOff(this UrlHelper urlHelper, string returnUrl) { if (!string.IsNullOrEmpty(returnUrl)) - return urlHelper.Action("Logout", "Customer", new { ReturnUrl = returnUrl, area = "" }); + { + return urlHelper.Action("Logout", "Customer", new { returnUrl, area = "" }); + } + return urlHelper.Action("Logout", "Customer", new { area = "" }); } @@ -28,5 +39,105 @@ public static string Referrer(this UrlHelper urlHelper, string fallbackUrl = "") return fallbackUrl; } - } + + public static string Picture(this UrlHelper urlHelper, int? pictureId, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null) + { + var pictureService = EngineContext.Current.Resolve(); + return pictureService.GetUrl(pictureId.GetValueOrDefault(), targetSize, fallbackType, host); + } + + public static string Picture(this UrlHelper urlHelper, Picture picture, int targetSize = 0, FallbackPictureType fallbackType = FallbackPictureType.Entity, string host = null) + { + var pictureService = EngineContext.Current.Resolve(); + return pictureService.GetUrl(picture, targetSize, fallbackType, host); + } + + /// + /// Resolves a link to a topic page. + /// + /// The system name of the topic. + /// Link + /// + /// This method returns an empty string in following cases: + /// - the requested page does not exist. + /// - the current user has no permission to acces the page. + /// + public static string Topic(this UrlHelper urlHelper, string systemName, bool popup = false) + { + Guard.NotEmpty(systemName, nameof(systemName)); + + var expression = "topic:" + systemName; + if (popup) + { + expression += "?popup=true"; + } + + return Entity(urlHelper, expression); + } + + /// + /// Resolves a link label for a topic page. + /// The label is either the page short title or title. + /// + /// The system name of the topic. + /// Label + /// + /// This method returns an empty string if the requested page does not exist. + /// + public static string TopicLabel(this UrlHelper urlHelper, string systemName) + { + Guard.NotEmpty(systemName, nameof(systemName)); + + return EntityLabel(urlHelper, "topic:" + systemName); + } + + /// + /// Resolves a link to a system internal entity like product, topic, category or manufacturer. + /// + /// A link expression as supported by the + /// Link + /// + /// This method returns an empty string in following cases: + /// - the requested entity does not exist. + /// - the current user has no permission to acces the entity. + /// + public static string Entity(this UrlHelper urlHelper, string expression) + { + Guard.NotEmpty(expression, nameof(expression)); + + var linkResolver = EngineContext.Current.Resolve(); + var link = linkResolver.Resolve(expression); + + if (link.Status == LinkStatus.Ok) + { + return link.Link.EmptyNull(); + } + + return string.Empty; + } + + /// + /// Resolves a link label for a system internal entity like product, topic, category or manufacturer. + /// The label is either the entity short title, title or name, whichever is applicable. + /// + /// A link expression as supported by the + /// Label + /// + /// This method returns an empty string if the requested entity does not exist. + /// + public static string EntityLabel(this UrlHelper urlHelper, string expression) + { + Guard.NotEmpty(expression, nameof(expression)); + + var linkResolver = EngineContext.Current.Resolve(); + var link = linkResolver.Resolve(expression); + + if (link.Status == LinkStatus.Ok || link.Status == LinkStatus.Forbidden) + { + return link.Label; + } + + return string.Empty; + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/CheckAffiliateAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/CheckAffiliateAttribute.cs index ccbca493af..b9f2972f8f 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/CheckAffiliateAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/CheckAffiliateAttribute.cs @@ -7,13 +7,13 @@ namespace SmartStore.Web.Framework.Filters { - public class CheckAffiliateAttribute : ActionFilterAttribute + public class CheckAffiliateAttribute : FilterAttribute, IActionFilter { public Lazy AffiliateService { get; set; } public Lazy WorkContext { get; set; } public Lazy CustomerService { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext == null || filterContext.HttpContext == null) return; @@ -45,5 +45,9 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) } } } - } + + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/CompressAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/CompressAttribute.cs index a2f0849df8..bdbde8447f 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/CompressAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/CompressAttribute.cs @@ -11,24 +11,22 @@ public class CompressAttribute : ActionFilterAttribute public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = HttpContext.Current.Response; - string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"].EmptyNull().ToLower(); + var acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"].EmptyNull().ToLower(); if (acceptEncoding.Contains("gzip") && !(PreferDeflate && acceptEncoding.Contains("deflate"))) { response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); - response.Headers.Remove("Content-Encoding"); response.AppendHeader("Content-Encoding", "gzip"); } else if (acceptEncoding.Contains("deflate")) { response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); - response.Headers.Remove("Content-Encoding"); response.AppendHeader("Content-Encoding", "deflate"); } - //response.AppendHeader("Vary", "Content-Encoding"); + response.AppendHeader("Vary", "Content-Encoding"); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/CookieConsentFilter.cs b/src/Presentation/SmartStore.Web.Framework/Filters/CookieConsentFilter.cs new file mode 100644 index 0000000000..7d8b4e8751 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Filters/CookieConsentFilter.cs @@ -0,0 +1,152 @@ +using System; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Domain.Customers; +using SmartStore.Services; +using SmartStore.Services.Common; +using SmartStore.Web.Framework.UI; + +namespace SmartStore.Web.Framework.Filters +{ + public class CookieConsentFilter : IActionFilter, IResultFilter + { + private readonly IUserAgent _userAgent; + private readonly ICommonServices _services; + private readonly Lazy _widgetProvider; + private readonly PrivacySettings _privacySettings; + + public CookieConsentFilter( + IUserAgent userAgent, + ICommonServices services, + Lazy widgetProvider, + PrivacySettings privacySettings) + { + _userAgent = userAgent; + _services = services; + _widgetProvider = widgetProvider; + _privacySettings = privacySettings; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + if (!_privacySettings.EnableCookieConsent) + return; + + if (filterContext?.ActionDescriptor == null || filterContext?.HttpContext?.Request == null) + return; + + string actionName = filterContext.ActionDescriptor.ActionName; + string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; + + var viewBag = filterContext.Controller.ViewBag; + viewBag.AskCookieConsent = true; + viewBag.HasCookieConsent = false; + + var request = filterContext.HttpContext.Request; + + // Check if the user has a consent cookie + var consentCookie = request.Cookies[CookieConsent.CONSENT_COOKIE_NAME]; + if (consentCookie == null) + { + // No consent cookie. We first check the Do Not Track header value, this can have the value "0" or "1" + string dnt = request.Headers.Get("DNT"); + + // If we receive a DNT header, we accept its value and do not ask the user anymore + if (!String.IsNullOrEmpty(dnt)) + { + viewBag.AskCookieConsent = false; + viewBag.HasCookieConsent = dnt == "0"; + } + else + { + if (_userAgent.IsBot) + { + // don't ask consent from search engines, also don't set cookies + viewBag.AskCookieConsent = false; + } + else + { + // first request on the site and no DNT header. + consentCookie = new HttpCookie(CookieConsent.CONSENT_COOKIE_NAME); + consentCookie.Value = "asked"; + filterContext.HttpContext.Response.Cookies.Add(consentCookie); + } + } + } + else + { + // we received a consent cookie + viewBag.AskCookieConsent = false; + if (consentCookie.Value == "asked") + { + // consent is implicitly given + consentCookie.Expires = DateTime.UtcNow.AddYears(1); + filterContext.HttpContext.Response.Cookies.Set(consentCookie); + viewBag.HasCookieConsent = true; + } + else if (consentCookie.Value == "true") + { + viewBag.HasCookieConsent = true; + } + else + { + // assume consent denied + viewBag.HasCookieConsent = false; + } + } + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + if (!_privacySettings.EnableCookieConsent) + return; + + if (filterContext.IsChildAction) + return; + + var result = filterContext.Result; + + // should only run on a full view rendering result or HTML ContentResult + if (!result.IsHtmlViewResult()) + return; + + _widgetProvider.Value.RegisterAction( + new[] { "body_end_html_tag_before" }, + "CookieConsentBadge", + "Common", + new { area = "" }); + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + } + } + + public static class CookieConsent + { + public const string CONSENT_COOKIE_NAME = "CookieConsent"; + + public static void SetCookieConsent(HttpResponseBase response, bool consent) + { + var consentCookie = new HttpCookie(CookieConsent.CONSENT_COOKIE_NAME); + consentCookie.Value = consent ? "true" : "false"; + consentCookie.Expires = DateTime.UtcNow.AddYears(1); + response.Cookies.Set(consentCookie); + } + + public static bool AskCookieConsent(ViewContext context) + { + return context.ViewBag.AskCookieConsent ?? false; + } + + public static bool HasCookieConsent(ViewContext context) + { + return context.ViewBag.HasCookieConsent ?? false; + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/CustomerLastActivityAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/CustomerLastActivityAttribute.cs index 8f9418de66..f6d7f08da0 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/CustomerLastActivityAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/CustomerLastActivityAttribute.cs @@ -6,12 +6,12 @@ namespace SmartStore.Web.Framework.Filters { - public class CustomerLastActivityAttribute : ActionFilterAttribute + public class CustomerLastActivityAttribute : FilterAttribute, IActionFilter { public Lazy WorkContext { get; set; } public Lazy CustomerService { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { if (!DataSettings.DatabaseIsInstalled()) return; @@ -36,5 +36,9 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) CustomerService.Value.UpdateCustomer(customer); } } - } + + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/GdprConsentAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/GdprConsentAttribute.cs new file mode 100644 index 0000000000..b76bf74857 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Filters/GdprConsentAttribute.cs @@ -0,0 +1,102 @@ +using SmartStore.Core; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Logging; +using SmartStore.Services; +using SmartStore.Services.Common; +using SmartStore.Web.Framework.UI; +using System; +using System.Web.Mvc; +using System.Web.Routing; + +namespace SmartStore.Web.Framework.Filters +{ + public class GdprConsentAttribute : FilterAttribute, IActionFilter, IResultFilter + { + public Lazy Services { get; set; } + public Lazy PrivacySettings { get; set; } + public Lazy GenericAttributeService { get; set; } + public Lazy WidgetProvider { get; set; } + public Lazy Notifier { get; set; } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + if (!PrivacySettings.Value.DisplayGdprConsentOnForms) + return; + + if (filterContext?.ActionDescriptor == null || filterContext?.HttpContext?.Request == null) + return; + + if (filterContext.HttpContext.Request.HttpMethod.Equals("GET")) + return; + + var customer = Services.Value.WorkContext.CurrentCustomer; + var hasConsentedToGdpr = filterContext.HttpContext.Request.Form["GdprConsent"]; + + if (filterContext.HttpContext.Request.HttpMethod.Equals("POST") && hasConsentedToGdpr.HasValue()) + { + // set flag which can be accessed in corresponding action + filterContext.Controller.ViewData.Add("GdprConsent", hasConsentedToGdpr.Contains("true")); + + if (hasConsentedToGdpr.Contains("true")) + { + GenericAttributeService.Value.SaveAttribute(customer, SystemCustomerAttributeNames.HasConsentedToGdpr, true); + } + else + { + if (!filterContext.HttpContext.Request.IsAjaxRequest()) + { + // add a validation message + filterContext.Controller.ViewData.ModelState.AddModelError("", Services.Value.Localization.GetResource("Gdpr.Consent.ValidationMessage")); + } + else + { + // notify + Notifier.Value.Error(Services.Value.Localization.GetResource("Gdpr.Consent.ValidationMessage")); + } + + return; + } + } + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + if (!PrivacySettings.Value.DisplayGdprConsentOnForms) + return; + + if (filterContext.HttpContext.Items.Contains("GdprConsentRendered")) + return; + + var result = filterContext.Result; + + // should only run on a full view rendering result or HTML ContentResult + if (!result.IsHtmlViewResult()) + return; + + if (!filterContext.IsChildAction) + { + WidgetProvider.Value.RegisterAction( + new[] { "gdpr_consent" }, + "GdprConsent", + "Common", + new { area = "", isSmall = false }); + } + + WidgetProvider.Value.RegisterAction( + new[] { "gdpr_consent_small" }, + "GdprConsent", + "Common", + new { area = "", isSmall = true }); + + filterContext.HttpContext.Items["GdprConsentRendered"] = true; + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/HandleExceptionFilter.cs b/src/Presentation/SmartStore.Web.Framework/Filters/HandleExceptionFilter.cs index 299e0faa3e..25a7056aa7 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/HandleExceptionFilter.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/HandleExceptionFilter.cs @@ -15,25 +15,25 @@ namespace SmartStore.Web.Framework.Filters { public class HandleExceptionFilter : IActionFilter { - private readonly Lazy _logger; + private readonly ILoggerFactory _loggerFactory; private readonly Lazy> _exceptionFilters; private readonly Lazy _workContext; public HandleExceptionFilter( - Lazy logger, + ILoggerFactory loggerFactory, Lazy> exceptionFilters, Lazy workContext) { - this._logger = logger; - this._exceptionFilters = exceptionFilters; - this._workContext = workContext; + _loggerFactory = loggerFactory; + _exceptionFilters = exceptionFilters; + _workContext = workContext; } - public void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { } - public void OnActionExecuted(ActionExecutedContext filterContext) + public virtual void OnActionExecuted(ActionExecutedContext filterContext) { var descriptor = filterContext.ActionDescriptor; @@ -41,11 +41,11 @@ public void OnActionExecuted(ActionExecutedContext filterContext) // don't provide custom errors if the action has some custom code to handle exceptions if (!filterContext.ActionDescriptor.GetCustomAttributes(typeof(HandleErrorAttribute), false).Any()) { - if (!filterContext.ExceptionHandled && filterContext.Exception != null && filterContext.HttpContext.IsCustomErrorEnabled) + if (!filterContext.ExceptionHandled && filterContext.Exception != null) { if (ShouldHandleException(filterContext.Exception)) { - LogException(filterContext.Exception); + LogException(filterContext.Exception, filterContext.ActionDescriptor.ControllerDescriptor); // inform exception filters of the exception that was suppressed var exceptionContext = new ExceptionContext(filterContext.Controller.ControllerContext, filterContext.Exception); @@ -90,6 +90,8 @@ public void OnActionExecuted(ActionExecutedContext filterContext) ViewData = new ViewDataDictionary(new HandleErrorInfo(filterContext.Exception, controllerName, actionName)), TempData = filterContext.Controller.TempData }; + + _workContext.Value.IsAdmin = false; } filterContext.ExceptionHandled = true; @@ -110,7 +112,7 @@ public void OnActionExecuted(ActionExecutedContext filterContext) // handle not found (404) from within the MVC pipeline (only called when HttpNotFoundResult is returned from actions) var requestContext = filterContext.RequestContext; var url = requestContext.HttpContext.Request.RawUrl; - + filterContext.Result = new ViewResult { ViewName = "NotFound", @@ -118,6 +120,9 @@ public void OnActionExecuted(ActionExecutedContext filterContext) ViewData = new ViewDataDictionary(new HandleErrorInfo(new HttpException(404, "The resource does not exist."), descriptor.ActionName, descriptor.ControllerDescriptor.ControllerName)), TempData = filterContext.Controller.TempData }; + + _workContext.Value.IsAdmin = false; + requestContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; // prevent IIS 7.0 classic mode from handling the 404/500 itself @@ -137,7 +142,7 @@ exception is SecurityException || exception is SEHException); } - protected void LogException(Exception exception) + protected void LogException(Exception exception, ControllerDescriptor descriptor) { if (exception == null) return; @@ -152,9 +157,8 @@ protected void LogException(Exception exception) try { - var logger = _logger.Value; - var workContext = _workContext.Value; - logger.Error(exception.Message, exception, workContext.CurrentCustomer); + var logger = _loggerFactory.GetLogger(descriptor.ControllerType); + logger.Error(exception); } catch { diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/HandleInstallFilter.cs b/src/Presentation/SmartStore.Web.Framework/Filters/HandleInstallFilter.cs index 5f457e20af..af2e1fbaf5 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/HandleInstallFilter.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/HandleInstallFilter.cs @@ -4,34 +4,28 @@ namespace SmartStore.Web.Framework.Filters { - public class HandleInstallFilter : IActionFilter - { - public void OnActionExecuting(ActionExecutingContext filterContext) - { - var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; - var actionName = filterContext.ActionDescriptor.ActionName; + public class HandleInstallFilter : IAuthorizationFilter + { + public void OnAuthorization(AuthorizationContext filterContext) + { + var controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; + var actionName = filterContext.ActionDescriptor.ActionName; - if (controllerName == "Install" && actionName != "Index" && filterContext.HttpContext.Request.IsAjaxRequest()) - { - // probably "progress" or "finalize" call - return; - } + if (controllerName == "Install" && actionName != "Index" && filterContext.HttpContext.Request.IsAjaxRequest()) + { + // probably "progress" or "finalize" call + return; + } - if (!DataSettings.DatabaseIsInstalled() && controllerName != "Install") - { - filterContext.Result = new RedirectToRouteResult( - new RouteValueDictionary { - { "Controller", "Install" }, - { "Action", "Index" } - }); - } - } - - public void OnActionExecuted(ActionExecutedContext filterContext) - { - // nada - } - - } + if (!DataSettings.DatabaseIsInstalled() && controllerName != "Install") + { + filterContext.Result = new RedirectToRouteResult( + new RouteValueDictionary { + { "Controller", "Install" }, + { "Action", "Index" } + }); + } + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/JsonNetAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/JsonNetAttribute.cs index 335977bd29..1c706ee2f2 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/JsonNetAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/JsonNetAttribute.cs @@ -1,27 +1,21 @@ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net; -using System.Runtime.InteropServices; -using System.Security; -using System.Threading; -using System.Web; using System.Web.Mvc; -using SmartStore.Core; +using Newtonsoft.Json; using SmartStore.Core.Data; -using SmartStore.Core.Logging; using SmartStore.Services.Helpers; using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Framework.Filters { - public class JsonNetAttribute : ActionFilterAttribute + public class JsonNetAttribute : FilterAttribute, IActionFilter { public Lazy DateTimeHelper { get; set; } - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - public override void OnActionExecuted(ActionExecutedContext filterContext) + public void OnActionExecuting(ActionExecutingContext filterContext) + { + } + + public void OnActionExecuted(ActionExecutedContext filterContext) { if (!DataSettings.DatabaseIsInstalled()) return; @@ -29,22 +23,37 @@ public override void OnActionExecuted(ActionExecutedContext filterContext) if (filterContext == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) return; - // don't apply filter to child methods + // Don't apply filter to child methods. if (filterContext.IsChildAction) return; - // handle JsonResult only + // Hndle JsonResult only. if (filterContext.Result.GetType() != typeof(JsonResult)) return; - var jsonResult = filterContext.Result as JsonResult; + var jsonResult = filterContext.Result as JsonResult; + var settings = new JsonSerializerSettings + { + MissingMemberHandling = MissingMemberHandling.Ignore, + TypeNameHandling = TypeNameHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, + + // We cannot ignore null. Client template of several Telerik grids would fail. + //NullValueHandling = NullValueHandling.Ignore, + + MaxDepth = 32 + }; - filterContext.Result = new JsonNetResult(DateTimeHelper.Value) + filterContext.Result = new JsonNetResult(DateTimeHelper.Value, settings) { Data = jsonResult.Data, ContentType = jsonResult.ContentType, ContentEncoding = jsonResult.ContentEncoding, - JsonRequestBehavior = jsonResult.JsonRequestBehavior + JsonRequestBehavior = jsonResult.JsonRequestBehavior, + MaxJsonLength = jsonResult.MaxJsonLength, + RecursionLimit = jsonResult.RecursionLimit }; } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/NotifyAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/NotifyAttribute.cs index 217f7f00f2..ec44b5bb1d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/NotifyAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/NotifyAttribute.cs @@ -7,19 +7,18 @@ namespace SmartStore.Web.Framework.Filters { - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] - public class NotifyAttribute : ActionFilterAttribute + public class NotifyAttribute : FilterAttribute, IResultFilter { public const string NotificationsKey = "sm.notifications.all"; public INotifier Notifier { get; set; } - public override void OnActionExecuted(ActionExecutedContext filterContext) + public virtual void OnResultExecuting(ResultExecutingContext filterContext) { - if (Notifier == null || !Notifier.Entries.Any()) + if (Notifier == null || Notifier.Entries.Count == 0) return; - if (filterContext.HttpContext.Request.IsAjaxRequest()) + if (!filterContext.IsChildAction && filterContext.HttpContext.Request.IsAjaxRequest()) { HandleAjaxRequest(Notifier.Entries.FirstOrDefault(), filterContext.HttpContext.Response); return; @@ -27,6 +26,12 @@ public override void OnActionExecuted(ActionExecutedContext filterContext) Persist(filterContext.Controller.ViewData, Notifier.Entries.Where(x => x.Durable == false)); Persist(filterContext.Controller.TempData, Notifier.Entries.Where(x => x.Durable == true)); + + Notifier.Entries.Clear(); + } + + public virtual void OnResultExecuted(ResultExecutedContext filterContext) + { } private void Persist(IDictionary bag, IEnumerable source) @@ -34,14 +39,15 @@ private void Persist(IDictionary bag, IEnumerable s if (!source.Any()) return; - var existing = (bag[NotificationsKey] ?? new List()) as List; + var existing = (bag[NotificationsKey] ?? new HashSet()) as HashSet; - source.Each(x => { - if (x.Message.Text.HasValue() && !existing.Contains(x)) + source.Each(x => + { + if (x.Message.Text.HasValue()) existing.Add(x); }); - bag[NotificationsKey] = existing; + bag[NotificationsKey] = TrimSet(existing); } private void HandleAjaxRequest(NotifyEntry entry, HttpResponseBase response) @@ -53,6 +59,15 @@ private void HandleAjaxRequest(NotifyEntry entry, HttpResponseBase response) response.AddHeader("X-Message", entry.Message.Text); } + private HashSet TrimSet(HashSet entries) + { + if (entries.Count <= 20) + { + return entries; + } + + return new HashSet(entries.Skip(entries.Count - 20)); + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/PublicStoreAllowNavigationAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/PublicStoreAllowNavigationAttribute.cs index 17edc7fa99..a9790ec54a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/PublicStoreAllowNavigationAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/PublicStoreAllowNavigationAttribute.cs @@ -7,7 +7,7 @@ namespace SmartStore.Web.Framework.Filters { - public class PublicStoreAllowNavigationAttribute : ActionFilterAttribute + public class PublicStoreAllowNavigationAttribute : FilterAttribute, IActionFilter { private static readonly List> s_permittedRoutes = new List> { @@ -17,12 +17,13 @@ public class PublicStoreAllowNavigationAttribute : ActionFilterAttribute new Tuple("SmartStore.Web.Controllers.CustomerController", "PasswordRecovery"), new Tuple("SmartStore.Web.Controllers.CustomerController", "PasswordRecoveryConfirm"), new Tuple("SmartStore.Web.Controllers.CustomerController", "AccountActivation"), - new Tuple("SmartStore.Web.Controllers.CustomerController", "CheckUsernameAvailability") + new Tuple("SmartStore.Web.Controllers.CustomerController", "CheckUsernameAvailability"), + new Tuple("SmartStore.Web.Controllers.CatalogController", "OffCanvasMenu") }; public Lazy PermissionService { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext == null || filterContext.HttpContext == null) return; @@ -54,6 +55,10 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) } } + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } + private static bool IsPermittedRoute(string controllerName, string actionName) { foreach (var route in s_permittedRoutes) @@ -66,5 +71,5 @@ private static bool IsPermittedRoute(string controllerName, string actionName) return false; } - } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/StoreClosedAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/StoreClosedAttribute.cs index 8d1a936cf4..aec0ac22d8 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/StoreClosedAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/StoreClosedAttribute.cs @@ -11,7 +11,7 @@ namespace SmartStore.Web.Framework.Filters { - public class StoreClosedAttribute : ActionFilterAttribute + public class StoreClosedAttribute : FilterAttribute, IActionFilter { private static readonly List> s_permittedRoutes = new List> { @@ -26,42 +26,42 @@ public class StoreClosedAttribute : ActionFilterAttribute public Lazy WorkContext { get; set; } public Lazy StoreInformationSettings { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { - if (filterContext == null || filterContext.HttpContext == null) - return; + if (filterContext == null || filterContext.HttpContext == null) + return; - HttpRequestBase request = filterContext.HttpContext.Request; - if (request == null) - return; + HttpRequestBase request = filterContext.HttpContext.Request; + if (request == null) + return; //don't apply filter to child methods if (filterContext.IsChildAction) return; - string actionName = filterContext.ActionDescriptor.ActionName; - if (String.IsNullOrEmpty(actionName)) - return; + string actionName = filterContext.ActionDescriptor.ActionName; + if (String.IsNullOrEmpty(actionName)) + return; - string controllerName = filterContext.Controller.ToString(); - if (String.IsNullOrEmpty(controllerName)) - return; + string controllerName = filterContext.Controller.ToString(); + if (String.IsNullOrEmpty(controllerName)) + return; if (!DataSettings.DatabaseIsInstalled()) - return; + return; - var storeInformationSettings = StoreInformationSettings.Value; - if (!storeInformationSettings.StoreClosed) - return; + var storeInformationSettings = StoreInformationSettings.Value; + if (!storeInformationSettings.StoreClosed) + return; - if (!IsPermittedRoute(controllerName, actionName)) + if (!IsPermittedRoute(controllerName, actionName)) { - if (storeInformationSettings.StoreClosedAllowForAdmins && WorkContext.Value.CurrentCustomer.IsAdmin()) - { - //do nothing - allow admin access - } - else - { + if (storeInformationSettings.StoreClosedAllowForAdmins && WorkContext.Value.CurrentCustomer.IsAdmin()) + { + //do nothing - allow admin access + } + else + { if (request.IsAjaxRequest()) { var storeClosedMessage = "{0} {1}".FormatCurrentUI( @@ -77,9 +77,13 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) filterContext.Result = new RedirectResult(storeClosedUrl); } - } - } - } + } + } + } + + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } private static bool IsPermittedRoute(string controllerName, string actionName) { @@ -93,5 +97,5 @@ private static bool IsPermittedRoute(string controllerName, string actionName) return false; } - } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/StoreIpAddressAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/StoreIpAddressAttribute.cs index fce317402c..8a65cd046a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/StoreIpAddressAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/StoreIpAddressAttribute.cs @@ -2,43 +2,44 @@ using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; using SmartStore.Services.Customers; namespace SmartStore.Web.Framework.Filters { - public class StoreIpAddressAttribute : ActionFilterAttribute + public class StoreIpAddressAttribute : FilterAttribute, IActionFilter { - public Lazy WebHelper { get; set; } public Lazy WorkContext { get; set; } public Lazy CustomerService { get; set; } - - public override void OnActionExecuting(ActionExecutingContext filterContext) + public Lazy PrivacySettings { get; set; } + + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { if (!DataSettings.DatabaseIsInstalled()) return; - if (filterContext == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) + if (filterContext?.HttpContext?.Request == null) return; - //don't apply filter to child methods + // Don't apply filter to child methods. if (filterContext.IsChildAction) return; - //only GET requests + // Only GET requests. if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) return; - var webHelper = this.WebHelper.Value; + if (!PrivacySettings.Value.StoreLastIpAddress) + return; - //update IP address - string currentIpAddress = webHelper.GetCurrentIpAddress(); + // Update IP address. + var webHelper = WebHelper.Value; + var currentIpAddress = webHelper.GetCurrentIpAddress(); if (!String.IsNullOrEmpty(currentIpAddress)) { - var workContext = WorkContext.Value; - var customer = workContext.CurrentCustomer; - - if (!currentIpAddress.Equals(customer.LastIpAddress, StringComparison.InvariantCultureIgnoreCase)) + var customer = WorkContext.Value.CurrentCustomer; + if (customer != null && !currentIpAddress.Equals(customer.LastIpAddress, StringComparison.InvariantCultureIgnoreCase)) { var customerService = CustomerService.Value; customer.LastIpAddress = currentIpAddress; @@ -46,5 +47,9 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) } } } - } + + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/StoreLastVisitedPageAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/StoreLastVisitedPageAttribute.cs index 79decb0121..fbbc9b9dca 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/StoreLastVisitedPageAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/StoreLastVisitedPageAttribute.cs @@ -7,16 +7,15 @@ namespace SmartStore.Web.Framework.Filters { - public class StoreLastVisitedPageAttribute : ActionFilterAttribute + public class StoreLastVisitedPageAttribute : FilterAttribute, IActionFilter { - public Lazy WebHelper { get; set; } public Lazy WorkContext { get; set; } public Lazy CustomerSettings { get; set; } public Lazy GenericAttributeService { get; set; } public Lazy UserAgent { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { if (!DataSettings.DatabaseIsInstalled()) return; @@ -63,5 +62,9 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) } } } - } + + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Filters/UnitOfWorkAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Filters/UnitOfWorkAttribute.cs index 9a060a6294..2721a44f65 100644 --- a/src/Presentation/SmartStore.Web.Framework/Filters/UnitOfWorkAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Filters/UnitOfWorkAttribute.cs @@ -7,7 +7,6 @@ namespace SmartStore.Web.Framework.Filters [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public class UnitOfWorkAttribute : ActionFilterAttribute { - public UnitOfWorkAttribute() : this(null, int.MaxValue) { diff --git a/src/Presentation/SmartStore.Web.Framework/FrameworkCacheConsumer.cs b/src/Presentation/SmartStore.Web.Framework/FrameworkCacheConsumer.cs index c456938640..5dee2d6c96 100644 --- a/src/Presentation/SmartStore.Web.Framework/FrameworkCacheConsumer.cs +++ b/src/Presentation/SmartStore.Web.Framework/FrameworkCacheConsumer.cs @@ -1,26 +1,23 @@ using System; +using System.Collections.Generic; +using System.Web; +using SmartStore.Core; using SmartStore.Core.Caching; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; using SmartStore.Core.Domain.Configuration; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Tax; using SmartStore.Core.Domain.Themes; using SmartStore.Core.Events; using SmartStore.Core.Themes; -using System.Web; +using SmartStore.Utilities; +using SmartStore.Web.Framework.Theming.Assets; namespace SmartStore.Web.Framework { - - public class FrameworkCacheConsumer : - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer>, - IConsumer + public partial class FrameworkCacheConsumer : DbSaveHook, IConsumer { - /// /// Key for ThemeVariables caching /// @@ -28,9 +25,8 @@ public class FrameworkCacheConsumer : /// {0} : theme name /// {1} : store identifier /// - public const string THEMEVARS_LESSCSS_KEY = "sm.pres.themevars-lesscss-{0}-{1}"; - public const string THEMEVARS_LESSCSS_THEME_KEY = "sm.pres.themevars-lesscss-{0}"; - + public const string THEMEVARS_KEY = "fw:themevars-{0}-{1}"; + public const string THEMEVARS_THEME_KEY = "fw:themevars-{0}"; /// /// Key for tax display type caching @@ -39,29 +35,19 @@ public class FrameworkCacheConsumer : /// {0} : customer role ids /// {1} : store identifier /// - public const string CUSTOMERROLES_TAX_DISPLAY_TYPES_KEY = "sm.fw.customerroles.taxdisplaytypes-{0}-{1}"; - public const string CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY = "sm.fw.customerroles.taxdisplaytypes"; + public const string CUSTOMERROLES_TAX_DISPLAY_TYPES_KEY = "fw:customerroles:taxdisplaytypes-{0}-{1}"; + public const string CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY = "fw:customerroles:taxdisplaytypes*"; - private readonly ICacheManager _cacheManager; + private readonly ICacheManager _cacheManager; + private readonly IAssetCache _assetCache; - public FrameworkCacheConsumer(ICacheManager cacheManager) - { - _cacheManager = cacheManager; - } + // Item1 = ThemeName, Item2 = StoreId + private HashSet> _themeScopes; - public void HandleEvent(EntityInserted eventMessage) + public FrameworkCacheConsumer(ICacheManager cacheManager, IAssetCache assetCache) { - HttpRuntime.Cache.Remove(BuildThemeVarsCacheKey(eventMessage.Entity)); - } - - public void HandleEvent(EntityUpdated eventMessage) - { - HttpRuntime.Cache.Remove(BuildThemeVarsCacheKey(eventMessage.Entity)); - } - - public void HandleEvent(EntityDeleted eventMessage) - { - HttpRuntime.Cache.Remove(BuildThemeVarsCacheKey(eventMessage.Entity)); + _cacheManager = cacheManager; + _assetCache = assetCache; } public void HandleEvent(ThemeTouchedEvent eventMessage) @@ -70,29 +56,57 @@ public void HandleEvent(ThemeTouchedEvent eventMessage) HttpRuntime.Cache.RemoveByPattern(cacheKey); } + public override void OnAfterSave(IHookedEntity entry) + { + if (entry.Entity is ThemeVariable) + { + var themeVar = entry.Entity as ThemeVariable; + AddEvictableThemeScope(themeVar.Theme, themeVar.StoreId); + } + else if (entry.Entity is CustomerRole) + { + _cacheManager.RemoveByPattern(CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY); + } + else if (entry.Entity is Setting && entry.InitialState == EntityState.Modified) + { + var setting = entry.Entity as Setting; + if (setting.Name.IsCaseInsensitiveEqual(TypeHelper.NameOf(x => x.TaxDisplayType, true))) + { + _cacheManager.RemoveByPattern(CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY); // depends on TaxSettings.TaxDisplayType + } + } + else + { + throw new NotImplementedException(); + } + } + + public override void OnAfterSaveCompleted() + { + FlushThemeVarsCacheEviction(); + } - public void HandleEvent(EntityDeleted eventMessage) - { - _cacheManager.RemoveByPattern(CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY); - } + #region Helpers - public void HandleEvent(EntityUpdated eventMessage) - { - _cacheManager.RemoveByPattern(CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY); - } + private void AddEvictableThemeScope(string themeName, int storeId) + { + if (_themeScopes == null) + _themeScopes = new HashSet>(); + _themeScopes.Add(new Tuple(themeName, storeId)); + } - public void HandleEvent(EntityInserted eventMessage) - { - _cacheManager.RemoveByPattern(CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY); - } + private void FlushThemeVarsCacheEviction() + { + if (_themeScopes == null || _themeScopes.Count == 0) + return; - public void HandleEvent(EntityUpdated eventMessage) - { - // clear models which depend on settings - _cacheManager.RemoveByPattern(CUSTOMERROLES_TAX_DISPLAY_TYPES_PATTERN_KEY); // depends on TaxSettings.TaxDisplayType - } + foreach (var scope in _themeScopes) + { + HttpRuntime.Cache.Remove(BuildThemeVarsCacheKey(scope.Item1 /* ThemeName */, scope.Item2 /* StoreId */)); + } - #region Helpers + _themeScopes.Clear(); + } private static string BuildThemeVarsCacheKey(ThemeVariable entity) { @@ -103,14 +117,12 @@ internal static string BuildThemeVarsCacheKey(string themeName, int storeId) { if (storeId > 0) { - return HttpRuntime.Cache.BuildScopedKey(THEMEVARS_LESSCSS_KEY.FormatInvariant(themeName, storeId)); + return HttpRuntime.Cache.BuildScopedKey(THEMEVARS_KEY.FormatInvariant(themeName, storeId)); } - return HttpRuntime.Cache.BuildScopedKey(THEMEVARS_LESSCSS_THEME_KEY.FormatInvariant(themeName)); + return HttpRuntime.Cache.BuildScopedKey(THEMEVARS_THEME_KEY.FormatInvariant(themeName)); } #endregion - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModel.cs b/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModel.cs index abde521279..d91f9f2ad8 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModel.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModel.cs @@ -6,8 +6,13 @@ public interface ILocalizedModel { } - public interface ILocalizedModel : ILocalizedModel + public interface ILocalizedModel : ILocalizedModel where T : ILocalizedModelLocal { - IList Locales { get; set; } + IList Locales { get; set; } } + + public interface ILocalizedModelLocal + { + int LanguageId { get; set; } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModelLocal.cs b/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModelLocal.cs deleted file mode 100644 index 35c850c80d..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Localization/ILocalizedModelLocal.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace SmartStore.Web.Framework.Localization -{ - public interface ILocalizedModelLocal - { - int LanguageId { get; set; } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/IText.cs b/src/Presentation/SmartStore.Web.Framework/Localization/IText.cs index aa60884b0a..ec648c3377 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/IText.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/IText.cs @@ -6,5 +6,6 @@ namespace SmartStore.Web.Framework.Localization public interface IText { LocalizedString Get(string key, params object[] args); + LocalizedString GetEx(string key, int languageId, params object[] args); } } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs index 6b4271ec47..28055079b9 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/LanguageSeoCodeAttribute.cs @@ -12,22 +12,19 @@ namespace SmartStore.Web.Framework.Localization /// /// Attribute which ensures that store URL contains a language SEO code if "SEO friendly URLs with multiple languages" setting is enabled /// - public class LanguageSeoCodeAttribute : ActionFilterAttribute + public class LanguageSeoCodeAttribute : FilterAttribute, IActionFilter { public Lazy WorkContext { get; set; } public Lazy LanguageService { get; set; } public Lazy LocalizationSettings { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public virtual void OnActionExecuting(ActionExecutingContext filterContext) { - if (filterContext == null || filterContext.HttpContext == null) - return; - - HttpRequestBase request = filterContext.HttpContext.Request; + var request = filterContext?.HttpContext?.Request; if (request == null) return; - //don't apply filter to child methods + // Don't apply filter to child methods if (filterContext.IsChildAction) return; @@ -105,5 +102,8 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) filterContext.Result = new RedirectResult(helper.GetAbsolutePath()); } - } + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs index 3419e77ee1..42fba7cd2b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRoute.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Infrastructure; +using SmartStore.Services.Localization; namespace SmartStore.Web.Framework.Localization { @@ -16,7 +17,7 @@ public class LocalizedRoute : Route, ICloneable { #region Fields - private bool? _seoFriendlyUrlsForLanguagesEnabled; + private static bool? _seoFriendlyUrlsForLanguagesEnabled; private string _leftPart; #endregion @@ -83,7 +84,7 @@ public LocalizedRoute(string url, RouteValueDictionary defaults, RouteValueDicti /// public override RouteData GetRouteData(HttpContextBase httpContext) { - if (DataSettings.DatabaseIsInstalled() && this.SeoFriendlyUrlsForLanguagesEnabled) + if (DataSettings.DatabaseIsInstalled() && SeoFriendlyUrlsForLanguagesEnabled) { var helper = new LocalizedUrlHelper(httpContext.Request); if (helper.IsLocalizedUrl()) @@ -120,22 +121,22 @@ public override VirtualPathData GetVirtualPath(RequestContext requestContext, Ro { VirtualPathData data = base.GetVirtualPath(requestContext, values); - if (data != null && DataSettings.DatabaseIsInstalled() && this.SeoFriendlyUrlsForLanguagesEnabled) + if (data != null && DataSettings.DatabaseIsInstalled() && SeoFriendlyUrlsForLanguagesEnabled) { var helper = new LocalizedUrlHelper(requestContext.HttpContext.Request, true); - string cultureCode; - if (helper.IsLocalizedUrl(out cultureCode)) - { + if (helper.IsLocalizedUrl(out string cultureCode)) + { if (!requestContext.RouteData.Values.ContainsKey("StripInvalidSeoCode")) { - data.VirtualPath = String.Concat(cultureCode, "/", data.VirtualPath); + data.VirtualPath = String.Concat(cultureCode, "/", data.VirtualPath).TrimEnd('/'); } - } - } + } + } + return data; } - public virtual void ClearSeoFriendlyUrlsCachedValue() + public static void ClearSeoFriendlyUrlsCachedValue() { _seoFriendlyUrlsForLanguagesEnabled = null; } @@ -146,14 +147,22 @@ public virtual void ClearSeoFriendlyUrlsCachedValue() public bool IsClone { get; private set; } - protected bool SeoFriendlyUrlsForLanguagesEnabled + protected internal static bool SeoFriendlyUrlsForLanguagesEnabled { get { - if (!_seoFriendlyUrlsForLanguagesEnabled.HasValue) - _seoFriendlyUrlsForLanguagesEnabled = EngineContext.Current.Resolve().SeoFriendlyUrlsForLanguagesEnabled; - - return _seoFriendlyUrlsForLanguagesEnabled.Value; + if (_seoFriendlyUrlsForLanguagesEnabled == null && EngineContext.Current.IsFullyInitialized) + { + try + { + var enabled = EngineContext.Current.Resolve().SeoFriendlyUrlsForLanguagesEnabled; + _seoFriendlyUrlsForLanguagesEnabled = enabled; + } + catch { } + } + + // Assume is enabled on very first request to prevent IIS 404 with localized URLs + return _seoFriendlyUrlsForLanguagesEnabled ?? true; } } @@ -163,15 +172,16 @@ protected bool SeoFriendlyUrlsForLanguagesEnabled public LocalizedRoute Clone() { - var clone = new LocalizedRoute(this.Url, - new RouteValueDictionary(this.Defaults), - new RouteValueDictionary(this.Constraints), - new RouteValueDictionary(this.DataTokens), - new MvcRouteHandler()); - clone.RouteExistingFiles = this.RouteExistingFiles; - clone._seoFriendlyUrlsForLanguagesEnabled = this._seoFriendlyUrlsForLanguagesEnabled; - clone.IsClone = true; - return clone; + var clone = new LocalizedRoute(this.Url, + new RouteValueDictionary(this.Defaults), + new RouteValueDictionary(this.Constraints), + new RouteValueDictionary(this.DataTokens), + new MvcRouteHandler()) + { + RouteExistingFiles = this.RouteExistingFiles, + IsClone = true + }; + return clone; } object ICloneable.Clone() diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRouteExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRouteExtensions.cs index 1b3e01301c..47c24c873a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRouteExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedRouteExtensions.cs @@ -1,16 +1,12 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Seo; namespace SmartStore.Web.Framework.Localization { - public static class LocalizedRouteExtensions { - #region RouteCollection extensions public static Route MapLocalizedRoute(this RouteCollection routes, string name, string url) @@ -65,21 +61,6 @@ public static Route MapLocalizedRoute(this RouteCollection routes, string name, return route; } - - public static void ClearSeoFriendlyUrlsCachedValueForRoutes(this RouteCollection routes) - { - if (routes == null) - { - throw new ArgumentNullException("routes"); - } - foreach (var route in routes) - { - if (route is LocalizedRoute) - { - ((LocalizedRoute) route).ClearSeoFriendlyUrlsCachedValue(); - } - } - } #endregion } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/SetWorkingCultureAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Localization/SetWorkingCultureAttribute.cs index c460525c0d..5e81f9cc77 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/SetWorkingCultureAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/SetWorkingCultureAttribute.cs @@ -1,30 +1,32 @@ using System; +using System.Collections.Generic; using System.Globalization; +using System.Text; using System.Threading; using System.Web; using System.Web.Mvc; +using Newtonsoft.Json; using SmartStore.Core; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Localization; +using SmartStore.Web.Framework.UI; namespace SmartStore.Web.Framework.Localization { /// - /// Attribute which determines and sets the working culture + /// Attribute which determines and sets working culture and globalization scripts /// - public class SetWorkingCultureAttribute : FilterAttribute, IAuthorizationFilter + public class SetWorkingCultureAttribute : FilterAttribute, IAuthorizationFilter, IActionFilter { public Lazy WorkContext { get; set; } + public Lazy AssetBuilder { get; set; } - public void OnAuthorization(AuthorizationContext filterContext) + public void OnAuthorization(AuthorizationContext filterContext) { - if (filterContext == null || filterContext.HttpContext == null) - return; - - HttpRequestBase request = filterContext.HttpContext.Request; + var request = filterContext?.HttpContext?.Request; if (request == null) return; - // don't apply filter to child methods if (filterContext.IsChildAction) return; @@ -32,21 +34,135 @@ public void OnAuthorization(AuthorizationContext filterContext) return; var workContext = WorkContext.Value; - var workingLanguage = workContext.WorkingLanguage; - - CultureInfo culture; - if (workContext.CurrentCustomer != null && workingLanguage != null) - { - culture = new CultureInfo(workingLanguage.LanguageCulture); - } - else - { - culture = new CultureInfo("en-US"); - } + + CultureInfo culture = workContext.CurrentCustomer != null && workContext.WorkingLanguage != null + ? new CultureInfo(workContext.WorkingLanguage.LanguageCulture) + : new CultureInfo("en-US"); Thread.CurrentThread.CurrentCulture = culture; Thread.CurrentThread.CurrentUICulture = culture; } - } + public void OnActionExecuting(ActionExecutingContext filterContext) + { + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + if (filterContext.IsChildAction) + return; + + if (!DataSettings.DatabaseIsInstalled()) + return; + + if (!(filterContext.Result is ViewResult)) + return; + + var culture = Thread.CurrentThread.CurrentUICulture; + if (culture.Name == "en-US") + return; + + var builder = AssetBuilder.Value; + var json = CreateCultureJson(culture); + + var sb = new StringBuilder(); + sb.Append(""); + + var script = sb.ToString(); + + builder.AppendCustomHeadParts(script); + } + + private string CreateCultureJson(CultureInfo ci) + { + var nf = ci.NumberFormat; + var df = ci.DateTimeFormat; + + var dict = new Dictionary + { + { "name", ci.Name }, + { "englishName", ci.EnglishName }, + { "nativeName", ci.NativeName }, + { "isRTL", WorkContext.Value.WorkingLanguage?.Rtl ?? ci.TextInfo.IsRightToLeft }, // favor RTL property of Language + { "language", ci.TwoLetterISOLanguageName }, + { "numberFormat", new Dictionary + { + { ",", nf.NumberGroupSeparator }, + { ".", nf.NumberDecimalSeparator }, + { "pattern", new[] { nf.NumberNegativePattern } }, + { "decimals", nf.NumberDecimalDigits }, + { "groupSizes", nf.NumberGroupSizes }, + { "+", nf.PositiveSign }, + { "-", nf.NegativeSign }, + { "NaN", nf.NaNSymbol }, + { "negativeInfinity", nf.NegativeInfinitySymbol }, + { "positiveInfinity", nf.PositiveInfinitySymbol }, + { "percent", new Dictionary + { + { ",", nf.PercentGroupSeparator }, + { ".", nf.PercentDecimalSeparator }, + { "pattern", new[] { nf.PercentNegativePattern, nf.PercentPositivePattern } }, + { "decimals", nf.PercentDecimalDigits }, + { "groupSizes", nf.PercentGroupSizes }, + { "symbol", nf.PercentSymbol } + } }, + { "currency", new Dictionary + { + { ",", nf.CurrencyGroupSeparator }, + { ".", nf.CurrencyDecimalSeparator }, + { "pattern", new[] { nf.CurrencyNegativePattern, nf.CurrencyPositivePattern } }, + { "decimals", nf.CurrencyDecimalDigits }, + { "groupSizes", nf.CurrencyGroupSizes }, + { "symbol", nf.CurrencySymbol } + } }, + } }, + { "dateTimeFormat", new Dictionary + { + { "calendarName", df.NativeCalendarName }, + { "/", df.DateSeparator }, + { ":", df.TimeSeparator }, + { "firstDay", (int)df.FirstDayOfWeek }, + { "twoDigitYearMax", ci.Calendar.TwoDigitYearMax }, + { "AM", df.AMDesignator.IsEmpty() ? null : new[] { df.AMDesignator, df.AMDesignator.ToLower(), df.AMDesignator.ToUpper() } }, + { "PM", df.PMDesignator.IsEmpty() ? null : new[] { df.PMDesignator, df.PMDesignator.ToLower(), df.PMDesignator.ToUpper() } }, + { "days", new Dictionary + { + { "names", df.DayNames }, + { "namesAbbr", df.AbbreviatedDayNames }, + { "namesShort", df.ShortestDayNames }, + } }, + { "months", new Dictionary + { + { "names", df.MonthNames }, + { "namesAbbr", df.AbbreviatedMonthNames }, + } }, + { "patterns", new Dictionary + { + { "d", df.ShortDatePattern }, + { "D", df.LongDatePattern }, + { "t", df.ShortTimePattern }, + { "T", df.LongTimePattern }, + { "g", df.ShortDatePattern + " " + df.ShortTimePattern }, + { "G", df.ShortDatePattern + " " + df.LongTimePattern }, + { "f", df.FullDateTimePattern }, // TODO: (mc) find it actually + { "F", df.FullDateTimePattern }, + { "M", df.MonthDayPattern }, + { "Y", df.YearMonthPattern }, + { "u", df.UniversalSortableDateTimePattern }, + } } + } } + }; + + var json = JsonConvert.SerializeObject(dict, new JsonSerializerSettings + { + Formatting = Formatting.None + }); + + return json; + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationService.cs b/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationService.cs index bc1b5114c0..273283616e 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationService.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationService.cs @@ -1,26 +1,45 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core.Caching; namespace SmartStore.Web.Framework.Localization { public class TelerikLocalizationService : Telerik.Web.Mvc.Infrastructure.ILocalizationService { - private string _resourceName; - private SmartStore.Services.Localization.ILocalizationService _localizationService; - private int _currentLanguageId; + private readonly string _componentName; + private readonly SmartStore.Services.Localization.ILocalizationService _localizationService; + private readonly IRequestCache _requestCache; + private readonly int _currentLanguageId; - public TelerikLocalizationService(string resourceName, int currentLanguageId, Services.Localization.ILocalizationService localizationService) + public TelerikLocalizationService( + string componentName, + int currentLanguageId, + Services.Localization.ILocalizationService localizationService, + IRequestCache requestCache) { - _resourceName = resourceName; + _componentName = componentName; _currentLanguageId = currentLanguageId; _localizationService = localizationService; + _requestCache = requestCache; } public IDictionary All() { - return ScopedResources(); - } + var scope = "Admin.Telerik." + _componentName; + + var cacheKey = scope + ".Resources"; + + return _requestCache.Get(cacheKey, () => + { + var resources = _localizationService.GetResourcesByPattern(scope, _currentLanguageId); + var dict = resources.ToDictionarySafe( + x => x.ResourceName.Substring(scope.Length).ToLowerInvariant(), + x => x.ResourceValue); + + return dict; + }); + } public bool IsDefault { @@ -29,18 +48,9 @@ public bool IsDefault public string One(string key) { - var resourceName = "Admin.Telerik." + _resourceName + "." + key; + var resourceName = "Admin.Telerik." + _componentName + "." + key; var result = _localizationService.GetResource(resourceName, _currentLanguageId, true, resourceName); return result; } - - private IDictionary ScopedResources() - { - var scope = "Admin.Telerik." + _resourceName; - var result = _localizationService.GetResourceValues(_currentLanguageId, true) - .Where(x => x.Key.StartsWith(scope, StringComparison.InvariantCultureIgnoreCase)) - .ToDictionary(x => x.Key.Replace(scope, ""), x => x.Value.Item2); - return result; - } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationServiceFactory.cs b/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationServiceFactory.cs index 9101a00c8c..5d6855db9a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationServiceFactory.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/TelerikLocalizationServiceFactory.cs @@ -1,21 +1,28 @@ using SmartStore.Core; +using SmartStore.Core.Caching; using Telerik.Web.Mvc.Infrastructure; namespace SmartStore.Web.Framework.Localization { public class TelerikLocalizationServiceFactory : ILocalizationServiceFactory { - private IWorkContext _workContext; - private Services.Localization.ILocalizationService _localizationService; + private readonly IWorkContext _workContext; + private readonly Services.Localization.ILocalizationService _localizationService; + private readonly IRequestCache _requestCache; - public TelerikLocalizationServiceFactory(IWorkContext workContext, Services.Localization.ILocalizationService localizationService) + public TelerikLocalizationServiceFactory( + IWorkContext workContext, + Services.Localization.ILocalizationService localizationService, + IRequestCache requestCache) { _workContext = workContext; _localizationService = localizationService; + _requestCache = requestCache; } - public ILocalizationService Create(string resourceName, System.Globalization.CultureInfo culture) + + public ILocalizationService Create(string componentName, System.Globalization.CultureInfo culture) { - return new TelerikLocalizationService(resourceName, _workContext.WorkingLanguage.Id, _localizationService); + return new TelerikLocalizationService(componentName, _workContext.WorkingLanguage.Id, _localizationService, _requestCache); } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/Text.cs b/src/Presentation/SmartStore.Web.Framework/Localization/Text.cs index f96004f6f3..e3ace14f69 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/Text.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/Text.cs @@ -1,4 +1,5 @@ -using SmartStore.Core.Localization; +using System; +using SmartStore.Core.Localization; using SmartStore.Services.Localization; namespace SmartStore.Web.Framework.Localization @@ -13,10 +14,15 @@ public Text(ILocalizationService localizationService) } public LocalizedString Get(string key, params object[] args) + { + return GetEx(key, 0, args); + } + + public LocalizedString GetEx(string key, int languageId, params object[] args) { try { - var value = _localizationService.GetResource(key); + var value = _localizationService.GetResource(key, languageId); if (string.IsNullOrEmpty(value)) { diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/IAclSelector.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/IAclSelector.cs new file mode 100644 index 0000000000..5dfddbe817 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/IAclSelector.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.Modelling +{ + public interface IAclSelector + { + [SmartResourceDisplayName("Admin.Common.Acl.SubjectTo")] + bool SubjectToAcl { get; } + + [SmartResourceDisplayName("Admin.Common.Acl.AvailableFor")] + IEnumerable AvailableCustomerRoles { get; } + + int[] SelectedCustomerRoleIds { get; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/IStoreSelector.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/IStoreSelector.cs new file mode 100644 index 0000000000..cb124b943e --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/IStoreSelector.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.Modelling +{ + public interface IStoreSelector + { + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + bool LimitedToStores { get; } + + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] + IEnumerable AvailableStores { get; } + + int[] SelectedStoreIds { get; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/ModelBase.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/ModelBase.cs index 05e2f798c8..6d3934989a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Modelling/ModelBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/ModelBase.cs @@ -1,30 +1,115 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Web.Mvc; using AutoMapper; +using Newtonsoft.Json; +using SmartStore.Core.Infrastructure; namespace SmartStore.Web.Framework.Modelling { + [Serializable] public sealed class CustomPropertiesDictionary : Dictionary { } + [Serializable] public abstract partial class ModelBase { - protected ModelBase() + private readonly static ContextState>> _contextState; + + static ModelBase() + { + _contextState = new ContextState>>("ModelBase.CustomThreadProperties"); + } + + protected ModelBase() { - this.CustomProperties = new CustomPropertiesDictionary(); + CustomProperties = new CustomPropertiesDictionary(); } public virtual void BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { } + /// + /// Gets a custom property value either from the thread local or the static storage (in this particular order) + /// + /// Type of property + /// Custom property key + /// The property value or null + public TProperty Get(string key) + { + Guard.NotEmpty(key, nameof(key)); + + IDictionary dict; + object value; + + if (TryGetCustomThreadProperties(false, out dict) && dict.TryGetValue(key, out value)) + { + return (TProperty)value; + } + + if (CustomProperties.TryGetValue(key, out value)) + { + return (TProperty)value; + } + + return default(TProperty); + } + /// /// Use this property to store any custom value for your models. /// [IgnoreMap] public CustomPropertiesDictionary CustomProperties { get; set; } - } + + /// + /// A data bag for custom model properties which only + /// lives during a thread/request lifecycle + /// + /// + /// Use thread properties whenever you need to persist request-scoped data, + /// but the model is potentially cached statically. + /// + [IgnoreMap, JsonIgnore] + public IDictionary CustomThreadProperties + { + get + { + IDictionary dict; + TryGetCustomThreadProperties(true, out dict); + return dict; + } + } + + private bool TryGetCustomThreadProperties(bool create, out IDictionary dict) + { + dict = null; + var state = _contextState.GetState(); + + if (state == null && create) + { + state = new Dictionary>(); + _contextState.SetState(state); + } + + if (state != null) + { + if (!state.TryGetValue(this, out dict)) + { + if (create) + { + dict = new Dictionary(StringComparer.OrdinalIgnoreCase); + state[this] = dict; + } + } + + return dict != null; + } + + return false; + } + } public abstract partial class EntityModelBase : ModelBase diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/PictureResult.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/PictureResult.cs deleted file mode 100644 index b7374f6bab..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Modelling/PictureResult.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.IO; -using System.Web.Mvc; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Infrastructure; -using SmartStore.Services.Media; - -namespace SmartStore.Web.Framework.Modelling -{ - public class PictureResult : FileStreamResult - { - public PictureResult(Picture picture, int targetSize) - : base(GetMemoryStream(picture, targetSize), picture.MimeType) - { - - } - - static MemoryStream GetMemoryStream(Picture picture, int targetSize) - { - var localUrl = EngineContext.Current.Resolve().GetThumbLocalPath(picture, targetSize); - var fileStream = new FileStream(localUrl, FileMode.Open, FileAccess.Read, FileShare.Read); - var memoryStream = StreamToMemory(fileStream); - fileStream.Close(); - return memoryStream; - } - - static MemoryStream StreamToMemory(Stream input) - { - - byte[] buffer = new byte[1024]; - int count = 1024; - MemoryStream output; - - // build a new stream - if (input.CanSeek) - { - output = new MemoryStream((int)input.Length); - } - else - { - output = new MemoryStream(); - } - - // iterate stream and transfer to memory stream - do - { - count = input.Read(buffer, 0, count); - if (count == 0) - break; // TODO: might not be correct. Was : Exit Do - output.Write(buffer, 0, count); - } while (true); - - // rewind stream - output.Position = 0; - - // pass back - return output; - - } - - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/CachedFileResult.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/CachedFileResult.cs new file mode 100644 index 0000000000..0967a4d685 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/CachedFileResult.cs @@ -0,0 +1,234 @@ +using System; +using System.Globalization; +using System.IO; +using System.Web; +using System.Web.Hosting; +using System.Web.Mvc; +using SmartStore.Core.IO; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Modelling +{ + public class CachedFileResult : ActionResult, IFileResponse + { + #region Ctor + + public CachedFileResult(string path, string contentType = null) + : this(GetFileInfo(path), contentType) + { + } + + public CachedFileResult(FileInfo file, string contentType = null) + { + Guard.NotNull(file, nameof(file)); + + if (!file.Exists) + { + throw new FileNotFoundException(file.FullName); + } + + Transmitter = new FileStreamTransmitter(file.OpenRead); + ContentType = contentType.NullEmpty() ?? MimeTypes.MapNameToMimeType(file.Name); + FileLength = file.Length; + LastModifiedUtc = file.LastWriteTimeUtc; + } + + public CachedFileResult(IFile file, string contentType = null) + { + Guard.NotNull(file, nameof(file)); + + if (!file.Exists) + { + throw new FileNotFoundException(file.Path); + } + + Transmitter = new FileStreamTransmitter(file.OpenRead); + ContentType = contentType.NullEmpty() ?? MimeTypes.MapNameToMimeType(file.Name); + FileLength = file.Size; + LastModifiedUtc = file.LastUpdated; + } + + public CachedFileResult(VirtualFile file, DateTime? lastModifiedUtc = null, string contentType = null) + { + Guard.NotNull(file, nameof(file)); + + try + { + var fi = GetFileInfo(file.VirtualPath); + if (fi.Exists) + { + ContentType = contentType.NullEmpty() ?? MimeTypes.MapNameToMimeType(fi.Name); + FileLength = fi.Length; + LastModifiedUtc = fi.LastWriteTimeUtc; + } + } + finally + { + if (lastModifiedUtc == null) + { + throw new ArgumentNullException(nameof(lastModifiedUtc), "A modification date must be provided if the VirtualFile cannot be mapped to a physical path."); + } + + if (FileLength == null) + { + ContentType = contentType.NullEmpty() ?? MimeTypes.MapNameToMimeType(file.Name); + using (var stream = file.Open()) + { + FileLength = stream.Length; + } + } + } + + Transmitter = new FileStreamTransmitter(file.Open); + LastModifiedUtc = lastModifiedUtc.Value; + } + + public CachedFileResult(string contentType, DateTime lastModifiedUtc, Func factory, long? fileLength = null) + { + Guard.NotNull(factory, nameof(factory)); + Guard.NotEmpty(contentType, nameof(contentType)); + + Transmitter = new FileStreamTransmitter(factory); + ContentType = contentType; + FileLength = fileLength; + LastModifiedUtc = lastModifiedUtc; + } + + public CachedFileResult(string contentType, DateTime lastModifiedUtc, Func factory, long? fileLength = null) + { + Guard.NotNull(factory, nameof(factory)); + Guard.NotEmpty(contentType, nameof(contentType)); + + Transmitter = new FileBufferTransmitter(factory); + ContentType = contentType; + FileLength = fileLength; + LastModifiedUtc = lastModifiedUtc; + } + + private static FileInfo GetFileInfo(string path) + { + Guard.NotEmpty(path, nameof(path)); + + if (!PathHelper.IsAbsolutePhysicalPath(path)) + { + path = CommonHelper.MapPath(path); + } + + return new FileInfo(path); + } + + #endregion + + #region Properties + + public string ContentType { get; private set; } + + public long? FileLength { get; set; } + + public DateTime LastModifiedUtc { get; set; } + + public TimeSpan MaxAge { get; set; } = TimeSpan.FromDays(1); + + /// + /// If not set, will be auto-generated based on property. + /// + public string ETag { get; set; } + + public FileTransmitter Transmitter { get; private set; } + + /// + /// A callback that will be invoked on successful result execution + /// + public Action OnExecuted { get; set; } + + #endregion + + public override void ExecuteResult(ControllerContext context) + { + var httpContext = context.HttpContext; + var request = httpContext.Request; + var response = httpContext.Response; + + // Fix Last Modified Time. We might need it soon + // if we encounter a Range: and If-Range header + // Using UTC time to avoid daylight savings time bug 83230 + var lastModified = new DateTime(LastModifiedUtc.Year, + LastModifiedUtc.Month, + LastModifiedUtc.Day, + LastModifiedUtc.Hour, + LastModifiedUtc.Minute, + LastModifiedUtc.Second, + 0, + DateTimeKind.Utc); + + // Because we can't set a "Last-Modified" header to any time + // in the future, check the last modified time and set it to + // DateTime.Now if it's in the future. + // This is to fix VSWhidbey #402323 + var utcNow = DateTime.UtcNow; + if (lastModified > utcNow) + { + // use 1 second resolution + lastModified = new DateTime(utcNow.Ticks - (utcNow.Ticks % TimeSpan.TicksPerSecond), DateTimeKind.Utc); + } + + LastModifiedUtc = lastModified; + + // Generate ETag if empty + if (ETag.IsEmpty()) + { + ETag = GenerateETag(httpContext, lastModified, utcNow); + } + + // Determine applicable file responder + var responder = ResolveResponder(request); + + // Execute response (send file) + if (responder.TrySendHeaders(httpContext)) + { + responder.SendFile(httpContext); + } + + // Finish: invoke the optional callback + OnExecuted?.Invoke(); + } + + private FileResponder ResolveResponder(HttpRequestBase request) + { + // is this a request for an unmodified file? + var ifNoneMatch = request.Headers["If-None-Match"]; + if (ifNoneMatch.HasValue() && ETag == ifNoneMatch) + { + return new UnmodifiedFileResponder(this); + } + + // is this a Range request? + var rangeHeader = request.Headers["Range"]; + if (rangeHeader.HasValue() && rangeHeader.StartsWith("bytes", StringComparison.OrdinalIgnoreCase)) + { + return new RangeFileResponder(this, rangeHeader); + } + + // Responder for sending the whole file + return new FullFileResponder(this); + } + + private static string GenerateETag(HttpContextBase context, DateTime lastModified, DateTime now) + { + // Get 64-bit FILETIME stamp + var lastModFileTime = lastModified.ToFileTime(); + var nowFileTime = now.ToFileTime(); + var hexFileTime = lastModFileTime.ToString("X8", CultureInfo.InvariantCulture); + + //// Do what IIS does to determine if this is a weak ETag. + //// Compare the last modified time to now and if the difference is + //// less than or equal to 3 seconds, then it is weak + //if ((nowFileTime - lastModFileTime) <= 30000000) + //{ + // return "W/\"" + hexFileTime + "\""; + //} + + return "\"" + hexFileTime + "\""; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/FileResponder.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/FileResponder.cs new file mode 100644 index 0000000000..3b30853fe1 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/FileResponder.cs @@ -0,0 +1,561 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Web; + +namespace SmartStore.Web.Framework.Modelling +{ + internal abstract class FileResponder + { + protected FileResponder(IFileResponse fileResponse) + { + Guard.NotNull(fileResponse, nameof(fileResponse)); + FileResponse = fileResponse; + } + + protected IFileResponse FileResponse { get; private set; } + + public virtual bool TrySendHeaders(HttpContextBase context) + { + var response = context.Response; + + var utcNow = DateTime.UtcNow; + + // Specify content type + response.ContentType = FileResponse.ContentType; + // We support byte ranges + response.AppendHeader("Accept-Ranges", "bytes"); + // Set the expires header for HTTP 1.0 cliets + response.Cache.SetExpires(utcNow.Add(FileResponse.MaxAge)); + // How often the browser should check that it has the latest version + response.Cache.SetMaxAge(FileResponse.MaxAge); + // The unique identifier for the entity + response.Cache.SetETag(FileResponse.ETag); + // Proxy and browser can cache response + response.Cache.SetCacheability(HttpCacheability.Public); + // Proxy cache should check with orginal server once cache has expired + response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); + //// The date the file was last modified + //context.Response.Cache.SetLastModified(FileResponse.LastModifiedUtc); + + return true; + } + + public abstract void SendFile(HttpContextBase context); + } + + + + internal sealed class UnmodifiedFileResponder : FileResponder + { + public UnmodifiedFileResponder(IFileResponse fileResponse) + : base(fileResponse) + { + } + + public override bool TrySendHeaders(HttpContextBase context) + { + var response = context.Response; + + base.TrySendHeaders(context); + + response.StatusCode = (int)HttpStatusCode.NotModified; + response.StatusDescription = "Not Modified"; + + // Explicitly set the Content-Length header so the client doesn't wait for + // content but keeps the connection open for other requests + response.AddHeader("Content-Length", "0"); + + return true; + } + + public override void SendFile(HttpContextBase context) + { + // Don't send file, it is unmodified. Let browser fetch from its cache. + } + } + + + + internal sealed class FullFileResponder : FileResponder + { + public FullFileResponder(IFileResponse fileResponse) + : base(fileResponse) + { + } + + public override bool TrySendHeaders(HttpContextBase context) + { + base.TrySendHeaders(context); + + // The date the file was last modified + context.Response.Cache.SetLastModified(FileResponse.LastModifiedUtc); + + return true; + } + + public override void SendFile(HttpContextBase context) + { + var fileLength = FileResponse.FileLength ?? FileResponse.Transmitter.GetFileLength(); + FileResponse.Transmitter.TransmitFile(0, fileLength, fileLength, context); + } + } + + + + internal sealed class RangeFileResponder : FileResponder + { + internal struct ByteRange + { + internal long Offset; + internal long Length; + } + + private const string RANGE_BOUNDARY = ""; + private const string MULTIPART_CONTENT_TYPE = "multipart/byteranges; boundary="; + private const string MULTIPART_RANGE_DELIMITER = "--\r\n"; + private const string MULTIPART_RANGE_END = "----\r\n\r\n"; + private const string CONTENT_RANGE_FORMAT = "bytes {0}-{1}/{2}"; + private const int MAX_RANGE_ALLOWED = 5; + + private const int ERROR_ACCESS_DENIED = 5; + + private static readonly string[] _httpDateFormats = new string[] { "r", "dddd, dd-MMM-yy HH':'mm':'ss 'GMT'", "ddd MMM d HH':'mm':'ss yyyy" }; + + private readonly FileResponder _defaultResponder; + private readonly string _rangeHeader; + + public RangeFileResponder(IFileResponse fileResponse, string rangeHeader) + : base(fileResponse) + { + _defaultResponder = new FullFileResponder(fileResponse); + _rangeHeader = rangeHeader; + } + + public override bool TrySendHeaders(HttpContextBase context) + { + var fileLength = FileResponse.FileLength ?? FileResponse.Transmitter.GetFileLength(); + var etag = FileResponse.ETag; + var lastModified = FileResponse.LastModifiedUtc; + + var handled = ExecuteRangeRequest(context, fileLength, _rangeHeader, etag, lastModified); + if (!handled && _defaultResponder.TrySendHeaders(context)) + { + _defaultResponder.SendFile(context); + handled = true; + } + + return handled; + } + + public override void SendFile(HttpContextBase context) + { + // Do nothing here, we have handled everything in 'TrySendHeaders()' already + } + + #region Range Utils + + // Most of the helpers here were copied over from the internal StaticFileHandler.cs + + private bool ExecuteRangeRequest( + HttpContextBase context, + long fileLength, + string rangeHeader, + string etag, + DateTime lastModified) + { + var handled = false; + var request = context.Request; + var response = context.Response; + + // return "416 Requested range not satisfiable" if the file length is zero. + if (fileLength <= 0) + { + SendRangeNotSatisfiable(response, fileLength); + handled = true; + return handled; + } + + var ifRangeHeader = request.Headers["If-Range"]; + if (ifRangeHeader != null && ifRangeHeader.Length > 1) + { + // Is this an ETag or a Date? We only need to check two + // characters; an ETag either begins with W/ or it is quoted. + if (ifRangeHeader[0] == '"') + { + // it's a strong ETag + if (ifRangeHeader != etag) + { + // the etags do not match, and we will therefore return the entire response + return handled; + } + } + else if (ifRangeHeader[0] == 'W' && ifRangeHeader[1] == '/') + { + // it's a weak ETag, and is therefore not usable for sub-range retrieval and + // we will return the entire response + return handled; + } + else + { + // It's a date. If it is greater than or equal to the last-write time of the file, we can send the range. + if (IsOutDated(ifRangeHeader, lastModified)) + { + return handled; + } + } + } + + // the expected format is "bytes = [, , ...]" + // where is "-[]" or "-". + int indexOfEquals = rangeHeader.IndexOf('='); + if (indexOfEquals == -1 || indexOfEquals == rangeHeader.Length - 1) + { + // invalid syntax + return handled; + } + + int startIndex = indexOfEquals + 1; + bool isRangeHeaderSyntacticallyValid = true; + long offset; + long length; + bool isSatisfiable; + bool exceededMax = false; + ByteRange[] byteRanges = null; + int byteRangesCount = 0; + long totalBytes = 0; + while (startIndex < rangeHeader.Length && isRangeHeaderSyntacticallyValid) + { + isRangeHeaderSyntacticallyValid = GetNextRange(rangeHeader, ref startIndex, fileLength, out offset, out length, out isSatisfiable); + + if (!isRangeHeaderSyntacticallyValid) + { + break; + } + + if (!isSatisfiable) + { + continue; + } + + if (byteRanges == null) + { + byteRanges = new ByteRange[16]; + } + + if (byteRangesCount >= byteRanges.Length) + { + // grow byteRanges array + ByteRange[] buffer = new ByteRange[byteRanges.Length * 2]; + Array.Copy(byteRanges, buffer, byteRanges.Length); + byteRanges = buffer; + } + + byteRanges[byteRangesCount].Offset = offset; + byteRanges[byteRangesCount].Length = length; + byteRangesCount++; + + // IIS imposes this limitation too, and sends "400 Bad Request" if exceeded + totalBytes += length; + if (totalBytes > fileLength * MAX_RANGE_ALLOWED) + { + exceededMax = true; + break; + } + } + + if (!isRangeHeaderSyntacticallyValid) + { + return handled; + } + + if (exceededMax) + { + SendBadRequest(response); + handled = true; + return handled; + } + + if (byteRangesCount == 0) + { + // we parsed the Range header and found no satisfiable byte ranges, so return "416 Requested Range Not Satisfiable" + SendRangeNotSatisfiable(response, fileLength); + handled = true; + return handled; + } + + if (byteRangesCount == 1) + { + offset = byteRanges[0].Offset; + length = byteRanges[0].Length; + response.ContentType = FileResponse.ContentType; + string contentRange = String.Format(CultureInfo.InvariantCulture, CONTENT_RANGE_FORMAT, offset, offset + length - 1, fileLength); + response.AppendHeader("Content-Range", contentRange); + + SendResponseHeaders(); + FileResponse.Transmitter.TransmitFile(offset, length, fileLength, context); + } + else + { + response.ContentType = MULTIPART_CONTENT_TYPE; + string contentRange; + string partialContentType = "Content-Type: " + FileResponse.ContentType + "\r\n"; + + SendResponseHeaders(); + + for (int i = 0; i < byteRangesCount; i++) + { + offset = byteRanges[i].Offset; + length = byteRanges[i].Length; + response.Write(MULTIPART_RANGE_DELIMITER); + response.Write(partialContentType); + response.Write("Content-Range: "); + contentRange = String.Format(CultureInfo.InvariantCulture, CONTENT_RANGE_FORMAT, offset, offset + length - 1, fileLength); + response.Write(contentRange); + response.Write("\r\n\r\n"); + // Transmit + FileResponse.Transmitter.TransmitFile(offset, length, fileLength, context); + response.Write("\r\n"); + } + response.Write(MULTIPART_RANGE_END); + } + + handled = true; + return handled; + + void SendResponseHeaders() + { + // Send a "206 Partial Content" status + response.StatusCode = (int)HttpStatusCode.PartialContent; + response.AppendHeader("Accept-Ranges", "bytes"); + response.Cache.SetLastModified(lastModified); + response.Cache.SetETag(etag); + response.Cache.SetCacheability(HttpCacheability.Public); + } + } + + private void SendBadRequest(HttpResponseBase response) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + response.Write("Bad Request"); + } + + private void SendRangeNotSatisfiable(HttpResponseBase response, long fileLength) + { + response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable; + response.ContentType = null; + response.AppendHeader("Content-Range", "bytes */" + fileLength.ToString(NumberFormatInfo.InvariantInfo)); + } + + private static bool IsOutDated(string ifRangeHeader, DateTime lastModified) + { + try + { + var utcLastModified = lastModified.ToUniversalTime(); + var utc = UtcParse(ifRangeHeader); + return (utc < utcLastModified); + } + catch + { + return true; + } + } + + private static DateTime UtcParse(string time) + { + Guard.NotNull(time, nameof(time)); + + var dtStyles = DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal; + if (DateTime.TryParseExact(time, _httpDateFormats, null, dtStyles, out var utcDate)) + { + return utcDate; + } + + throw new FormatException($"{time} is an invalid date expression."); + } + + // initial space characters are skipped, and the string of digits up until the first non-digit + // are converted to a long. If digits are found the method returns true; otherwise false. + private static bool GetLongFromSubstring(string s, ref int startIndex, out long result) + { + result = 0; + + // get index of first digit + MovePastSpaceCharacters(s, ref startIndex); + int beginIndex = startIndex; + + // get index of last digit + MovePastDigits(s, ref startIndex); + int endIndex = startIndex - 1; + + // are there any digits? + if (endIndex < beginIndex) + { + return false; + } + + long multipleOfTen = 1; + for (int i = endIndex; i >= beginIndex; i--) + { + int digit = s[i] - '0'; + result += digit * multipleOfTen; + multipleOfTen *= 10; + // check for overflow + if (result < 0) + { + return false; + } + } + + return true; + } + + // The Range header consists of one or more byte range specifiers. E.g, "Range: bytes=0-1024,-1024" is a request + // for the first and last 1024 bytes of a file. Before this method is called, startIndex points to the beginning + // of a byte range specifier; and afterwards it points to the beginning of the next byte range specifier. + // If the current byte range specifier is syntactially inavlid, this function will return false indicating that the + // Range header must be ignored. If the function returns true, then the byte range specifier will be converted to + // an offset and length, and the startIndex will be incremented to the next byte range specifier. The byte range + // specifier (offset and length) returned by this function is satisfiable if and only if isSatisfiable is true. + private static bool GetNextRange(string rangeHeader, ref int startIndex, long fileLength, out long offset, out long length, out bool isSatisfiable) + { + // startIndex is first char after '=', or first char after ',' + Debug.Assert(startIndex < rangeHeader.Length, "startIndex < rangeHeader.Length"); + + offset = 0; + length = 0; + isSatisfiable = false; + + // A Range request to an empty file is never satisfiable, and will always receive a 416 status. + if (fileLength <= 0) + { + // put startIndex at end of string so we don't try to call GetNextRange again + startIndex = rangeHeader.Length; + return true; + } + + MovePastSpaceCharacters(rangeHeader, ref startIndex); + + if (startIndex < rangeHeader.Length && rangeHeader[startIndex] == '-') + { + // this range is of the form "-mmm" + startIndex++; + if (!GetLongFromSubstring(rangeHeader, ref startIndex, out length)) + { + return false; + } + + if (length > fileLength) + { + // send entire file + offset = 0; + length = fileLength; + } + else + { + // send last N bytes + offset = fileLength - length; + } + + isSatisfiable = IsRangeSatisfiable(offset, length, fileLength); + // we parsed the current range, but need to successfully move the startIndex to the next range + return IncrementToNextRange(rangeHeader, ref startIndex); + } + else + { + // this range is of the form "nnn-[mmm]" + if (!GetLongFromSubstring(rangeHeader, ref startIndex, out offset)) + { + return false; + } + + // increment startIndex past '-' + if (startIndex < rangeHeader.Length && rangeHeader[startIndex] == '-') + { + startIndex++; + } + else + { + return false; + } + + if (!GetLongFromSubstring(rangeHeader, ref startIndex, out long endPos)) + { + // assume range is of form "nnn-". If it isn't, + // the call to IncrementToNextRange will return false + length = fileLength - offset; + } + else + { + // if...greater than or equal to the current length of the entity-body, last-byte-pos + // is taken to be equal to one less than the current length of the entity- body in bytes. + if (endPos > fileLength - 1) + { + endPos = fileLength - 1; + } + + length = endPos - offset + 1; + + if (length < 1) + { + // the byte range specifier is syntactially invalid + // because the last-byte-pos < first-byte-pos + return false; + } + } + + isSatisfiable = IsRangeSatisfiable(offset, length, fileLength); + + // we parsed the current range, but need to successfully move the startIndex to the next range + return IncrementToNextRange(rangeHeader, ref startIndex); + } + } + + private static bool IncrementToNextRange(string s, ref int startIndex) + { + // increment startIndex until next token and return true, unless the syntax is invalid + MovePastSpaceCharacters(s, ref startIndex); + + if (startIndex < s.Length) + { + if (s[startIndex] != ',') + { + return false; + } + // move to first char after ',' + startIndex++; + } + + return true; + } + + private static bool IsRangeSatisfiable(long offset, long length, long fileLength) + { + return (offset < fileLength && length > 0); + } + + private static bool IsSecurityError(int ErrorCode) + { + return (ErrorCode == ERROR_ACCESS_DENIED); + } + + private static void MovePastSpaceCharacters(string s, ref int startIndex) + { + while (startIndex < s.Length && s[startIndex] == ' ') + { + startIndex++; + } + } + + private static void MovePastDigits(string s, ref int startIndex) + { + while (startIndex < s.Length && s[startIndex] <= '9' && s[startIndex] >= '0') + { + startIndex++; + } + } + + #endregion + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/FileTransmitter.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/FileTransmitter.cs new file mode 100644 index 0000000000..82b2399294 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/FileTransmitter.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.Web; + +namespace SmartStore.Web.Framework.Modelling +{ + public abstract class FileTransmitter + { + public abstract long GetFileLength(); + public abstract void TransmitFile(long offset, long length, long fileLength, HttpContextBase context); + } + + public sealed class FileBufferTransmitter : FileTransmitter + { + private readonly Func _bufferFactory; + private byte[] _buffer; + + public FileBufferTransmitter(Func bufferFactory) + { + Guard.NotNull(bufferFactory, nameof(bufferFactory)); + _bufferFactory = bufferFactory; + } + + public override long GetFileLength() + { + return GetBuffer().LongLength; + } + + public override void TransmitFile(long offset, long length, long fileLength, HttpContextBase context) + { + context.Response.OutputStream.Write(GetBuffer(), (int)offset, (int)length); + } + + private byte[] GetBuffer() + { + if (_buffer == null) + { + _buffer = _bufferFactory(); + if (_buffer == null) + { + throw new NullReferenceException("File buffer cannot be NULL."); + } + } + + return _buffer; + } + } + + public sealed class FileStreamTransmitter : FileTransmitter + { + // Default buffer size as defined in BufferedStream type + const int DefaultBufferSize = 81920; + + private readonly Func _streamFactory; + private Stream _stream; + + public FileStreamTransmitter(Func streamFactory) + { + Guard.NotNull(streamFactory, nameof(streamFactory)); + _streamFactory = streamFactory; + } + + public override long GetFileLength() + { + return GetStream().Length; + } + + public override void TransmitFile(long offset, long length, long fileLength, HttpContextBase context) + { + var response = context.Response; + var stream = GetStream(); + + response.BufferOutput = false; + + using (stream) + { + int bufferSize = (int)Math.Min(DefaultBufferSize, length); + byte[] buffer = new byte[bufferSize]; + + int read; + long remaining = length; + + stream.Seek(offset, SeekOrigin.Begin); + while ((remaining > 0) && (read = stream.Read(buffer, 0, buffer.Length)) != 0) + { + if (response.IsClientConnected) + { + response.OutputStream.Write(buffer, 0, read); + //response.Flush(); + + remaining -= read; + } + else + { + remaining = 0; + break; + } + } + } + } + + private Stream GetStream() + { + if (_stream == null) + { + _stream = _streamFactory(); + if (_stream == null) + { + throw new NullReferenceException("File stream cannot be NULL."); + } + } + + return _stream; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/IFileResponse.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/IFileResponse.cs new file mode 100644 index 0000000000..f490fe8dac --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/IFileResponse.cs @@ -0,0 +1,14 @@ +using System; + +namespace SmartStore.Web.Framework.Modelling +{ + public interface IFileResponse + { + string ContentType { get; } + long? FileLength { get; } + DateTime LastModifiedUtc { get; } + TimeSpan MaxAge { get; } + string ETag { get; } + FileTransmitter Transmitter { get; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/JsonNetResult.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/JsonNetResult.cs index b64e060a45..d1478076e2 100644 --- a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/JsonNetResult.cs +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/JsonNetResult.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Web.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -23,7 +19,7 @@ public JsonNetResult(IDateTimeHelper dateTimeHelper) public JsonNetResult(IDateTimeHelper dateTimeHelper, JsonSerializerSettings settings) { - Guard.ArgumentNotNull(() => dateTimeHelper); + Guard.NotNull(dateTimeHelper, nameof(dateTimeHelper)); _dateTimeHelper = dateTimeHelper; _settings = settings; @@ -31,7 +27,7 @@ public JsonNetResult(IDateTimeHelper dateTimeHelper, JsonSerializerSettings sett public override void ExecuteResult(ControllerContext context) { - Guard.ArgumentNotNull(() => context); + Guard.NotNull(context, nameof(context)); if (this.Data == null) return; @@ -53,21 +49,19 @@ public override void ExecuteResult(ControllerContext context) var serializerSettings = _settings ?? new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore, + TypeNameHandling = TypeNameHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore, + DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind, + DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, // Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions // from deserialization errors that might occur from deeply nested objects. - MaxDepth = 32, - - // Do not change this setting - // Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types - TypeNameHandling = TypeNameHandling.None + MaxDepth = 32 }; if (_settings == null) { - serializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind; - serializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; - var utcDateTimeConverter = new UTCDateTimeConverter(_dateTimeHelper, new JavaScriptDateTimeConverter()); serializerSettings.Converters.Add(utcDateTimeConverter); } @@ -87,7 +81,7 @@ class UTCDateTimeConverter : DateTimeConverterBase public UTCDateTimeConverter(IDateTimeHelper dateTimeHelper, DateTimeConverterBase innerConverter) { - Guard.ArgumentNotNull(() => innerConverter); + Guard.NotNull(innerConverter, nameof(innerConverter)); _dateTimeHelper = dateTimeHelper; _innerConverter = innerConverter; diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/PermissiveRedirectResult.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/PermissiveRedirectResult.cs new file mode 100644 index 0000000000..e394aba608 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/PermissiveRedirectResult.cs @@ -0,0 +1,25 @@ +using System; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.Modelling +{ + /// + /// Allows redirects from within child actions while keeping TempData + /// + public class PermissiveRedirectResult : ActionResult + { + private readonly string _url; + + public PermissiveRedirectResult(string url) + { + _url = url; + } + + public override void ExecuteResult(ControllerContext context) + { + var url = UrlHelper.GenerateContentUrl(_url, context.HttpContext); + context.Controller.TempData.Keep(); + context.HttpContext.Response.Redirect(url, false); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/Results/RootActionViewResult.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/RootActionViewResult.cs new file mode 100644 index 0000000000..2e9f733b80 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/Results/RootActionViewResult.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.Modelling.Results +{ + public class RootActionViewResult : ViewResult + { + protected override ViewEngineResult FindView(ControllerContext context) + { + if (base.ViewName.IsEmpty()) + throw new InvalidOperationException($"{nameof(ViewName)} is required when using {nameof(RootActionViewResult)}."); + + return base.FindView(context.GetRootControllerContext()); + } + } + + public class RootActionPartialViewResult : PartialViewResult + { + protected override ViewEngineResult FindView(ControllerContext context) + { + if (base.ViewName.IsEmpty()) + throw new InvalidOperationException($"{nameof(ViewName)} is required when using {nameof(RootActionPartialViewResult)}."); + + return base.FindView(context.GetRootControllerContext()); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Modelling/SmartModelBinder.cs b/src/Presentation/SmartStore.Web.Framework/Modelling/SmartModelBinder.cs index 4bf8481d2d..658eb4a74b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Modelling/SmartModelBinder.cs +++ b/src/Presentation/SmartStore.Web.Framework/Modelling/SmartModelBinder.cs @@ -1,21 +1,24 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Web.Mvc; +using SmartStore.Core.Html; +using SmartStore.Web.Framework.Security; namespace SmartStore.Web.Framework.Modelling { - public class SmartModelBinder : DefaultModelBinder + public class SmartModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var modelType = bindingContext.ModelType; - if (bindingContext.ModelType == typeof(CustomPropertiesDictionary)) + if (modelType == typeof(CustomPropertiesDictionary)) { return BindCustomPropertiesDictioary(controllerContext, bindingContext); } - + var model = base.BindModel(controllerContext, bindingContext); if (model is ModelBase) @@ -26,6 +29,19 @@ public override object BindModel(ControllerContext controllerContext, ModelBindi return model; } + protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor property, object value) + { + var attrs = property.Attributes; + var sanitizeHtmlAttr = attrs.OfType().FirstOrDefault(); + + if (property.PropertyType == typeof(string) && sanitizeHtmlAttr != null && value is string input && !string.IsNullOrWhiteSpace(input)) + { + value = HtmlUtils.SanitizeHtml(input, sanitizeHtmlAttr.IsFragment); + } + + base.SetProperty(controllerContext, bindingContext, property, value); + } + private CustomPropertiesDictionary BindCustomPropertiesDictioary(ControllerContext controllerContext, ModelBindingContext bindingContext) { var model = bindingContext.Model as CustomPropertiesDictionary ?? new CustomPropertiesDictionary(); diff --git a/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfPartialViewContent.cs b/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfPartialViewContent.cs index fb266aa64e..eef903e167 100644 --- a/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfPartialViewContent.cs +++ b/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfPartialViewContent.cs @@ -4,7 +4,6 @@ namespace SmartStore.Web.Framework.Pdf { - public class PdfPartialViewContent : PdfHtmlContent { public PdfPartialViewContent(string partialViewName, object model, ControllerContext controllerContext) @@ -12,5 +11,4 @@ public PdfPartialViewContent(string partialViewName, object model, ControllerCon { } } - } diff --git a/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfRouteContent.cs b/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfRouteContent.cs index 407ae9a490..170a240570 100644 --- a/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfRouteContent.cs +++ b/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfRouteContent.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; using System.Web.Mvc; using System.Web.Routing; using SmartStore.Services.Pdf; namespace SmartStore.Web.Framework.Pdf -{ - +{ public class PdfRouteContent : PdfUrlContent { public PdfRouteContent(string routeName, ControllerContext controllerContext) @@ -32,15 +30,12 @@ public PdfRouteContent(string action, string controller, RouteValueDictionary ro protected PdfRouteContent(string routeName, string action, string controller, RouteValueDictionary routeValues, ControllerContext controllerContext) : base(UrlHelper.GenerateUrl(routeName, action, controller, routeValues, RouteTable.Routes, controllerContext.RequestContext, true)) { - //Guard.ArgumentNotNull(() => controllerContext); + //Guard.NotNull(controllerContext, nameof(controllerContext)); //if (routeName.IsEmpty() || action.IsEmpty()) //{ // throw Error.InvalidOperation("Either 'routeName' or 'action' must be set"); //} } - - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfViewContent.cs b/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfViewContent.cs index 46c389750e..a96f90c4b5 100644 --- a/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfViewContent.cs +++ b/src/Presentation/SmartStore.Web.Framework/Pdf/Content/PdfViewContent.cs @@ -6,11 +6,9 @@ using SmartStore.Web.Framework.Controllers; namespace SmartStore.Web.Framework.Pdf -{ - +{ public class PdfViewContent : PdfHtmlContent { - public PdfViewContent(string viewName, object model, ControllerContext controllerContext) : this(viewName, null, model, controllerContext) { @@ -23,7 +21,7 @@ public PdfViewContent(string viewName, string masterName, object model, Controll protected internal static string ViewToString(string viewName, string masterName, object model, bool isPartial, ControllerContext context, bool throwOnError) { - Guard.ArgumentNotNull(() => context); + Guard.NotNull(context, nameof(context)); string html = null; @@ -48,5 +46,4 @@ protected internal static string ViewToString(string viewName, string masterName return html; } } - } diff --git a/src/Presentation/SmartStore.Web.Framework/Pdf/PdfHeaderFooterVariables.cs b/src/Presentation/SmartStore.Web.Framework/Pdf/PdfHeaderFooterVariables.cs index 854b365091..4b1dd669f6 100644 --- a/src/Presentation/SmartStore.Web.Framework/Pdf/PdfHeaderFooterVariables.cs +++ b/src/Presentation/SmartStore.Web.Framework/Pdf/PdfHeaderFooterVariables.cs @@ -4,10 +4,8 @@ namespace SmartStore.Web.Framework.Pdf { - public class PdfHeaderFooterVariables { - /// /// The number of the page currently being printed /// @@ -57,6 +55,5 @@ public class PdfHeaderFooterVariables /// The number of pages in the current site being converted /// public int SitePages { get; set; } - } } diff --git a/src/Presentation/SmartStore.Web.Framework/Pdf/PdfReceiptHeaderFooterModel.cs b/src/Presentation/SmartStore.Web.Framework/Pdf/PdfReceiptHeaderFooterModel.cs index 8ccb73199e..67a69d0b5a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Pdf/PdfReceiptHeaderFooterModel.cs +++ b/src/Presentation/SmartStore.Web.Framework/Pdf/PdfReceiptHeaderFooterModel.cs @@ -16,6 +16,8 @@ public partial class PdfReceiptHeaderFooterModel : ModelBase public BankConnectionSettings MerchantBankAccount { get; set; } public ContactDataSettings MerchantContactData { get; set; } + public string MerchantFormattedAddress { get; set; } + public PdfHeaderFooterVariables Variables { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Pdf/PdfResult.cs b/src/Presentation/SmartStore.Web.Framework/Pdf/PdfResult.cs index 8d11030975..19df5bc70a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Pdf/PdfResult.cs +++ b/src/Presentation/SmartStore.Web.Framework/Pdf/PdfResult.cs @@ -4,6 +4,7 @@ using System.Web.Mvc; using System.Web.Security; using SmartStore.Services.Pdf; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.Pdf { @@ -13,7 +14,7 @@ public class PdfResult : ActionResult public PdfResult(IPdfConverter converter, PdfConvertSettings settings) { - Guard.ArgumentNotNull(() => converter); + Guard.NotNull(converter, nameof(converter)); this.Converter = converter; this.Settings = settings ?? new PdfConvertSettings(); @@ -34,7 +35,7 @@ protected HttpResponseBase PrepareResponse(HttpResponseBase response) if (FileName.HasValue()) { - response.AddHeader("Content-Disposition", "attachment; filename=\"{0}\"".FormatCurrent(SanitizeFileName(FileName))); + response.AddHeader("Content-Disposition", "attachment; filename=\"{0}\"".FormatCurrent(PathHelper.SanitizeFileName(FileName))); } response.AddHeader("Content-Type", ContentType); @@ -48,15 +49,5 @@ public override void ExecuteResult(ControllerContext context) var response = PrepareResponse(context.HttpContext.Response); response.OutputStream.Write(buffer, 0, buffer.Length); } - - private static string SanitizeFileName(string name) - { - string invalidChars = Regex.Escape(new string(Path.GetInvalidPathChars()) + new string(Path.GetInvalidFileNameChars())); - string invalidCharsPattern = string.Format(@"[{0}]+", invalidChars); - - string result = Regex.Replace(name, invalidCharsPattern, "-"); - return result; - } - } } diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginDebugViewVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginDebugViewVirtualPathProvider.cs deleted file mode 100644 index 4b0bba6a21..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginDebugViewVirtualPathProvider.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web; -using System.Web.Caching; -using System.Web.Hosting; - -namespace SmartStore.Web.Framework.Plugins -{ - public class PluginDebugViewVirtualPathProvider : VirtualPathProvider - { - private readonly ConcurrentDictionary _cachedDebugFilePaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly DirectoryInfo _pluginsDebugDir; - - public PluginDebugViewVirtualPathProvider() - { - var appRootPath = HostingEnvironment.MapPath("~/").EnsureEndsWith("\\"); - var debugPath = Path.GetFullPath(Path.Combine(appRootPath, @"..\..\Plugins")); - if (Directory.Exists(debugPath)) - { - _pluginsDebugDir = new DirectoryInfo(debugPath); - } - } - - public override bool FileExists(string virtualPath) - { - // Require files in production path to exist, do never fallback to dev path. - // Doing so could lead to deployment errors (e.g. forgetting to copy a view file to production folder) - return Previous.FileExists(virtualPath); - } - - public override VirtualFile GetFile(string virtualPath) - { - string appRelativePath; - if (!IsPluginPath(virtualPath, out appRelativePath)) - { - return Previous.GetFile(virtualPath); - } - - string debugPath = ResolveDebugFilePath(appRelativePath); - return debugPath != null - ? new DebugPluginVirtualFile(virtualPath, debugPath) - : Previous.GetFile(virtualPath); - } - - public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) - { - string appRelativePath; - if (!IsPluginPath(virtualPath, out appRelativePath)) - { - return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); - } - - string debugPath = ResolveDebugFilePath(appRelativePath); - return debugPath != null - ? new CacheDependency(debugPath) - : Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); - } - - public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) - { - string appRelativePath; - if (!IsPluginPath(virtualPath, out appRelativePath)) - { - return Previous.GetFileHash(virtualPath, virtualPathDependencies); - } - - string debugPath = ResolveDebugFilePath(appRelativePath); - return debugPath != null - ? File.GetLastWriteTime(debugPath).ToString() - : Previous.GetFileHash(virtualPath, virtualPathDependencies); - } - - private string ResolveDebugFilePath(string appRelativePath) - { - return _cachedDebugFilePaths.GetOrAdd(appRelativePath, FindDebugFile); - } - - private string FindDebugFile(string appRelativePath) - { - if (_pluginsDebugDir == null) - return null; - - var unrooted = appRelativePath.Substring(10); // strip "~/Plugins/" - string area = unrooted.Substring(0, unrooted.IndexOf('/')); - - var pluginDir = _pluginsDebugDir.EnumerateDirectories("*{0}*".FormatInvariant(area), SearchOption.TopDirectoryOnly).FirstOrDefault(); - if (pluginDir != null) - { - // get "Views/Something/View.cshtml" - var viewPath = unrooted.Substring(area.Length + 1); - var result = Path.Combine(pluginDir.FullName, viewPath).Replace("/", "\\"); - return File.Exists(result) ? result : null; - } - - return null; - } - - private static bool IsPluginPath(string virtualPath, out string appRelativePath) - { - appRelativePath = VirtualPathUtility.ToAppRelative(virtualPath); - var result = appRelativePath.StartsWith("~/Plugins/", StringComparison.InvariantCultureIgnoreCase); - return result; - } - - } - - internal class DebugPluginVirtualFile : VirtualFile - { - private readonly string _debugPath; - - public DebugPluginVirtualFile(string virtualPath, string debugPath) - : base(virtualPath) - { - this._debugPath = debugPath; - } - - public override bool IsDirectory - { - get { return false; } - } - - public override Stream Open() - { - var fileView = new FileStream(_debugPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - return fileView; - } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginMediator.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginMediator.cs index 334b638a18..9f8d8026f0 100644 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginMediator.cs +++ b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginMediator.cs @@ -24,33 +24,30 @@ public class PluginMediator public PluginMediator(ICommonServices services, IComponentContext ctx) { - this._services = services; - this._ctx = ctx; - - T = NullLocalizer.Instance; + _services = services; + _ctx = ctx; } - public Localizer T { get; set; } - - public string GetLocalizedFriendlyName(ProviderMetadata metadata, int languageId = 0, bool returnDefaultValue = true) + public string GetLocalizedFriendlyName(IProviderMetadata metadata, int languageId = 0, bool returnDefaultValue = true) { return GetLocalizedValue(metadata, "FriendlyName", x => x.FriendlyName, languageId, returnDefaultValue); } - public string GetLocalizedDescription(ProviderMetadata metadata, int languageId = 0, bool returnDefaultValue = true) + public string GetLocalizedDescription(IProviderMetadata metadata, int languageId = 0, bool returnDefaultValue = true) { return GetLocalizedValue(metadata, "Description", x => x.Description, languageId, returnDefaultValue); } - public string GetLocalizedValue(ProviderMetadata metadata, + public string GetLocalizedValue(TMetadata metadata, string propertyName, - Expression> fallback, + Expression> fallback, int languageId = 0, - bool returnDefaultValue = true) + bool returnDefaultValue = true) + where TMetadata : IProviderMetadata { - Guard.ArgumentNotNull(() => metadata); + Guard.NotNull(metadata, nameof(metadata)); - string systemName = metadata.SystemName; + var systemName = metadata.SystemName; var resourceName = metadata.ResourceKeyPattern.FormatInvariant(metadata.SystemName, propertyName); string result = _services.Localization.GetResource(resourceName, languageId, false, "", true); @@ -60,11 +57,11 @@ public string GetLocalizedValue(ProviderMetadata metadata, return result; } - public void SaveLocalizedValue(ProviderMetadata metadata, int languageId, string propertyName, string value) + public void SaveLocalizedValue(IProviderMetadata metadata, int languageId, string propertyName, string value) { - Guard.ArgumentNotNull(() => metadata); - Guard.ArgumentIsPositive(languageId, "languageId"); - Guard.ArgumentNotEmpty(() => propertyName); + Guard.NotNull(metadata, nameof(metadata)); + Guard.IsPositive(languageId, nameof(languageId)); + Guard.NotEmpty(propertyName, nameof(propertyName)); var resourceName = metadata.ResourceKeyPattern.FormatInvariant(metadata.SystemName, propertyName); var resource = _services.Localization.GetLocaleStringResourceByName(resourceName, languageId, false); @@ -82,21 +79,19 @@ public void SaveLocalizedValue(ProviderMetadata metadata, int languageId, string resource.ResourceValue = value; _services.Localization.UpdateLocaleStringResource(resource); } - _services.Localization.ClearCache(); } else { if (value.HasValue()) { // insert - resource = new LocaleStringResource() + resource = new LocaleStringResource { LanguageId = languageId, ResourceName = resourceName, ResourceValue = value, }; _services.Localization.InsertLocaleStringResource(resource); - _services.Localization.ClearCache(); } } } @@ -114,7 +109,7 @@ public T GetSetting(ProviderMetadata metadata, string propertyName) public void SetUserDisplayOrder(ProviderMetadata metadata, int displayOrder) { - Guard.ArgumentNotNull(() => metadata); + Guard.NotNull(metadata, nameof(metadata)); metadata.DisplayOrder = displayOrder; SetSetting(metadata, "DisplayOrder", displayOrder); @@ -122,12 +117,12 @@ public void SetUserDisplayOrder(ProviderMetadata metadata, int displayOrder) public void SetSetting(ProviderMetadata metadata, string propertyName, T value) { - Guard.ArgumentNotNull(() => metadata); - Guard.ArgumentNotEmpty(() => propertyName); + Guard.NotNull(metadata, nameof(metadata)); + Guard.NotEmpty(propertyName, nameof(propertyName)); var settingKey = metadata.SettingKeyPattern.FormatInvariant(metadata.SystemName, propertyName); - using (_services.Settings.BeginBatch()) + using (_services.Settings.BeginScope()) { if (value != null) { @@ -149,7 +144,7 @@ public TModel ToProviderModel(Provider provider, b where TModel : ProviderModel, new() where TProvider : IProvider { - Guard.ArgumentNotNull(() => provider); + Guard.NotNull(provider, nameof(provider)); var metadata = provider.Metadata; var model = new TModel(); @@ -170,10 +165,8 @@ public TModel ToProviderModel(Provider provider, b { var routeInfo = _routesCache.GetOrAdd(model.SystemName, (key) => { - string actionName, controllerName; - RouteValueDictionary routeValues; var configurable = (IConfigurable)provider.Value; - configurable.GetConfigurationRoute(out actionName, out controllerName, out routeValues); + configurable.GetConfigurationRoute(out var actionName, out var controllerName, out var routeValues); if (actionName.IsEmpty()) { @@ -284,7 +277,7 @@ public string GetDefaultIconUrl(string groupName) public void ActivateDependentWidgets(ProviderMetadata parent, bool activate) { - Guard.ArgumentNotNull(() => parent); + Guard.NotNull(parent, nameof(parent)); if (parent.DependentWidgets == null || parent.DependentWidgets.Length == 0) return; diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginRazorHost.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginRazorHost.cs new file mode 100644 index 0000000000..01527d924f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginRazorHost.cs @@ -0,0 +1,42 @@ +using System; +using System.Web; +using System.Web.Hosting; +using System.Web.Mvc.Razor; +using System.Web.WebPages.Razor; +using SmartStore.Utilities; +using SmartStore.Web.Framework.Theming; + +namespace SmartStore.Web.Framework.Plugins +{ + public class PluginRazorHostFactory : WebRazorHostFactory + { + public override WebPageRazorHost CreateHost(string virtualPath, string physicalPath) + { + // Implementation borrowed from MvcRazorHostFactory + var host = base.CreateHost(virtualPath, physicalPath); + + if (!host.IsSpecialPage) + { + return new PluginRazorHost(virtualPath, physicalPath); + } + + return host; + } + } + + public class PluginRazorHost : MvcWebPageRazorHost + { + public PluginRazorHost(string virtualPath, string physicalPath) + : base(virtualPath, physicalPath) + { + if (CommonHelper.IsDevEnvironment && HttpContext.Current.IsDebuggingEnabled) + { + var file = HostingEnvironment.VirtualPathProvider.GetFile(virtualPath) as DebugVirtualFile; + if (file != null) + { + PhysicalPath = file.PhysicalPath; + } + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs index 6b60298eb7..e08ef6561e 100644 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs +++ b/src/Presentation/SmartStore.Web.Framework/Plugins/ProviderManager.cs @@ -15,9 +15,9 @@ public partial class ProviderManager : IProviderManager public ProviderManager(IComponentContext ctx, ICommonServices services, PluginMediator pluginMediator) { - this._ctx = ctx; - this._services = services; - this._pluginMediator = pluginMediator; + _ctx = ctx; + _services = services; + _pluginMediator = pluginMediator; } public Provider GetProvider(string systemName, int storeId = 0) where TProvider : IProvider @@ -45,7 +45,7 @@ public Provider GetProvider(string systemName, int storeId public Provider GetProvider(string systemName, int storeId = 0) { - Guard.ArgumentNotEmpty(() => systemName); + Guard.NotEmpty(systemName, nameof(systemName)); var provider = _ctx.ResolveOptionalNamed>(systemName); if (provider != null) diff --git a/src/Presentation/SmartStore.Web.Framework/Properties/AssemblyInfo.cs b/src/Presentation/SmartStore.Web.Framework/Properties/AssemblyInfo.cs index 7ce572fa25..3803039590 100644 --- a/src/Presentation/SmartStore.Web.Framework/Properties/AssemblyInfo.cs +++ b/src/Presentation/SmartStore.Web.Framework/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Runtime.CompilerServices; +using System.Security; [assembly: AssemblyTitle("SmartStore.Web.Framework")] diff --git a/src/Presentation/SmartStore.Web.Framework/Routing/GuidConstraint.cs b/src/Presentation/SmartStore.Web.Framework/Routing/GuidConstraint.cs index 105fceb0b1..97f1550d1b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Routing/GuidConstraint.cs +++ b/src/Presentation/SmartStore.Web.Framework/Routing/GuidConstraint.cs @@ -20,9 +20,7 @@ public bool Match(HttpContextBase httpContext, Route route, string parameterName if (!string.IsNullOrEmpty(stringValue)) { - Guid guidValue; - - return Guid.TryParse(stringValue, out guidValue) && + return Guid.TryParse(stringValue, out var guidValue) && (_allowEmpty || guidValue != Guid.Empty); } } diff --git a/src/Presentation/SmartStore.Web.Framework/Security/AdminValidateIpAddressAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/AdminValidateIpAddressAttribute.cs index 9e7f6afafd..aca5a27830 100644 --- a/src/Presentation/SmartStore.Web.Framework/Security/AdminValidateIpAddressAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Security/AdminValidateIpAddressAttribute.cs @@ -7,54 +7,58 @@ namespace SmartStore.Web.Framework.Security { - public class AdminValidateIpAddressAttribute : ActionFilterAttribute + public class AdminValidateIpAddressAttribute : FilterAttribute, IAuthorizationFilter { - public Lazy WebHelper { get; set; } public Lazy SecuritySettings { get; set; } - - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - if (filterContext == null || filterContext.HttpContext == null) - return; - HttpRequestBase request = filterContext.HttpContext.Request; - if (request == null) - return; + public virtual void OnAuthorization(AuthorizationContext filterContext) + { + if (filterContext == null || filterContext.HttpContext == null) + return; + + HttpRequestBase request = filterContext.HttpContext.Request; + if (request == null) + return; + + // don't apply filter to child methods + if (filterContext.IsChildAction) + return; - //don't apply filter to child methods - if (filterContext.IsChildAction) - return; + // Prevent lockout + if (filterContext.HttpContext.Request.IsLocal) + return; - bool ok = false; + bool ok = false; var ipAddresses = SecuritySettings.Value.AdminAreaAllowedIpAddresses; - if (ipAddresses != null && ipAddresses.Count > 0) - { + if (ipAddresses != null && ipAddresses.Count > 0) + { var webHelper = WebHelper.Value; - foreach (string ip in ipAddresses) - if (ip.Equals(webHelper.GetCurrentIpAddress(), StringComparison.InvariantCultureIgnoreCase)) - { - ok = true; - break; - } - } - else - { - //no restrictions - ok = true; - } + var curIpAddress = webHelper.GetCurrentIpAddress(); + foreach (string ip in ipAddresses) + if (ip.Equals(curIpAddress, StringComparison.InvariantCultureIgnoreCase)) + { + ok = true; + break; + } + } + else + { + //no restrictions + ok = true; + } - if (!ok) - { - //ensure that it's not 'Access denied' page + if (!ok) + { + //ensure that it's not 'Access denied' page var webHelper = WebHelper.Value; - var thisPageUrl = webHelper.GetThisPageUrl(false); - if (!thisPageUrl.StartsWith(string.Format("{0}admin/security/accessdenied", webHelper.GetStoreLocation()), StringComparison.InvariantCultureIgnoreCase)) - { - //redirect to 'Access denied' page - filterContext.Result = new RedirectResult(webHelper.GetStoreLocation() + "admin/security/accessdenied"); - } - } - } - } + var thisPageUrl = webHelper.GetThisPageUrl(false); + if (!thisPageUrl.StartsWith(string.Format("{0}admin/security/accessdenied", webHelper.GetStoreLocation()), StringComparison.InvariantCultureIgnoreCase)) + { + //redirect to 'Access denied' page + filterContext.Result = new RedirectResult(webHelper.GetStoreLocation() + "admin/security/accessdenied"); + } + } + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaSettings.cs b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/CaptchaSettings.cs similarity index 79% rename from src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaSettings.cs rename to src/Presentation/SmartStore.Web.Framework/Security/Captcha/CaptchaSettings.cs index 507685b560..99a0e1e619 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaSettings.cs +++ b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/CaptchaSettings.cs @@ -1,7 +1,6 @@ - -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; -namespace SmartStore.Web.Framework.UI.Captcha +namespace SmartStore.Web.Framework.Security { public class CaptchaSettings : ISettings { @@ -14,8 +13,10 @@ public class CaptchaSettings : ISettings public bool ShowOnAskQuestionPage { get; set; } public bool ShowOnBlogCommentPage { get; set; } public bool ShowOnNewsCommentPage { get; set; } + public bool ShowOnForumPage { get; set; } public bool ShowOnProductReviewPage { get; set; } public string ReCaptchaPublicKey { get; set; } public string ReCaptchaPrivateKey { get; set; } - } + public bool UseInvisibleReCaptcha { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Captcha/HtmlCaptchaExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/HtmlCaptchaExtensions.cs new file mode 100644 index 0000000000..5fc3cd68d4 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/HtmlCaptchaExtensions.cs @@ -0,0 +1,42 @@ +using System.Text; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Infrastructure; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Security +{ + public static class HtmlCaptchaExtensions + { + public static IHtmlString GenerateCaptcha(this HtmlHelper helper) + { + var captchaSettings = EngineContext.Current.Resolve(); + var workContext = EngineContext.Current.Resolve(); + var widgetUrl = CommonHelper.GetAppSetting("g:RecaptchaWidgetUrl"); + var ident = CommonHelper.GenerateRandomDigitCode(5).ToString(); + var elementId = "recaptcha" + ident; + var siteKey = captchaSettings.ReCaptchaPublicKey; + var callbackName = "recaptchaOnload" + ident; + + var url = "{0}?onload={1}&render=explicit&hl={2}".FormatInvariant( + widgetUrl, + callbackName, + workContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToLower() + ); + + var script = new[] + { + "", + "
    ".FormatInvariant(elementId, siteKey), + "".FormatInvariant(url), + }.StrJoin(""); + + return MvcHtmlString.Create(script); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Captcha/ValidateCaptchaAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/ValidateCaptchaAttribute.cs new file mode 100644 index 0000000000..48f56031a8 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/ValidateCaptchaAttribute.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Logging; +using SmartStore.Services.Localization; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Security +{ + public class ValidateCaptchaAttribute : ActionFilterAttribute + { + public ValidateCaptchaAttribute() + { + Logger = NullLogger.Instance; + } + + public Lazy CaptchaSettings { get; set; } + public ILogger Logger { get; set; } + public Lazy LocalizationService { get; set; } + + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + var valid = false; + + try + { + var captchaSettings = CaptchaSettings.Value; + if (captchaSettings.Enabled && captchaSettings.ReCaptchaPrivateKey.HasValue()) + { + var verifyUrl = CommonHelper.GetAppSetting("g:RecaptchaVerifyUrl"); + var recaptchaResponse = filterContext.HttpContext.Request.Form["g-recaptcha-response"]; + + var url = "{0}?secret={1}&response={2}".FormatInvariant( + verifyUrl, + HttpUtility.UrlEncode(captchaSettings.ReCaptchaPrivateKey), + HttpUtility.UrlEncode(recaptchaResponse) + ); + + using (var client = new WebClient()) + { + var jsonResponse = client.DownloadString(url); + using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonResponse))) + { + var serializer = new DataContractJsonSerializer(typeof(GoogleRecaptchaApiResponse)); + var result = serializer.ReadObject(memoryStream) as GoogleRecaptchaApiResponse; + + if (result == null) + { + Logger.Error(LocalizationService.Value.GetResource("Common.CaptchaUnableToVerify")); + } + else + { + if (result.ErrorCodes == null) + { + valid = result.Success; + } + } + } + } + } + } + catch (Exception exception) + { + Logger.ErrorsAll(exception); + } + + // this will push the result value into a parameter in our Action + filterContext.ActionParameters["captchaValid"] = valid; + + base.OnActionExecuting(filterContext); + } + } + + + [DataContract] + public class GoogleRecaptchaApiResponse + { + [DataMember(Name = "success")] + public bool Success { get; set; } + + [DataMember(Name = "error-codes")] + public List ErrorCodes { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs b/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs index 74e63cfc0b..35b5720dd3 100644 --- a/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs @@ -3,6 +3,7 @@ using System.Security.AccessControl; using System.Security.Principal; using SmartStore.Core; +using SmartStore.Core.Data; namespace SmartStore.Web.Framework.Security { @@ -155,14 +156,12 @@ public static IEnumerable GetDirectoriesWrite(IWebHelper webHelper) string rootDir = webHelper.MapPath("~/"); var dirsToCheck = new List(); dirsToCheck.Add(Path.Combine(rootDir, "App_Data")); - dirsToCheck.Add(Path.Combine(rootDir, "bin")); + dirsToCheck.Add(Path.Combine(rootDir, "App_Data", "Tenants", DataSettings.Current.TenantName)); + dirsToCheck.Add(Path.Combine(rootDir, "App_Data", "Tenants", DataSettings.Current.TenantName, "Media")); + dirsToCheck.Add(Path.Combine(rootDir, "bin")); dirsToCheck.Add(Path.Combine(rootDir, "content")); - dirsToCheck.Add(Path.Combine(rootDir, "content\\files\\exportimport")); dirsToCheck.Add(Path.Combine(rootDir, "Exchange")); - dirsToCheck.Add(Path.Combine(rootDir, "media")); - dirsToCheck.Add(Path.Combine(rootDir, "media\\thumbs")); - dirsToCheck.Add(Path.Combine(rootDir, "media\\uploaded")); - dirsToCheck.Add(Path.Combine(rootDir, "plugins")); + dirsToCheck.Add(Path.Combine(rootDir, "plugins")); dirsToCheck.Add(Path.Combine(rootDir, "plugins\\bin")); return dirsToCheck; } @@ -178,9 +177,10 @@ public static IEnumerable GetFilesWrite(IWebHelper webHelper) var filesToCheck = new List(); //filesToCheck.Add(Path.Combine(rootDir, "Global.asax")); //filesToCheck.Add(Path.Combine(rootDir, "web.config")); - filesToCheck.Add(Path.Combine(rootDir, "App_Data\\InstalledPlugins.txt")); - filesToCheck.Add(Path.Combine(rootDir, "App_Data\\Settings.txt")); - return filesToCheck; + filesToCheck.Add(Path.Combine(rootDir, "App_Data", "Tenants", DataSettings.Current.TenantName, "InstalledPlugins.txt")); + filesToCheck.Add(Path.Combine(rootDir, "App_Data", "Tenants", DataSettings.Current.TenantName, "Settings.txt")); + + return filesToCheck; } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/Honeypot.cs b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/Honeypot.cs new file mode 100644 index 0000000000..c9e58fe770 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/Honeypot.cs @@ -0,0 +1,76 @@ +using System; +using System.Text; +using System.Web; +using System.Web.Security; +using Newtonsoft.Json; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Security +{ + public class HoneypotField + { + public string Name { get; set; } + public DateTime CreatedOnUtc { get; set; } + } + + internal static class Honeypot + { + internal const string TokenFieldName = "__hpToken"; + + private static readonly string[] _fieldNames = new[] { "Phone", "Fax", "Email", "Age", "Name", "FirstName", "LastName", "Type", "Custom", "Reason", "Pet", "Question", "Region" }; + private static readonly string _fieldSuffix = CommonHelper.GenerateRandomDigitCode(5); + + public static HoneypotField CreateToken() + { + var r = new Random(); + + // Create a rondom field name with pattern "[random1]-[random2][suffix]" + var len = _fieldNames.Length; + var fieldName = string.Concat(_fieldNames[r.Next(0, len)], "-", _fieldNames[r.Next(0, len)], _fieldSuffix); + + return new HoneypotField + { + Name = fieldName, + CreatedOnUtc = DateTime.UtcNow + }; + } + + public static string SerializeToken(HoneypotField token) + { + Guard.NotNull(token, nameof(token)); + + var json = JsonConvert.SerializeObject(token); + var encoded = MachineKey.Protect(Encoding.UTF8.GetBytes(json)); + + var result = Convert.ToBase64String(encoded); + return result; + } + + public static HoneypotField DeserializeToken(string token) + { + Guard.NotEmpty(token, nameof(token)); + + var encoded = Convert.FromBase64String(token); + var decoded = MachineKey.Unprotect(encoded); + var json = Encoding.UTF8.GetString(decoded); + + var result = JsonConvert.DeserializeObject(json); + return result; + } + + public static bool IsBot(HttpContextBase httpContext) + { + var tokenString = httpContext.Request.Form[TokenFieldName]; + if (tokenString.IsEmpty()) + { + throw new InvalidOperationException("The required honeypot form field is missing. Please render the field with 'Html.HoneypotField()'."); + } + + var token = DeserializeToken(tokenString); + var trap = httpContext.Request.Form[token.Name]; + var isBot = trap == null || trap.Length > 0 || (DateTime.UtcNow - token.CreatedOnUtc).TotalMilliseconds < 2000; + + return isBot; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/HtmlHoneypotExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/HtmlHoneypotExtensions.cs new file mode 100644 index 0000000000..49593920a1 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/HtmlHoneypotExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Web.Mvc; +using System.Web.Mvc.Html; + +namespace SmartStore.Web.Framework.Security +{ + public static class HtmlHoneypotExtensions + { + public static MvcHtmlString HoneypotField(this HtmlHelper html) + { + var token = Honeypot.CreateToken(); + var serializedToken = Honeypot.SerializeToken(token); + + var textField = html.TextBox(token.Name, string.Empty, new { @class = "required-text-input", autocomplete = "off" }).ToHtmlString(); + var hiddenField = html.Hidden(Honeypot.TokenFieldName, serializedToken).ToHtmlString(); + + return MvcHtmlString.Create(textField + hiddenField); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/ValidateHoneypotAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/ValidateHoneypotAttribute.cs new file mode 100644 index 0000000000..3d026fc75f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/ValidateHoneypotAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; + +namespace SmartStore.Web.Framework.Security +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] + public class ValidateHoneypotAttribute : FilterAttribute, IAuthorizationFilter + { + public ValidateHoneypotAttribute() + { + Logger = NullLogger.Instance; + } + + public SecuritySettings SecuritySettings { get; set; } + public ILogger Logger { get; set; } + public Localizer T { get; set; } + public Lazy WebHelper { get; set; } + + public void OnAuthorization(AuthorizationContext filterContext) + { + if (!SecuritySettings.EnableHoneypotProtection) + return; + + var isBot = Honeypot.IsBot(filterContext.HttpContext); + if (!isBot) + return; + + Logger.Warn("Honeypot detected a bot and rejected the request."); + + var redirectUrl = WebHelper.Value.GetThisPageUrl(true); + filterContext.Result = new RedirectResult(redirectUrl); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs deleted file mode 100644 index 626a125317..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Security; -using SmartStore.Core.Infrastructure; - -namespace SmartStore.Web.Framework.Security -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] - public class RequireHttpsByConfigAttribute : FilterAttribute, IAuthorizationFilter - { - public RequireHttpsByConfigAttribute(SslRequirement sslRequirement) - { - this.SslRequirement = sslRequirement; - } - - public Lazy SecuritySettings { get; set; } - public Lazy StoreContext { get; set; } - public Lazy WebHelper { get; set; } - - public virtual void OnAuthorization(AuthorizationContext filterContext) - { - if (filterContext == null) - throw new ArgumentNullException("filterContext"); - - // don't apply filter to child methods - if (filterContext.IsChildAction) - return; - - // only redirect for GET requests, - // otherwise the browser might not propagate the verb and request body correctly. - if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) - return; - - if (!DataSettings.DatabaseIsInstalled()) - return; - - var currentConnectionSecured = filterContext.HttpContext.Request.IsSecureConnection(); - - var securitySettings = SecuritySettings.Value; - if (securitySettings.ForceSslForAllPages) - { - // all pages are forced to be SSL no matter of the specified value - this.SslRequirement = SslRequirement.Yes; - } - - switch (this.SslRequirement) - { - case SslRequirement.Yes: - { - if (!currentConnectionSecured) - { - var storeContext = StoreContext.Value; - var currentStore = storeContext.CurrentStore; - - if (currentStore != null && currentStore.GetSecurityMode() > HttpSecurityMode.Unsecured) - { - // redirect to HTTPS version of page - // string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; - var webHelper = WebHelper.Value; - string url = webHelper.GetThisPageUrl(true, true); - filterContext.Result = new RedirectResult(url, true); - } - } - } - break; - case SslRequirement.No: - { - if (currentConnectionSecured) - { - var webHelper = WebHelper.Value; - - // redirect to HTTP version of page - // string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl; - string url = webHelper.GetThisPageUrl(true, false); - filterContext.Result = new RedirectResult(url, true); - } - } - break; - case SslRequirement.Retain: - { - //do nothing - } - break; - default: - throw new SmartException("Unsupported SslRequirement parameter"); - } - } - - public SslRequirement SslRequirement { get; set; } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/RewriteUrlAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/RewriteUrlAttribute.cs new file mode 100644 index 0000000000..f63ad4b5ba --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/RewriteUrlAttribute.cs @@ -0,0 +1,137 @@ +using System; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Seo; + +namespace SmartStore.Web.Framework.Security +{ + public class RewriteUrlAttribute : FilterAttribute, IAuthorizationFilter + { + public RewriteUrlAttribute(SslRequirement sslRequirement) + { + this.SslRequirement = sslRequirement; + this.Order = 100; + } + + public Lazy SeoSettings { get; set; } + public Lazy SecuritySettings { get; set; } + public Lazy StoreContext { get; set; } + public Lazy WebHelper { get; set; } + + public virtual void OnAuthorization(AuthorizationContext filterContext) + { + // don't apply filter to child methods + if (filterContext.IsChildAction) + return; + + // only redirect for GET requests, + // otherwise the browser might not propagate the verb and request body correctly. + if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) + return; + + if (!DataSettings.DatabaseIsInstalled()) + return; + + var securitySettings = SecuritySettings.Value; + var isLocalRequest = filterContext.HttpContext.Request.IsLocal; + + if (!securitySettings.UseSslOnLocalhost && isLocalRequest) + return; + + var webHelper = WebHelper.Value; + + var currentConnectionSecured = webHelper.IsCurrentConnectionSecured(); + var currentStore = StoreContext.Value.CurrentStore; + + if (currentStore.ForceSslForAllPages) + { + // all pages are forced to be SSL no matter of the specified value + this.SslRequirement = SslRequirement.Yes; + } + + var securityMode = currentStore.GetSecurityMode(); + + switch (this.SslRequirement) + { + case SslRequirement.Yes: + { + if (!currentConnectionSecured && securityMode > HttpSecurityMode.Unsecured) + { + // Redirect to HTTPS version of page + string url = webHelper.GetThisPageUrl(true, true); + filterContext.Result = new RedirectResult(url, !isLocalRequest); + } + } + break; + case SslRequirement.No: + { + if (currentConnectionSecured) + { + // Redirect to HTTP version of page + string url = webHelper.GetThisPageUrl(true, false); + filterContext.Result = new RedirectResult(url, !isLocalRequest); + } + } + break; + case SslRequirement.Retain: + { + // Do nothing + } + break; + default: + throw new SmartException("Unsupported SslRequirement parameter"); + } + + ApplyCanonicalHostNameRule(filterContext, isLocalRequest, securityMode); + } + + private void ApplyCanonicalHostNameRule(AuthorizationContext filterContext, bool isLocalRequest, HttpSecurityMode securityMode) + { + var rule = SeoSettings.Value.CanonicalHostNameRule; + if (rule == CanonicalHostNameRule.NoRule) + return; + + var uri = filterContext.Result != null + ? new Uri(((RedirectResult)filterContext.Result).Url) + : filterContext.HttpContext.Request.Url; + + if (uri.IsLoopback) + { + // allows testing of "localtest.me" + return; + } + + var url = uri.ToString(); + var protocol = uri.Scheme.ToLower(); + var isHttps = protocol.IsCaseInsensitiveEqual("https"); + var startsWith = "{0}://www.".FormatInvariant(protocol); + var hasPrefix = url.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase); + + if (isHttps) + { + if (securityMode == HttpSecurityMode.SharedSsl) + { + // Don't attempt to redirect when shared SSL is being used and current request is secured. + // Redirecting from http://ssl.bla.com to https://www.ssl.bla.com will most probably fail. + return; + } + } + + if (hasPrefix && rule == CanonicalHostNameRule.OmitWww) + { + url = url.Replace("://www.", "://"); + filterContext.Result = new RedirectResult(url, !isLocalRequest); + } + + if (!hasPrefix && rule == CanonicalHostNameRule.RequireWww) + { + url = url.Replace("{0}://".FormatInvariant(protocol), startsWith); + filterContext.Result = new RedirectResult(url, !isLocalRequest); + } + } + + public SslRequirement SslRequirement { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/SanitizeHtmlAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/SanitizeHtmlAttribute.cs new file mode 100644 index 0000000000..ddfb9cec9f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/SanitizeHtmlAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.Security +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class SanitizeHtmlAttribute : Attribute, IMetadataAware + { + public SanitizeHtmlAttribute() + { + IsFragment = true; + } + + public bool IsFragment + { + get; + set; + } + + public void OnMetadataCreated(ModelMetadata metadata) + { + Guard.NotNull(metadata, nameof(metadata)); + + metadata.RequestValidationEnabled = false; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs deleted file mode 100644 index f683d550e1..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Seo/CanonicalHostNameAttribute.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Infrastructure; - -namespace SmartStore.Web.Framework.Seo -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] - public class CanonicalHostNameAttribute : FilterAttribute, IAuthorizationFilter - { - public CanonicalHostNameAttribute() - { - } - - public Lazy SeoSettings { get; set; } - public Lazy WebHelper { get; set; } - public Lazy StoreContext { get; set; } - - public virtual void OnAuthorization(AuthorizationContext filterContext) - { - if (filterContext == null) - throw new ArgumentNullException("filterContext"); - - if (filterContext.IsChildAction) - return; - - // only redirect for GET requests - if (!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) - return; - - if (!DataSettings.DatabaseIsInstalled()) - return; - - var rule = SeoSettings.Value.CanonicalHostNameRule; - if (rule == CanonicalHostNameRule.NoRule) - return; - - var uri = filterContext.HttpContext.Request.Url; - - if (uri.IsLoopback) - { - // allows testing of "localtest.me" - return; - } - - var url = uri.ToString(); - var protocol = uri.Scheme.ToLower(); - var isHttps = protocol.IsCaseInsensitiveEqual("https"); - var startsWith = "{0}://www.".FormatInvariant(protocol); - var hasPrefix = url.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase); - - if (isHttps) - { - var securityMode = StoreContext.Value.CurrentStore.GetSecurityMode(); - if (securityMode == HttpSecurityMode.SharedSsl) - { - // Don't attempt to redirect when shared SSL is being used and current request is secured. - // Redirecting from http://ssl.bla.com to https://www.ssl.bla.com will most probably fail. - return; - } - } - - if (hasPrefix && rule == CanonicalHostNameRule.OmitWww) - { - url = url.Replace("://www.", "://"); - filterContext.Result = new RedirectResult(url, true); - } - - if (!hasPrefix && rule == CanonicalHostNameRule.RequireWww) - { - url = url.Replace("{0}://".FormatInvariant(protocol), startsWith); - filterContext.Result = new RedirectResult(url, true); - } - - } - - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs index 6ea897dfa0..ccf42a7bfd 100644 --- a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs @@ -1,5 +1,9 @@ -using System.Web; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; using System.Web.Routing; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; @@ -13,9 +17,10 @@ namespace SmartStore.Web.Framework.Seo ///
    public class GenericPathRoute : LocalizedRoute { - #region Constructors - - /// + // Key = Prefix, Value = EntityType + private static readonly Multimap _urlPrefixes = new Multimap(StringComparer.OrdinalIgnoreCase); + + /// /// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern and handler class. /// /// The URL pattern for the route. @@ -62,9 +67,22 @@ public GenericPathRoute(string url, RouteValueDictionary defaults, RouteValueDic { } - #endregion + public static void RegisterUrlPrefix(string prefix, params string[] entityNames) + { + Guard.NotEmpty(prefix, nameof(prefix)); + + _urlPrefixes.AddRange(prefix, entityNames); + } + + public static string GetUrlPrefixFor(string entityName) + { + Guard.NotEmpty(entityName, nameof(entityName)); - #region Methods + if (_urlPrefixes.Count == 0) + return null; + + return _urlPrefixes.FirstOrDefault(x => x.Value.Contains(entityName, StringComparer.OrdinalIgnoreCase)).Key; + } /// /// Returns information about the requested route. @@ -76,78 +94,104 @@ public GenericPathRoute(string url, RouteValueDictionary defaults, RouteValueDic public override RouteData GetRouteData(HttpContextBase httpContext) { RouteData data = base.GetRouteData(httpContext); + if (data != null && DataSettings.DatabaseIsInstalled()) { - var urlRecordService = EngineContext.Current.Resolve(); var slug = data.Values["generic_se_name"] as string; - var urlRecord = urlRecordService.GetBySlug(slug); + + if (TryResolveUrlPrefix(slug, out var urlPrefix, out var actualSlug, out var entityNames)) + { + slug = actualSlug; + } + + var urlRecordService = EngineContext.Current.Resolve(); + var urlRecord = urlRecordService.GetBySlug(slug); if (urlRecord == null) { - //no URL record found - data.Values["controller"] = "Error"; - data.Values["action"] = "NotFound"; - return data; + // no URL record found + return NotFound(data); } + if (!urlRecord.IsActive) { // URL record is not active. let's find the latest one var activeSlug = urlRecordService.GetActiveSlug(urlRecord.EntityId, urlRecord.EntityName, urlRecord.LanguageId); - if (!string.IsNullOrWhiteSpace(activeSlug)) + if (activeSlug.HasValue()) { - // the active one is found + // The active one is found var webHelper = EngineContext.Current.Resolve(); var response = httpContext.Response; response.Status = "301 Moved Permanently"; + if (urlPrefix.HasValue()) + { + activeSlug = urlPrefix + "/" + activeSlug; + } response.RedirectLocation = string.Format("{0}{1}", webHelper.GetStoreLocation(false), activeSlug); response.End(); return null; } else { - // no active slug found - data.Values["controller"] = "Error"; - data.Values["action"] = "NotFound"; - return data; - } + // no active slug found + return NotFound(data); + } } - // process URL - data.Values["SeName"] = urlRecord.Slug; + // Verify prefix matches any assigned entity name + if (entityNames != null && !entityNames.Contains(urlRecord.EntityName, StringComparer.OrdinalIgnoreCase)) + { + // does NOT match + return NotFound(data); + } + + // process URL + data.DataTokens["UrlRecord"] = urlRecord; + data.Values["SeName"] = slug; + + string controller, action, paramName; + switch (urlRecord.EntityName.ToLowerInvariant()) { case "product": { - data.Values["controller"] = "Product"; - data.Values["action"] = "ProductDetails"; - data.Values["productid"] = urlRecord.EntityId; + controller = "Product"; + action = "ProductDetails"; + paramName = "productid"; } break; case "category": { - data.Values["controller"] = "Catalog"; - data.Values["action"] = "Category"; - data.Values["categoryid"] = urlRecord.EntityId; + controller = "Catalog"; + action = "Category"; + paramName = "categoryid"; } break; case "manufacturer": { - data.Values["controller"] = "Catalog"; - data.Values["action"] = "Manufacturer"; - data.Values["manufacturerid"] = urlRecord.EntityId; + controller = "Catalog"; + action = "Manufacturer"; + paramName = "manufacturerid"; } break; - case "newsitem": + case "topic": + { + controller = "Topic"; + action = "TopicDetails"; + paramName = "topicId"; + } + break; + case "newsitem": { - data.Values["controller"] = "News"; - data.Values["action"] = "NewsItem"; - data.Values["newsItemId"] = urlRecord.EntityId; + controller = "News"; + action = "NewsItem"; + paramName = "newsItemId"; } break; case "blogpost": { - data.Values["controller"] = "Blog"; - data.Values["action"] = "BlogPost"; - data.Values["blogPostId"] = urlRecord.EntityId; + controller = "Blog"; + action = "BlogPost"; + paramName = "blogPostId"; } break; default: @@ -155,10 +199,46 @@ public override RouteData GetRouteData(HttpContextBase httpContext) throw new SmartException(string.Format("Unsupported EntityName for UrlRecord: {0}", urlRecord.EntityName)); } } - } + + data.Values["controller"] = controller; + data.Values["action"] = action; + data.Values[paramName] = urlRecord.EntityId; + } + return data; } - #endregion + private RouteData NotFound(RouteData data) + { + data.Values["controller"] = "Error"; + data.Values["action"] = "NotFound"; + + return data; + } + + private bool TryResolveUrlPrefix(string slug, out string urlPrefix, out string actualSlug, out ICollection entityNames) + { + urlPrefix = null; + actualSlug = null; + entityNames = null; + + if (_urlPrefixes.Count > 0) + { + var firstSepIndex = slug.IndexOf('/'); + if (firstSepIndex > 0) + { + var prefix = slug.Substring(0, firstSepIndex); + if (_urlPrefixes.ContainsKey(prefix)) + { + urlPrefix = prefix; + entityNames = _urlPrefixes[prefix]; + actualSlug = slug.Substring(prefix.Length + 1); + return true; + } + } + } + + return false; + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs index 9029891c5f..cf931f15bc 100644 --- a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs @@ -6,27 +6,31 @@ namespace SmartStore.Web.Framework.Seo { public static class GenericPathRouteExtensions { - //Override for generic route public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url) { return MapGenericPathRoute(routes, name, url, null /* defaults */, (object)null /* constraints */); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults) { return MapGenericPathRoute(routes, name, url, defaults, (object)null /* constraints */); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints) { return MapGenericPathRoute(routes, name, url, defaults, constraints, null /* namespaces */); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, string[] namespaces) { return MapGenericPathRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces) { return MapGenericPathRoute(routes, name, url, defaults, null /* constraints */, namespaces); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) { if (routes == null) @@ -54,5 +58,30 @@ public static Route MapGenericPathRoute(this RouteCollection routes, string name return route; } + + /// + /// Changes the url pattern of an existing named route. + /// + /// Route collection + /// Name of the route + /// The new url pattern + /// The route instance + public static Route ChangeRouteUrl(this RouteCollection routes, string name, string url) + { + Guard.NotNull(routes, nameof(routes)); + Guard.NotEmpty(name, nameof(name)); + Guard.NotEmpty(url, nameof(url)); + + var route = routes[name] as Route; + + if (route == null) + { + throw new ArgumentException("The route '{0}' does not exist or is not assignable from 'Route'.".FormatInvariant(name), nameof(name)); + } + + route.Url = url; + + return route; + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/LoadSettingAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Settings/LoadSettingAttribute.cs new file mode 100644 index 0000000000..df6e25159f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Settings/LoadSettingAttribute.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using SmartStore.Core.Configuration; +using SmartStore.Services; +using SmartStore.Web.Framework.Controllers; + +namespace SmartStore.Web.Framework.Settings +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + public class LoadSettingAttribute : FilterAttribute, IActionFilter + { + public sealed class SettingParam + { + public ISettings Instance { get; set; } + public ParameterDescriptor Parameter { get; set; } + } + + protected int _storeId; + protected SettingParam[] _settingParams; + + public LoadSettingAttribute() + : this(true) + { + } + + public LoadSettingAttribute(bool updateParameterFromStore) + { + UpdateParameterFromStore = updateParameterFromStore; + } + + public bool UpdateParameterFromStore { get; set; } + public ICommonServices Services { get; set; } + + public virtual void OnActionExecuting(ActionExecutingContext filterContext) + { + // Get the current configured store id + _storeId = filterContext.Controller.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + Func predicate = (x) => new[] { "storescope", "storeid" }.Contains(x.ParameterName, StringComparer.OrdinalIgnoreCase); + var storeScopeParam = FindActionParameters(filterContext.ActionDescriptor, false, false, predicate).FirstOrDefault(); + if (storeScopeParam != null) + { + // We found an action param named storeScope with type int. Assign our storeId to it. + filterContext.ActionParameters[storeScopeParam.ParameterName] = _storeId; + } + + // Find the required ISettings concrete types in ActionDescriptor.GetParameters() + _settingParams = FindActionParameters(filterContext.ActionDescriptor) + .Select(x => + { + // Load settings for the settings type obtained with FindActionParameters() + var settings = UpdateParameterFromStore + ? Services.Settings.LoadSetting(x.ParameterType, _storeId) + : filterContext.ActionParameters[x.ParameterName] as ISettings; + + if (settings == null) + { + throw new InvalidOperationException($"Could not load settings for type '{x.ParameterType.FullName}'."); + } + + // Replace settings from action parameters with our loaded settings + if (UpdateParameterFromStore) + { + filterContext.ActionParameters[x.ParameterName] = settings; + } + + return new SettingParam + { + Instance = settings, + Parameter = x + }; + }) + .ToArray(); + } + + public virtual void OnActionExecuted(ActionExecutedContext filterContext) + { + var viewResult = filterContext.Result as ViewResultBase; + + if (viewResult != null) + { + var model = viewResult.Model; + + if (model == null) + { + // Nothing to override. E.g. insufficient permission. + return; + } + + var settingsHelper = new StoreDependingSettingHelper(filterContext.Controller.ViewData); + + foreach (var param in _settingParams) + { + settingsHelper.GetOverrideKeys(param.Instance, model, _storeId, Services.Settings); + } + } + } + + protected IEnumerable FindActionParameters( + ActionDescriptor actionDescriptor, + bool requireDefaultConstructor = true, + bool throwIfNotFound = true, + Func predicate = null) + { + Guard.NotNull(actionDescriptor, nameof(actionDescriptor)); + + var t = typeof(T); + + var query = actionDescriptor + .GetParameters() + .Where(x => t.IsAssignableFrom(x.ParameterType)); + + if (predicate != null) + { + query = query.Where(predicate); + } + + if (throwIfNotFound && !query.Any()) + { + throw new InvalidOperationException( + $"A controller action method with a '{this.GetType().Name}' attribute requires an action parameter of type '{t.Name}' in order to execute properly."); + } + + if (requireDefaultConstructor) + { + foreach (var param in query) + { + if (!param.ParameterType.HasDefaultConstructor()) + { + throw new InvalidOperationException($"The parameter '{param.ParameterName}' must have a default parameterless constructor."); + } + } + } + + return query; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs new file mode 100644 index 0000000000..278679dbde --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq; +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.Settings +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + public sealed class SaveSettingAttribute : LoadSettingAttribute + { + private FormCollection _form; + private IDisposable _settingsWriteBatch; + + public SaveSettingAttribute() + : base(true) + { + } + + public SaveSettingAttribute(bool updateParameterFromStore) + : base(updateParameterFromStore) + { + } + + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + base.OnActionExecuting(filterContext); + + if (!filterContext.Controller.ViewData.ModelState.IsValid) + { + return; + } + + // Find the required FormCollection parameter in ActionDescriptor.GetParameters() + var formParam = FindActionParameters(filterContext.ActionDescriptor, requireDefaultConstructor: false, throwIfNotFound: false).FirstOrDefault(); + _form = formParam != null + ? (FormCollection)filterContext.ActionParameters[formParam.ParameterName] + : BindFormCollection(filterContext.Controller.ControllerContext); + + + _settingsWriteBatch = Services.Settings.BeginScope(); + } + + public override void OnActionExecuted(ActionExecutedContext filterContext) + { + if (filterContext.Controller.ViewData.ModelState.IsValid) + { + var updateSettings = true; + var redirectResult = filterContext.Result as RedirectToRouteResult; + if (redirectResult != null) + { + var controllerName = redirectResult.RouteValues["controller"] as string; + var areaName = redirectResult.RouteValues["area"] as string; + if (controllerName.IsCaseInsensitiveEqual("security") && areaName.IsCaseInsensitiveEqual("admin")) + { + // Insufficient permission. We must not save because the action did not run. + updateSettings = false; + } + } + + if (updateSettings) + { + var settingHelper = new StoreDependingSettingHelper(filterContext.Controller.ViewData); + + foreach (var param in _settingParams) + { + settingHelper.UpdateSettings(param.Instance, _form, _storeId, Services.Settings); + } + } + } + + if (_settingsWriteBatch != null) + { + _settingsWriteBatch.Dispose(); + _settingsWriteBatch = null; + } + + base.OnActionExecuted(filterContext); + } + + private FormCollection BindFormCollection(ControllerContext controllerContext) + { + var bindingContext = new ModelBindingContext + { + ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(FormCollection)), + ModelState = controllerContext.Controller.ViewData.ModelState, + ValueProvider = controllerContext.Controller.ValueProvider + }; + + var modelBinder = ModelBinders.Binders.GetBinder(typeof(FormCollection)); + + return (FormCollection)modelBinder.BindModel(controllerContext, bindingContext); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs index 4e066cd201..7605dd7934 100644 --- a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs @@ -18,7 +18,7 @@ public StoreDependingSettingHelper(ViewDataDictionary viewData) _viewData = viewData; } - public static string ViewDataKey { get { return "StoreDependingSettingData"; } } + public static string ViewDataKey => "StoreDependingSettingData"; public StoreDependingSettingData Data { @@ -48,28 +48,40 @@ public bool IsOverrideChecked(object settings, string name, FormCollection form) public void AddOverrideKey(object settings, string name) { + if (Data == null) + { + throw new SmartException("You must call GetOverrideKeys or CreateViewDataObject before AddOverrideKey."); + } + var key = settings.GetType().Name + "." + name; Data.OverrideSettingKeys.Add(key); } public void CreateViewDataObject(int activeStoreScopeConfiguration, string rootSettingClass = null) { - _viewData[ViewDataKey] = new StoreDependingSettingData() + _viewData[ViewDataKey] = new StoreDependingSettingData { ActiveStoreScopeConfiguration = activeStoreScopeConfiguration, RootSettingClass = rootSettingClass }; } - public void GetOverrideKeys(object settings, object model, int storeId, ISettingService settingService, bool isRootModel = true, ILocalizedModelLocal localized = null, int? index = null) + public void GetOverrideKeys( + object settings, + object model, + int storeId, + ISettingService settingService, + bool isRootModel = true, + ILocalizedModelLocal localized = null, + int? index = null) { - if (storeId <= 0) - return; // single store mode -> there are no overrides - - var data = Data; - if (data == null) - data = new StoreDependingSettingData(); + if (storeId <= 0) + { + // Single store mode -> there are no overrides. + return; + } + var data = Data ?? new StoreDependingSettingData(); var settingName = settings.GetType().Name; var properties = settings.GetType().GetProperties(); var localizedEntityService = EngineContext.Current.Resolve(); @@ -81,26 +93,37 @@ public void GetOverrideKeys(object settings, object model, int storeId, ISetting var name = prop.Name; var modelProperty = modelType.GetProperty(name); - if (modelProperty == null) - continue; // setting is not configurable or missing or whatever... however we don't need the override info - - var key = String.Empty; - var setting = String.Empty; - - if (localized == null) - { - key = settingName + "." + name; - setting = settingService.GetSettingByKey(key, storeId: storeId); - } - else - { - key = "Locales[" + index.ToString() + "]." + name; - setting = localizedEntityService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); - } - - if (!String.IsNullOrEmpty(setting)) - data.OverrideSettingKeys.Add(key); - } + if (modelProperty == null) + { + // Setting is not configurable or missing or whatever... however we don't need the override info. + continue; + } + + string key = null; + + if (localized == null) + { + key = settingName + "." + name; + + if (settingService.GetSettingByKey(key, storeId: storeId) == null) + { + key = null; + } + } + else + { + var value = localizedEntityService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); + if (!string.IsNullOrEmpty(value)) + { + key = string.Concat("Locales[", index.ToString(), "].", name); + } + } + + if (key != null) + { + data.OverrideSettingKeys.Add(key); + } + } if (isRootModel) { @@ -111,29 +134,116 @@ public void GetOverrideKeys(object settings, object model, int storeId, ISetting } } - public void UpdateSettings(object settings, FormCollection form, int storeId, ISettingService settingService, ILocalizedModelLocal localized = null) + public void GetOverrideKey( + string formKey, + string settingName, + object settings, + int storeId, + ISettingService settingService, + ILocalizedModelLocal localized = null, + int? index = null) + { + if (storeId <= 0) + { + // Single store mode -> there are no overrides. + return; + } + + var key = formKey; + + if (localized == null) + { + if (settingService.GetSettingByKey(string.Concat(settings.GetType().Name, ".", settingName), storeId: storeId) == null) + { + key = null; + } + } + else + { + // TODO if required + throw new ArgumentException("Localized override key not supported yet."); + } + + if (key != null) + { + var data = Data ?? new StoreDependingSettingData(); + data.OverrideSettingKeys.Add(key); + } + } + + /// + /// Updates settings for a store. + /// + /// Settings class instance. + /// Form value collection. + /// Store identifier. + /// Setting service. + /// Localized model. + /// Function to map property names. Return null to skip a property. + public void UpdateSettings( + object settings, + FormCollection form, + int storeId, + ISettingService settingService, + ILocalizedModelLocal localized = null, + Func propertyNameMapper = null) { var settingName = settings.GetType().Name; var properties = FastProperty.GetProperties(localized == null ? settings.GetType() : localized.GetType()).Values; - using (settingService.BeginBatch()) + foreach (var prop in properties) { - foreach (var prop in properties) + var name = prop.Name; + + if (propertyNameMapper != null) { - var name = prop.Name; - var key = settingName + "." + name; + name = propertyNameMapper(name); + } - if (storeId == 0 || IsOverrideChecked(key, form)) - { - dynamic value = prop.GetValue(localized == null ? settings : localized); - settingService.SetSetting(key, value == null ? "" : value, storeId, false); - } - else if (storeId > 0) - { - settingService.DeleteSetting(key, storeId); - } + if (string.IsNullOrWhiteSpace(name)) + { + continue; + } + + var key = string.Concat(settingName, ".", name); + + if (storeId == 0 || IsOverrideChecked(key, form)) + { + dynamic value = prop.GetValue(localized == null ? settings : localized); + settingService.SetSetting(key, value == null ? "" : value, storeId, false); + } + else if (storeId > 0) + { + settingService.DeleteSetting(key, storeId); } } } + + public void UpdateSetting( + string formKey, + string settingName, + object settings, + FormCollection form, + int storeId, + ISettingService settingService, + ILocalizedModelLocal localized = null) + { + if (storeId == 0 || IsOverrideChecked(formKey, form)) + { + var prop = FastProperty.GetProperty(localized == null ? settings.GetType() : localized.GetType(), settingName); + if (prop != null) + { + dynamic value = prop.GetValue(localized == null ? settings : localized); + var key = string.Concat(settings.GetType().Name, ".", settingName); + + settingService.SetSetting(key, value == null ? "" : value, storeId, false); + } + } + else if (storeId > 0) + { + var key = string.Concat(settings.GetType().Name, ".", settingName); + settingService.DeleteSetting(key, storeId); + } + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index b37e9baf66..bcf02806b0 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -10,7 +11,7 @@ Properties SmartStore.Web.Framework SmartStore.Web.Framework - v4.5.1 + v4.6.1 512 @@ -23,6 +24,8 @@ ..\..\ true + + true @@ -62,69 +65,73 @@ MinimumRecommendedRules.ruleset + + ..\..\packages\AdvancedStringBuilder.0.1.0\lib\net45\AdvancedStringBuilder.dll + False ..\..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll - - ..\..\packages\Autofac.3.5.2\lib\net40\Autofac.dll - True + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll - - ..\..\packages\Autofac.Mvc5.3.3.4\lib\net45\Autofac.Integration.Mvc.dll + + ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll True - - False - ..\..\packages\Autofac.WebApi.3.1.0\lib\net40\Autofac.Integration.WebApi.dll + + ..\..\packages\Autofac.WebApi2.4.2.0\lib\net45\Autofac.Integration.WebApi.dll - - ..\..\packages\AutoMapper.4.1.1\lib\net45\AutoMapper.dll - True + + ..\..\packages\AutoMapper.8.0.0\lib\net461\AutoMapper.dll - - False - ..\..\packages\BundleTransformer.Core.1.8.23\lib\net40\BundleTransformer.Core.dll + + ..\..\packages\BundleTransformer.Autoprefixer.1.10.2\lib\net40\BundleTransformer.Autoprefixer.dll - - False - ..\..\packages\BundleTransformer.Less.1.8.25\lib\net40\BundleTransformer.Less.dll + + ..\..\packages\BundleTransformer.Core.1.10.0\lib\net40\BundleTransformer.Core.dll - - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + + ..\..\packages\BundleTransformer.SassAndScss.1.10.0\lib\net40\BundleTransformer.SassAndScss.dll - - False - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + + ..\..\packages\CommonServiceLocator.2.0.4\lib\net46\CommonServiceLocator.dll - - ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll + + ..\..\packages\DotLiquid.2.0.254\lib\net45\DotLiquid.dll + + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll True - - ..\..\packages\JavaScriptEngineSwitcher.Core.1.2.4\lib\net40\JavaScriptEngineSwitcher.Core.dll + + ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll True - - False - ..\..\packages\Microsoft.Data.Edm.5.6.1\lib\net40\Microsoft.Data.Edm.dll + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll - - False - ..\..\packages\Microsoft.Data.OData.5.6.1\lib\net40\Microsoft.Data.OData.dll + + ..\..\packages\JavaScriptEngineSwitcher.Core.3.0.0\lib\net45\JavaScriptEngineSwitcher.Core.dll - - False - ..\..\packages\CommonServiceLocator.1.2\lib\portable-windows8+net40+sl5+windowsphone8\Microsoft.Practices.ServiceLocation.dll + + ..\..\packages\LibSassHost.1.2.2\lib\net45\LibSassHost.dll + + + ..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll + + + ..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll + + + ..\..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll @@ -132,66 +139,60 @@ - - False - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.1.2\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + ..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll + - - False - ..\..\packages\System.Spatial.5.6.1\lib\net40\System.Spatial.dll + + ..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll + + + ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - - False - ..\..\packages\Microsoft.AspNet.Cors.5.1.2\lib\net45\System.Web.Cors.dll + + ..\..\packages\Microsoft.AspNet.Cors.5.2.7\lib\net45\System.Web.Cors.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.WebApi.Core.5.1.2\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - False - ..\..\packages\Microsoft.AspNet.WebApi.Cors.5.1.1\lib\net45\System.Web.Http.Cors.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Cors.5.2.7\lib\net45\System.Web.Http.Cors.dll - - False - ..\..\packages\Microsoft.AspNet.WebApi.OData.5.1.2\lib\net45\System.Web.Http.OData.dll + + ..\..\packages\Microsoft.AspNet.WebApi.OData.5.7.0\lib\net45\System.Web.Http.OData.dll + True - - False - ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.1.2\lib\net45\System.Web.Http.WebHost.dll + + ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.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.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.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 @@ -214,10 +215,43 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -238,7 +272,6 @@ - @@ -253,52 +286,76 @@ - + + + + + + + - + + + + + + + + + + + + + + + + + + + - + - - + - - + - - + - + + + + - + @@ -314,10 +371,8 @@ - - @@ -326,51 +381,68 @@ + + + + + + + + + + + + + + + + + + + - + - - - + - - - + + + - + - + - + - + - + @@ -380,8 +452,7 @@ - - + diff --git a/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs b/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs index 2737fb5782..67097db01a 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs +++ b/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs @@ -1,36 +1,117 @@ using System; using System.Collections.Generic; using System.Collections.Concurrent; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Web; -using System.Web.Mvc; using System.Web.Routing; using System.Text.RegularExpressions; +using SmartStore.Utilities; +using SmartStore.Core; +using SmartStore.Core.IO; +using System.IO; +using System.Web.Hosting; +using System.Reflection; +using SmartStore.Web.Framework.Theming; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Events; +using SmartStore.Core.Data; +using SmartStore.Collections; namespace SmartStore.Web.Framework { + /// + /// Request event sequence: + /// - BeginRequest + /// - AuthenticateRequest + /// - PostAuthenticateRequest + /// - AuthorizeRequest + /// - PostAuthorizeRequest + /// - ResolveRequestCache + /// - PostResolveRequestCache + /// - MapRequestHandler + /// - PostMapRequestHandler + /// - AcquireRequestState + /// - PostAcquireRequestState + /// - PreRequestHandlerExecute + /// - PostRequestHandlerExecute + /// - ReleaseRequestState + /// - PostReleaseRequestState + /// - UpdateRequestCache + /// - PostUpdateRequestCache + /// - LogRequest + /// - PostLogRequest + /// - EndRequest + /// - PreSendRequestHeaders + /// - PreSendRequestContent + /// public class SmartUrlRoutingModule : IHttpModule { private static readonly object _contextKey = new object(); - private static readonly ConcurrentBag _routes = new ConcurrentBag(); + + private static readonly ICollection> _actions = + new SyncedCollection>(new List>()) { ReadLockFree = true }; + + private static readonly ICollection _routes = + new SyncedCollection(new List()) { ReadLockFree = true }; + + static SmartUrlRoutingModule() + { + StopSubDirMonitoring(); + } + + private static void StopSubDirMonitoring() + { + try + { + // http://stackoverflow.com/questions/2248825/asp-net-restarts-when-a-folder-is-created-renamed-or-deleted + var prop = typeof(HttpRuntime).GetProperty("FileChangesMonitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + var o = prop.GetValue(null, null); + + var fi = o.GetType().GetField("_dirMonSubdirs", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase); + var monitor = fi.GetValue(o); + var mi = monitor.GetType().GetMethod("StopMonitoring", BindingFlags.Instance | BindingFlags.NonPublic); + mi.Invoke(monitor, new object[] { }); + } + catch { } + } public void Init(HttpApplication application) { + if (!DataSettings.DatabaseIsInstalled()) + return; + if (application.Context.Items[_contextKey] == null) { application.Context.Items[_contextKey] = _contextKey; - application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; + + if (CommonHelper.IsDevEnvironment && HttpContext.Current.IsDebuggingEnabled) + { + // Handle plugin static file in DevMode + application.PostAuthorizeRequest += (s, e) => PostAuthorizeRequest(new HttpContextWrapper(((HttpApplication)s).Context)); + application.PreSendRequestHeaders += (s, e) => PreSendRequestHeaders(new HttpContextWrapper(((HttpApplication)s).Context)); + } + + application.PostResolveRequestCache += (s, e) => PostResolveRequestCache(new HttpContextWrapper(((HttpApplication)s).Context)); + + // Publish event to give plugins the chance to register custom event handlers for the request lifecycle. + foreach (var action in _actions) + { + action(application); + } + + // Set app to fully initialized state on very first request + EngineContext.Current.IsFullyInitialized = true; } } - private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) + /// + /// Registers an action that is called on application init. Call this to register HTTP request lifecycle callbacks / event handlers. + /// + /// Action + public static void RegisterAction(Action action) { - var application = (HttpApplication)sender; - var context = new HttpContextWrapper(application.Context); - this.PostResolveRequestCache(context); + Guard.NotNull(action, nameof(action)); + + _actions.Add(action); } /// @@ -41,13 +122,13 @@ private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) /// /// For performance reasons SmartStore.NET is configured to serve static files (files with extensions other than those mapped to managed handlers) /// through the native StaticFileModule. This method lets you define exceptions to this default rule: every path registered as routable gets - /// handled by the and therefore enables dynamic processing of (physically non-existant) static files. + /// handled by the and therefore enables dynamic processing of (physically non-existent) static files. /// public static void RegisterRoutablePath(string path, string verb = ".*") { - Guard.ArgumentNotEmpty(() => path); + Guard.NotEmpty(path, nameof(path)); - if (RegularExpressions.IsWebUrl.IsMatch(path)) + if (path.IsWebUrl()) { throw new ArgumentException("Only relative paths are allowed.", "path"); } @@ -60,8 +141,8 @@ public static void RegisterRoutablePath(string path, string verb = ".*") public static bool HasMatchingPathHandler(string path, string method = "GET") { - Guard.ArgumentNotEmpty(() => path); - Guard.ArgumentNotEmpty(() => method); + Guard.NotEmpty(path, nameof(path)); + Guard.NotEmpty(method, nameof(method)); if (_routes.Count > 0) { @@ -77,24 +158,68 @@ public static bool HasMatchingPathHandler(string path, string method = "GET") return false; } - public virtual void PostResolveRequestCache(HttpContextBase context) + public virtual void PostAuthorizeRequest(HttpContextBase context) { - var request = context.Request; - if (request == null || _routes.Count == 0) + var request = context?.Request; + if (request == null) return; - var path = request.AppRelativeCurrentExecutionFilePath.TrimStart('~').TrimEnd('/'); - var method = request.HttpMethod.EmptyNull(); + if (IsExtensionPath(request) && WebHelper.IsStaticResourceRequested(request)) + { + // We're in debug mode and in dev environment + var file = HostingEnvironment.VirtualPathProvider.GetFile(request.AppRelativeCurrentExecutionFilePath) as DebugVirtualFile; + if (file != null) + { + context.Items["DebugFile"] = file; + context.Response.WriteFile(file.PhysicalPath); + context.Response.End(); + } + } + } + + public virtual void PreSendRequestHeaders(HttpContextBase context) + { + if (context?.Response == null) + return; + + var file = context.Items?["DebugFile"] as DebugVirtualFile; + if (file != null) + { + context.Response.AddFileDependency(file.PhysicalPath); + context.Response.ContentType = MimeTypes.MapNameToMimeType(Path.GetFileName(file.PhysicalPath)); + context.Response.Cache.SetNoStore(); + context.Response.Cache.SetLastModifiedFromFileDependencies(); + } + } + + public virtual void PostResolveRequestCache(HttpContextBase context) + { + var request = context?.Request; + if (request == null) + return; - foreach (var route in _routes) + if (_routes.Count > 0) { - if (route.PathPattern.IsMatch(path) && route.HttpMethodPattern.IsMatch(method)) + var path = request.AppRelativeCurrentExecutionFilePath.TrimStart('~').TrimEnd('/'); + var method = request.HttpMethod.EmptyNull(); + + foreach (var route in _routes) { - var module = new UrlRoutingModule(); - module.PostResolveRequestCache(context); - return; + if (route.PathPattern.IsMatch(path) && route.HttpMethodPattern.IsMatch(method)) + { + var module = new UrlRoutingModule(); + module.PostResolveRequestCache(context); + return; + } } - } + } + } + + private bool IsExtensionPath(HttpRequestBase request) + { + var path = request.AppRelativeCurrentExecutionFilePath.ToLower(); + var result = path.StartsWith("~/plugins/") || path.StartsWith("~/themes/"); + return result; } public void Dispose() diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Events.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Events.cs new file mode 100644 index 0000000000..7754edbf54 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Events.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using DotLiquid; + +namespace SmartStore.Templating +{ + /// + /// Published when a template zone is about to be rendered. + /// By subscribing to this event, implementors can inject custom + /// content to specific template zones. + /// + public sealed class ZoneRenderingEvent + { + private IList _snippets; + + public ZoneRenderingEvent(string zoneName, IDictionary model) + { + ZoneName = zoneName; + Model = model; + } + + internal Context LiquidContext { get; set; } + + /// + /// The name of the rendered template. + /// + public string TemplateName + { + get => Evaluate("Context.TemplateName") as string; + } + + /// + /// The name of the zone which is rendered. + /// + public string ZoneName { get; private set; } + + /// + /// The template model + /// + public IDictionary Model { get; private set; } + + /// + /// Evaluates an expression - e.g. Product.Sku - and returns it's value. + /// + /// + /// + public object Evaluate(string expression) + { + return LiquidContext[expression, false]; + } + + /// + /// Specifies the custom content which the template engine should parse and inject. + /// + /// The content + public void InjectContent(string content) + { + if (content.HasValue()) + { + AddSnippet(new Snippet { Content = content, Parse = true }); + } + } + + /// + /// Specifies the custom content to inject. + /// + /// The content + /// This should be true if the content contains template syntax. + public void InjectContent(string content, bool parse) + { + if (content.HasValue()) + { + AddSnippet(new Snippet { Content = content, Parse = parse }); + } + } + + private void AddSnippet(Snippet snippet) + { + if (_snippets == null) + _snippets = new List(); + + _snippets.Add(snippet); + } + + internal IList Snippets + { + get => _snippets; + } + + internal class Snippet + { + public string Content { get; set; } + public bool Parse { get; set; } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/DictionaryDrop.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/DictionaryDrop.cs new file mode 100644 index 0000000000..e23556ddab --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/DictionaryDrop.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Templating.Liquid +{ + internal class DictionaryDrop : SafeDropBase + { + private readonly IDictionary _inner; + + public DictionaryDrop(IDictionary data) + { + Guard.NotNull(data, nameof(data)); + + _inner = data; + } + + public override bool ContainsKey(object key) + { + return (key is string s) + ? _inner.ContainsKey(s) + : false; + } + + protected override object InvokeMember(string name) + { + return _inner.Get(name); + } + + public override object GetWrappedObject() + { + return _inner; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/EnumerableWrapper.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/EnumerableWrapper.cs new file mode 100644 index 0000000000..721e73d533 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/EnumerableWrapper.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using DotLiquid; + +namespace SmartStore.Templating.Liquid +{ + internal class EnumerableWrapper : IEnumerable, ISafeObject + { + private readonly IEnumerable _enumerable; + + public EnumerableWrapper(IEnumerable enumerable) + { + Guard.NotNull(enumerable, nameof(enumerable)); + _enumerable = enumerable; + } + + public IEnumerator GetEnumerator() + { + return GetEnumeratorInternal(); + } + + public object GetWrappedObject() => _enumerable; + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumeratorInternal(); + } + + private IEnumerator GetEnumeratorInternal() + { + return _enumerable + .Cast() + .Select(x => LiquidUtil.CreateSafeObject(x)) + .OfType() + .GetEnumerator(); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs new file mode 100644 index 0000000000..259f6ef028 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs @@ -0,0 +1,59 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using SmartStore.ComponentModel; +using SmartStore.Core; + +namespace SmartStore.Templating.Liquid +{ + internal class ObjectDrop : SafeDropBase + { + private readonly object _data; + private readonly Type _type; + + public ObjectDrop(object data) + { + Guard.NotNull(data, nameof(data)); + + _data = data; + + if (data is BaseEntity be) + { + _type = be.GetUnproxiedType(); + } + else + { + _type = data.GetType(); + } + } + + public override bool ContainsKey(object key) + { + return true; + } + + protected override object InvokeMember(string name) + { + var prop = FastProperty.GetProperty(_type, name); + if (prop != null) + { + return prop.Property.HasAttribute(true) + ? null + : prop.GetValue(_data); + } + + var method = _type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public); + if (method != null && method.GetParameters().Length == 0) + { + return method.Invoke(_data, null); + } + + return null; + } + + public override object GetWrappedObject() + { + return _data; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/SafeDropBase.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/SafeDropBase.cs new file mode 100644 index 0000000000..2974c3cacf --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/SafeDropBase.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using DotLiquid; + +namespace SmartStore.Templating.Liquid +{ + internal interface ISafeObject + { + object GetWrappedObject(); + } + + internal abstract class SafeDropBase : ILiquidizable, IIndexable, ISafeObject + { + private readonly IDictionary _safeObjects = new Dictionary(); + + protected object GetOrCreateSafeObject(string name) + { + if (!_safeObjects.TryGetValue(name, out var safeObject)) + { + safeObject = LiquidUtil.CreateSafeObject(InvokeMember(name)); + if (safeObject is ISafeObject) + { + _safeObjects[name] = safeObject; + } + } + + return safeObject; + } + + public abstract bool ContainsKey(object key); + + public object this[object key] + { + get + { + return (key is string s) ? GetOrCreateSafeObject(s) : null; + } + } + + protected abstract object InvokeMember(string name); + + public object ToLiquid() + { + return this; + } + + public abstract object GetWrappedObject(); + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/TestDrop.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/TestDrop.cs new file mode 100644 index 0000000000..0c02cfc4c2 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/TestDrop.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using DotLiquid; +using SmartStore.ComponentModel; +using SmartStore.Core; + +namespace SmartStore.Templating.Liquid +{ + internal class TestDrop : ITestModel, ILiquidizable, IIndexable, ISafeObject + { + private readonly BaseEntity _entity; + private readonly Type _type; + private readonly string _modelPrefix; + + public TestDrop(BaseEntity entity, string modelPrefix) + { + _entity = entity; + _type = entity.GetUnproxiedType(); + + if (modelPrefix.HasValue()) + { + _modelPrefix = modelPrefix.EnsureEndsWith("."); + } + + _modelPrefix = _modelPrefix ?? string.Empty; + } + + public string ModelName + { + get => _type.Name; + } + + public object GetWrappedObject() + { + return _entity; + } + + public bool ContainsKey(object key) + { + return true; + } + + public object this[object key] + { + get + { + object value = null; + + if (key is string name) + { + var modelPrefix = _modelPrefix + name; + var fastProp = FastProperty.GetProperty(_type, name); + var pi = fastProp?.Property; + + if (pi == null) + { + value = "{{ " + modelPrefix + " }}"; + } + else if (pi.PropertyType.IsPredefinedType()) + { + value = "{{ " + modelPrefix + " }}"; + } + else if (fastProp.IsSequenceType) + { + var seqType = pi.PropertyType.GetGenericArguments()[0]; + if (typeof(BaseEntity).IsAssignableFrom(seqType)) + { + var testObj1 = new TestDrop((BaseEntity)Activator.CreateInstance(seqType), "it"); + var testObj2 = new TestDrop((BaseEntity)Activator.CreateInstance(seqType), "it"); + var list = Activator.CreateInstance(typeof(List)) as List; + list.Add(testObj1); + list.Add(testObj2); + value = list; + } + } + else if (typeof(BaseEntity).IsAssignableFrom(pi.PropertyType)) + { + value = new TestDrop((BaseEntity)Activator.CreateInstance(pi.PropertyType), modelPrefix); + } + + //if (value is string s) + //{ + // value = "{1}".FormatInvariant(invalid ? " invalid" : "", value); + //} + } + + return value; + } + } + + public object ToLiquid() + { + return this; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Filters/AdditionalFilters.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Filters/AdditionalFilters.cs new file mode 100644 index 0000000000..ce03bbb9dc --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Filters/AdditionalFilters.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using DotLiquid; +using Newtonsoft.Json; +using SmartStore.Utilities; +using SmartStore.Services; +using SmartStore.Services.Directory; +using SmartStore.Core.Domain.Directory; +using SmartStore.Services.Common; + +namespace SmartStore.Templating.Liquid +{ + public static class AdditionalFilters + { + private static LiquidTemplateEngine GetEngine() + { + return (LiquidTemplateEngine)Template.FileSystem; + } + + #region Common Filters + + public static object Default(object input, object value) + { + return input ?? value; + } + + public static string Json(object input) + { + if (input == null) + return null; + + return JsonConvert.SerializeObject(input, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }); + } + + public static string FormatAddress(Context context, object input) + { + if (input == null) + return null; + + var engine = GetEngine(); + var countryService = engine.Services.Resolve(); + var addressService = engine.Services.Resolve(); + + Country country = null; + + // We know that we converted Address entity to a dictionary. + + if (input is IDictionary dict) + { + country = countryService.GetCountryById(dict.Get("CountryId").Convert().GetValueOrDefault()); + } + else if (input is IIndexable lq) + { + country = countryService.GetCountryById(lq["CountryId"].Convert().GetValueOrDefault()); + } + + return addressService.FormatAddress(input, country?.AddressFormat, context.FormatProvider); + } + + #endregion + + #region Array Filters + + public static IEnumerable Uniq(IEnumerable input) + { + if (input == null) + return null; + + return input.Cast().Distinct(); + } + + public static IEnumerable Compact(IEnumerable input) + { + if (input == null) + return null; + + return input.Cast().Where(x => x != null); + } + + public static IEnumerable Reverse(IEnumerable input) + { + if (input == null) + return null; + + return input.Cast().Reverse(); + } + + #endregion + + #region Number Filters + + public static object Ceil(object input) + { + if (input == null) + return null; + + return CommonHelper.TryConvert(input, out var d) + ? Math.Ceiling(d) + : 0; + } + + public static object Floor(object input) + { + if (input == null) + return null; + + return CommonHelper.TryConvert(input, out var d) + ? Math.Floor(d) + : 0; + } + + public static object Abs(object input) + { + if (input == null) + return null; + + return CommonHelper.TryConvert(input, out var d) + ? Math.Abs(d) + : 0; + } + + public static object AtMost(object input, object operand) + { + if (input == null || operand == null) + return null; + + return CommonHelper.TryConvert(input, out var d) && CommonHelper.TryConvert(operand, out var o) + ? Math.Min(d, o) + : 0; + } + + public static object AtLeast(object input, object operand) + { + if (input == null || operand == null) + return null; + + return CommonHelper.TryConvert(input, out var d) && CommonHelper.TryConvert(operand, out var o) + ? Math.Max(d, o) + : 0; + } + + #endregion + + #region String Filters + + public static string Prettify(object input, bool allowSpace) + { + if (CommonHelper.TryConvert(input, out var l)) + { + return Prettifier.BytesToString(l); + } + else if (input is string s) + { + return s.Slugify(allowSpace); + } + + return null; + } + + public static string SanitizeHtmlId(string input) + { + return input?.SanitizeHtmlId(); + } + + public static string Md5(string input) + { + return input?.Hash(Encoding.UTF8); + } + + public static string Sha1(string input) + { + return input?.Sha(Encoding.UTF8); + } + + public static string UrlDecode(string input) + { + return input?.UrlDecode(); + } + + public static string Handleize(string input) + { + return Inflector.Handleize(input.EmptyNull()); + } + + public static string Pluralize(string input) + { + return Inflector.Pluralize(input.EmptyNull()); + } + + #endregion + + #region Localization Filters + + public static string T(Context context, string key, object arg1 = null, object arg2 = null, object arg3 = null, object arg4 = null) + { + var engine = GetEngine(); + + int languageId = 0; + + if (context["Context.LanguageId"] is int lid) + { + languageId = lid; + } + + var args = (new object[] { arg1, arg2, arg3, arg4 }).ToArray(); + + return engine.T(key, languageId, args); + } + + #endregion + + #region Url Filters + + #endregion + + #region Html Filters + + public static string ScriptTag(string input) + { + return String.Format("", input); + } + + public static string StylesheetTag(string input) + { + return String.Format("", input); + } + + public static string ImgTag(string input, string alt = "", string css = "") + { + return input == null ? null : GetImageTag(input, alt, css); + } + + private static string GetImageTag(string src, string alt, string css) + { + return String.Format("\"{1}\"", src, alt, css); + } + + #endregion + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplate.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplate.cs new file mode 100644 index 0000000000..c02037c9be --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplate.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Web.Hosting; +using DotLiquid; +using SmartStore.ComponentModel; + +namespace SmartStore.Templating.Liquid +{ + internal class LiquidTemplate : ITemplate + { + public LiquidTemplate(Template template, string source) + { + Guard.NotNull(template, nameof(template)); + Guard.NotNull(source, nameof(source)); + + Template = template; + Source = source; + } + + public string Source + { + get; + internal set; + } + + public Template Template + { + get; + internal set; + } + + public string Render(object model, IFormatProvider formatProvider) + { + Guard.NotNull(model, nameof(model)); + Guard.NotNull(formatProvider, nameof(formatProvider)); + + var p = CreateParameters(model, formatProvider); + return Template.Render(p); + } + + private RenderParameters CreateParameters(object data, IFormatProvider formatProvider) + { + var p = new RenderParameters(formatProvider); + + Hash hash = null; + + if (data is ISafeObject so) + { + if (so.GetWrappedObject() is IDictionary soDict) + { + hash = Hash.FromDictionary(soDict); + } + else + { + data = so.GetWrappedObject(); + } + } + + if (hash == null) + { + hash = new Hash(); + + if (data is IDictionary dict) + { + foreach (var kvp in dict) + { + hash[kvp.Key] = LiquidUtil.CreateSafeObject(kvp.Value); + } + } + else + { + var props = FastProperty.GetProperties(data); + foreach (var prop in props) + { + hash[prop.Key] = LiquidUtil.CreateSafeObject(prop.Value.GetValue(data)); + } + } + } + + p.LocalVariables = hash; + p.ErrorsOutputMode = HostingEnvironment.IsHosted ? ErrorsOutputMode.Display : ErrorsOutputMode.Rethrow; + + return p; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs new file mode 100644 index 0000000000..a229d69208 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs @@ -0,0 +1,169 @@ +using System; +using System.IO; +using System.Web; +using DotLiquid; +using DotLiquid.FileSystems; +using DotLiquid.NamingConventions; +using SmartStore.Core; +using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Core.IO; +using SmartStore.Core.Localization; +using SmartStore.Core.Themes; +using SmartStore.Services; + +namespace SmartStore.Templating.Liquid +{ + public partial class LiquidTemplateEngine : ITemplateEngine, ITemplateFileSystem + { + private readonly Work _services; + private readonly Work _localizer; + private readonly Work _themeContext; + private readonly IVirtualPathProvider _vpp; + + public LiquidTemplateEngine( + Work services, + IVirtualPathProvider vpp, + Work themeContext, + Work localizer) + { + _services = services; + _vpp = vpp; + _localizer = localizer; + _themeContext = themeContext; + + // Register Value type transformers + var allowedMoneyProps = new[] + { + nameof(Money.Amount), + nameof(Money.RoundedAmount), + nameof(Money.TruncatedAmount), + nameof(Money.Formatted), + nameof(Money.DecimalDigits) + }; + Template.RegisterSafeType(typeof(Money), allowedMoneyProps, x => x); + + // Register tag "zone" + Template.RegisterTagFactory(new ZoneTagFactory(_services.Value.EventPublisher)); + + // Register Filters + Template.RegisterFilter(typeof(AdditionalFilters)); + + Template.NamingConvention = new CSharpNamingConvention(); + Template.FileSystem = this; + } + + #region Services + + public ICommonServices Services => _services.Value; + + public LocalizedString T(string key, int languageId, params object[] args) + { + return _localizer.Value(key, languageId, args); + } + + #endregion + + #region ITemplateEngine + + public ITemplate Compile(string source) + { + Guard.NotNull(source, nameof(source)); + + return new LiquidTemplate(Template.Parse(source), source); + } + + public string Render(string source, object model, IFormatProvider formatProvider) + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(model, nameof(model)); + Guard.NotNull(formatProvider, nameof(formatProvider)); + + return Compile(source).Render(model, formatProvider); + } + + public ITestModel CreateTestModelFor(BaseEntity entity, string modelPrefix) + { + Guard.NotNull(entity, nameof(entity)); + + return new TestDrop(entity, modelPrefix); + } + + #endregion + + #region ITemplateFileSystem + + public Template GetTemplate(Context context, string templateName) + { + var virtualPath = ResolveVirtualPath(context, templateName); + + if (virtualPath.IsEmpty()) + { + return null; + } + + var cacheKey = HttpRuntime.Cache.BuildScopedKey("LiquidPartial://" + virtualPath); + var cachedTemplate = HttpRuntime.Cache.Get(cacheKey); + + if (cachedTemplate == null) + { + // Read from file, compile and put to cache with file dependeny + var source = ReadTemplateFileInternal(virtualPath); + cachedTemplate = Template.Parse(source); + var cacheDependency = _vpp.GetCacheDependency(virtualPath, DateTime.UtcNow); + HttpRuntime.Cache.Insert(cacheKey, cachedTemplate, cacheDependency); + } + + return (Template)cachedTemplate; + } + + public string ReadTemplateFile(Context context, string templateName) + { + var virtualPath = ResolveVirtualPath(context, templateName); + + return ReadTemplateFileInternal(virtualPath); + } + + private string ReadTemplateFileInternal(string virtualPath) + { + if (virtualPath.IsEmpty()) + { + return string.Empty; + } + + if (!_vpp.FileExists(virtualPath)) + { + throw new FileNotFoundException($"Include file '{virtualPath}' does not exist."); + } + + using (var stream = _vpp.OpenFile(virtualPath)) + { + return stream.AsString(); + } + } + + private string ResolveVirtualPath(Context context, string templateName) + { + var path = ((string)context[templateName]).NullEmpty() ?? templateName; + + if (path.IsEmpty()) + return string.Empty; + + path = path.EnsureEndsWith(".liquid"); + + string virtualPath = null; + + if (!path.StartsWith("~/")) + { + var currentTheme = _themeContext.Value.CurrentTheme; + virtualPath = _vpp.Combine(currentTheme.Location, currentTheme.ThemeName, "Views/Shared/EmailTemplates", path); + } + else + { + virtualPath = VirtualPathUtility.ToAppRelative(path); + } + return virtualPath; + } + + #endregion + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs new file mode 100644 index 0000000000..9363346814 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace SmartStore.Templating.Liquid +{ + internal static class LiquidUtil + { + private static readonly IDictionary> _typeWrapperCache + = new Dictionary>(); + + internal static object CreateSafeObject(object value) + { + if (value == null) + { + return null; + } + + if (value is TestDrop || value is IFormattable) + { + return value; + } + + var valueType = value.GetType(); + + if (!_typeWrapperCache.TryGetValue(valueType, out var fn)) + { + if (value is IDictionary dict) + { + fn = x => new DictionaryDrop((IDictionary)x); + } + else if (valueType.IsSequenceType()) + { + var genericArgs = valueType.GetGenericArguments(); + var isEnumerable = genericArgs.Length == 1 && valueType.IsSubClass(typeof(IEnumerable<>)); + if (isEnumerable) + { + var seqType = genericArgs[0]; + if (!IsSafeType(seqType)) + { + fn = x => new EnumerableWrapper((IEnumerable)x); + } + } + } + else if (valueType.IsPlainObjectType()) + { + fn = x => new ObjectDrop(x); + } + + _typeWrapperCache[valueType] = fn; + } + + return fn?.Invoke(value) ?? value; + } + + public static bool IsSafeType(Type type) + { + return type.IsPredefinedType(); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Tags/Zone.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Tags/Zone.cs new file mode 100644 index 0000000000..ed1113cf1c --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Tags/Zone.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using DotLiquid; +using DotLiquid.Exceptions; +using DotLiquid.Util; +using SmartStore.Core.Events; + +namespace SmartStore.Templating.Liquid +{ + internal sealed class ZoneTagFactory : ITagFactory + { + private readonly IEventPublisher _eventPublisher; + + public ZoneTagFactory(IEventPublisher eventPublisher) + { + _eventPublisher = eventPublisher; + } + + public string TagName => "zone"; + + public Tag Create() + { + return new Zone { EventPublisher = _eventPublisher }; + } + + class Zone : Tag + { + private static readonly Regex Syntax = R.B(@"^({0})", DotLiquid.Liquid.QuotedFragment); + + private string _zoneName; + + public IEventPublisher EventPublisher { get; set; } + + public override void Initialize(string tagName, string markup, List tokens) + { + Match syntaxMatch = Syntax.Match(markup); + + if (syntaxMatch.Success) + { + _zoneName = syntaxMatch.Groups[1].Value; + } + else + { + throw new SyntaxException("Syntax Error in 'zone' tag - Valid syntax: zone '[ZoneName]'."); + } + + base.Initialize(tagName, markup, tokens); + } + + public override void Render(Context context, TextWriter result) + { + var zoneName = (string)context[_zoneName] ?? _zoneName; + + if (zoneName.IsEmpty()) + return; + + var model = context.Environments.First(); + var evt = new ZoneRenderingEvent(zoneName, model); + + evt.LiquidContext = context; + + EventPublisher.Publish(evt); + + if (evt.Snippets != null && evt.Snippets.Count > 0) + { + foreach (var snippet in evt.Snippets) + { + if (snippet.Parse) + { + Template.Parse(snippet.Content) + .Render(result, new RenderParameters(context.FormatProvider) { LocalVariables = model }); + } + else + { + result.Write(snippet.Content); + } + } + } + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/AdminThemedAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Theming/AdminThemedAttribute.cs index bb5a86dd51..5698f20851 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/AdminThemedAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/AdminThemedAttribute.cs @@ -1,4 +1,6 @@ -using System.Web.Mvc; +using System; +using System.Web.Mvc; +using SmartStore.Core; namespace SmartStore.Web.Framework.Theming { @@ -12,20 +14,43 @@ namespace SmartStore.Web.Framework.Theming /// Without this attribute the view resolver would directly fallback to the default nameless area /// when a view could not be resolved from within the plugin area. /// - public class AdminThemedAttribute : ActionFilterAttribute + public class AdminThemedAttribute : FilterAttribute, IResultFilter { - public override void OnActionExecuting(ActionExecutingContext filterContext) + public readonly static string[] ExtraAreaViewLocations = new string[] { - if (filterContext == null) + "~/Administration/Views/{1}/{0}", + "~/Administration/Views/Shared/{0}" + }; + + public Lazy WorkContext { get; set; } + + public virtual void OnResultExecuting(ResultExecutingContext filterContext) + { + if (filterContext?.Result == null) + return; + + if (!filterContext.Result.IsHtmlViewResult()) return; - // add extra view location formats to all view results (even the partial ones) - filterContext.RouteData.DataTokens["ExtraAreaViewLocations"] = new string[] + if (filterContext.HttpContext.Response.StatusCode < 300) { - "~/Administration/Views/{1}/{0}", - "~/Administration/Views/Shared/{0}" - }; + var isNonAdmin = filterContext.HttpContext.GetItem("IsNonAdmin", forceCreation: false); + if(!isNonAdmin) + { + // Add extra view location formats to all view results (even the partial ones) + // {0} is appended by view engine + filterContext.RouteData.DataTokens["ExtraAreaViewLocations"] = ExtraAreaViewLocations; + + if (!filterContext.IsChildAction) + { + WorkContext.Value.IsAdmin = true; + } + } + } } + public virtual void OnResultExecuted(ResultExecutedContext filterContext) + { + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs new file mode 100644 index 0000000000..62bf5fd53e --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BundleTransformer.Core.Assets; +using BundleTransformer.Core.Constants; +using BundleTransformer.Core.Translators; +using BundleTransformer.SassAndScss.Translators; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Logging; +using SmartStore.Utilities.Threading; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + public abstract class AssetTranslator : ITranslator + where TTranslator : ITranslator, new() + { + private readonly ITranslator _inner; + + public AssetTranslator() + { + _inner = new TTranslator(); + } + + protected abstract string[] ValidTypeCodes { get; } + + private IAssetCache AssetCache + { + get { return EngineContext.Current.Resolve(); } + } + + public bool IsDebugMode + { + get { return _inner.IsDebugMode; } + set { _inner.IsDebugMode = value; } + } + + public IList Translate(IList assets) + { + var assets2 = WalkAndFindCachedAssets(assets).ToList(); + var result = _inner.Translate(assets2); + return result; + } + + private IEnumerable WalkAndFindCachedAssets(IList assets) + { + foreach (var asset in assets) + { + if (ValidTypeCodes.Contains(asset.AssetTypeCode.ToLowerInvariant())) + { + yield return TranslateInternal(asset); + } + else + { + yield return asset; + } + } + } + + public IAsset Translate(IAsset asset) + { + return TranslateInternal(asset); + } + + private IAsset TranslateInternal(IAsset asset) + { + IAsset result; + var chronometer = EngineContext.Current.Resolve(); + + using (chronometer.Step("Translate asset {0}".FormatInvariant(asset.VirtualPath))) + { + bool validationMode = ThemeHelper.IsStyleValidationRequest(); + + if (validationMode || !TryGetCachedAsset(asset, out result)) + { + using (KeyedLock.Lock("CachedAsset:" + asset.VirtualPath)) + { + if (validationMode || !TryGetCachedAsset(asset, out result)) + { + using (chronometer.Step("Compile asset {0}".FormatInvariant(asset.VirtualPath))) + { + result = _inner.Translate(asset); + + var cachedAsset = new CachedAsset + { + AssetTypeCode = AssetTypeCode.Css, + IsStylesheet = true, + Minified = result.Minified, + Combined = result.Combined, + Content = result.Content, + OriginalAssets = asset.OriginalAssets, + VirtualPath = asset.VirtualPath, + VirtualPathDependencies = result.VirtualPathDependencies, + Url = asset.Url + }; + + result = AssetTranslorUtil.PostProcessAsset(cachedAsset, this.IsDebugMode); + + if (!validationMode) + { + var pCodes = new List(3); + if (cachedAsset.Minified) pCodes.Add(DefaultAssetCache.MinificationCode); + if (cachedAsset.RelativePathsResolved) pCodes.Add(DefaultAssetCache.UrlRewriteCode); + if (cachedAsset.Autoprefixed) pCodes.Add(DefaultAssetCache.AutoprefixCode); + + AssetCache.InsertAsset( + cachedAsset.VirtualPath, + cachedAsset.VirtualPathDependencies, + cachedAsset.Content, + pCodes.ToArray()); + } + } + } + } + } + } + + return result; + } + + private bool TryGetCachedAsset(IAsset asset, out IAsset cachedAsset) + { + cachedAsset = null; + var entry = AssetCache.GetAsset(asset.VirtualPath); + + if (entry != null) + { + cachedAsset = new CachedAsset + { + AssetTypeCode = AssetTypeCode.Css, + Combined = true, + Content = entry.Content, + IsStylesheet = true, + Minified = entry.ProcessorCodes.Contains(DefaultAssetCache.MinificationCode), + RelativePathsResolved = entry.ProcessorCodes.Contains(DefaultAssetCache.UrlRewriteCode), + Autoprefixed = entry.ProcessorCodes.Contains(DefaultAssetCache.AutoprefixCode), + OriginalAssets = new List(), + VirtualPath = asset.VirtualPath, + VirtualPathDependencies = entry.VirtualPathDependencies.ToList(), + Url = asset.Url + }; + } + + return cachedAsset != null; + } + } + + public sealed class SassTranslator : AssetTranslator + { + protected override string[] ValidTypeCodes + { + get { return new[] { "sass", "scss" }; } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslorUtil.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslorUtil.cs new file mode 100644 index 0000000000..fc7d0b2c2f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslorUtil.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using BundleTransformer.Core; +using BundleTransformer.Core.Assets; +using BundleTransformer.Core.Transformers; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + internal static class AssetTranslorUtil + { + internal static IAsset PostProcessAsset(IAsset asset, bool isDebugMode) + { + var transformer = BundleTransformerContext.Current.Styles.GetDefaultTransformInstance() as ITransformer; + if (transformer != null) + { + var processors = transformer.PostProcessors.Where(x => x.UseInDebugMode || !isDebugMode); + foreach (var processor in processors) + { + asset = processor.PostProcess(asset); + } + } + + return asset; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AutoPrefixPostProcessor.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AutoPrefixPostProcessor.cs new file mode 100644 index 0000000000..254d8a6bfd --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AutoPrefixPostProcessor.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BundleTransformer.Core.Assets; +using BundleTransformer.Core.PostProcessors; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + public class AutoprefixPostProcessor : IPostProcessor + { + private readonly BundleTransformer.Autoprefixer.PostProcessors.AutoprefixCssPostProcessor _inner; + + public AutoprefixPostProcessor() + { + _inner = new BundleTransformer.Autoprefixer.PostProcessors.AutoprefixCssPostProcessor(); + } + + public bool UseInDebugMode + { + get { return _inner.UseInDebugMode; } + set { _inner.UseInDebugMode = value; } + } + + public IList PostProcess(IList assets) + { + Guard.NotNull(assets, nameof(assets)); + + var list = assets.Where(CanProcess).ToList(); + + if (list.Count == 0) + { + return assets; + } + + // Process only unprocessed assets... + _inner.PostProcess(list); + + // Set Autoprefixed flag to true in our custom cached assets + list.OfType().Each(x => x.Autoprefixed = true); + + // ...but return the original list + return assets; + } + + public IAsset PostProcess(IAsset asset) + { + Guard.NotNull(asset, nameof(asset)); + + var cachedAsset = asset as CachedAsset; + if (cachedAsset == null || cachedAsset.Autoprefixed == false) + { + asset = _inner.PostProcess(asset); + + if (cachedAsset != null) + { + cachedAsset.Autoprefixed = true; + } + + return asset; + } + + return asset; + } + + private static bool CanProcess(IAsset asset) + { + var cachedAsset = asset as CachedAsset; + return asset.IsStylesheet && (cachedAsset == null || cachedAsset.Autoprefixed == false); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs new file mode 100644 index 0000000000..edb20c5c8b --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Web.Caching; +using System.Web.Hosting; +using SmartStore.Core.Infrastructure; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + public sealed class BundlingVirtualPathProvider : ThemingVirtualPathProvider + { + private readonly SassCheckedPathStack _sassCheckedPathStack; + + public BundlingVirtualPathProvider(VirtualPathProvider previous) + : base(previous) + { + _sassCheckedPathStack = new SassCheckedPathStack(); + } + + public override bool FileExists(string virtualPath) + { + var exists = false; + var styleResult = ThemeHelper.IsStyleSheet(virtualPath); + + TokenizedSassPath sassPath = null; + + if (styleResult != null) + { + exists = _sassCheckedPathStack.Check(styleResult, out sassPath); + if (exists) + { + // It seems awkward to return false when check result actually says true. + // But true in this context means: a check for THIS sass file pattern (e.g. _slick.scss.sass) + // yielded true previously (because _slick.scss really exists on disks), therefore _slick.scss.sass + // CANNOT exist (or SHOULD not by convention). By returning false we prevent that a real filesystem check + // is made against _slick.scss.sass (which is huge performace saver considering that IThemeFileResolver + // also does some additional checks). + return false; + } + + if (styleResult.IsThemeVars || styleResult.IsModuleImports) + { + _sassCheckedPathStack.PushExistingPath(sassPath); + return true; + } + } + + exists = base.FileExists(virtualPath); + + if (exists && sassPath != null) + { + _sassCheckedPathStack.PushExistingPath(sassPath); + } + + return exists; + } + + public override VirtualFile GetFile(string virtualPath) + { + var styleResult = ThemeHelper.IsStyleSheet(virtualPath); + if (styleResult != null) + { + if (styleResult.IsThemeVars) + { + var theme = ThemeHelper.ResolveCurrentTheme(); + int storeId = ThemeHelper.ResolveCurrentStoreId(); + return new ThemeVarsVirtualFile(virtualPath, styleResult.Extension, theme.ThemeName, storeId); + } + else if (styleResult.IsModuleImports) + { + return new ModuleImportsVirtualFile(virtualPath, ThemeHelper.IsAdminArea()); + } + } + + return base.GetFile(virtualPath); + } + + public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) + { + if (ThemeHelper.IsStyleValidationRequest()) + { + var themeVarsPath = virtualPathDependencies.Cast().FirstOrDefault(x => ThemeHelper.PathIsThemeVars(x)); + if (themeVarsPath.HasValue()) + { + return base.GetCacheDependency(virtualPath, new[] { themeVarsPath }, utcStart); + } + } + + return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart); + } + } + + internal class SassCheckedPathStack + { + private readonly string[] _styleExtensions = new[] { ".scss", ".sass", ".css" }; + + private readonly ContextState> _state + = new ContextState>("SassCheckedPathStack.State", () => new Stack()); + + public SassCheckedPathStack() + { + } + + /// + /// Checks last path existence + /// + /// true = does exist, no need to check | false = not checked yet + public bool Check(StyleSheetResult styleResult, out TokenizedSassPath path) + { + path = new TokenizedSassPath(styleResult); + + var state = _state.GetState(); + if (state.Count == 0) + return false; + + var currentPath = path; + var lastPath = _state.GetState().Peek(); + + if (currentPath.Extension.IsEmpty()) + { + // We dont't allow extension-less Sass files, so no need to check. + return true; + } + + if (lastPath.Dir != currentPath.Dir) + { + return false; + } + + if (currentPath.StyleResult.IsBaseImport && lastPath.StyleResult.IsBaseImport && currentPath.FileName == lastPath.FileName) + { + return true; + } + + if (currentPath.StyleResult.IsModuleImports && lastPath.StyleResult.IsModuleImports) + { + return true; + } + + if (currentPath.StyleResult.IsThemeVars && lastPath.StyleResult.IsThemeVars) + { + return true; + } + + // slick.scss.(scss|sass|css) > slick.scss + if (Path.GetExtension(currentPath.FileNameWithoutExtension) == ".scss") + { + return true; + } + + // slick.(sass|css) > slick.scss + if (!currentPath.StyleResult.IsBaseImport && _styleExtensions.Contains(currentPath.Extension) && currentPath.FileNameWithoutExtension == lastPath.FileNameWithoutExtension) + { + return true; + } + + // _slick.scss > slick.scss + if (currentPath.FileName.StartsWith("_")) + { + if (currentPath.FileName.Substring(1) == lastPath.FileName) + { + return true; + } + } + + // slick.(scss|sass|css) > _slick.scss + if (lastPath.FileNameWithoutExtension.StartsWith("_")) + { + if (lastPath.FileNameWithoutExtension == "_" + currentPath.FileNameWithoutExtension) + { + return true; + } + } + + return false; + } + + public void PushExistingPath(TokenizedSassPath path) + { + _state.GetState().Push(path); + } + } + + internal class TokenizedSassPath + { + public TokenizedSassPath(StyleSheetResult styleResult) + { + StyleResult = styleResult; + VirtualPath = styleResult.Path; + Extension = styleResult.Extension.EmptyNull(); + FileName = Path.GetFileName(styleResult.Path); + FileNameWithoutExtension = FileName.Substring(0, FileName.Length - Extension.Length); + Dir = VirtualPath.Substring(0, VirtualPath.Length - FileName.Length); + } + + public StyleSheetResult StyleResult { get; set; } + public string VirtualPath { get; private set; } + public string Dir { get; private set; } + public string FileName { get; private set; } + public string FileNameWithoutExtension { get; private set; } + public string Extension { get; private set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/CachedAsset.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/CachedAsset.cs new file mode 100644 index 0000000000..6844f235cc --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/CachedAsset.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using BundleTransformer.Core.Assets; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + internal class CachedAsset : IAsset + { + public string AssetTypeCode { get; set; } + public bool Combined { get; set; } + public string Content { get; set; } + public bool IsScript { get; set; } + public bool IsStylesheet { get; set; } + public bool Minified { get; set; } + public bool RelativePathsResolved { get; set; } + public bool Autoprefixed { get; set; } + public IList OriginalAssets { get; set; } + public string Url { get; set; } + public string VirtualPath { get; set; } + public IList VirtualPathDependencies { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs new file mode 100644 index 0000000000..f1eaf270da --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Caching; +using System.Web.Hosting; +using System.Web.Optimization; +using SmartStore.Core; +using SmartStore.Core.Domain.Themes; +using SmartStore.Core.IO; +using SmartStore.Core.Logging; +using SmartStore.Core.Themes; +using SmartStore.Services; +using SmartStore.Utilities.Threading; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + public class DefaultAssetCache : IAssetCache + { + public const string MinificationCode = "min"; + public const string AutoprefixCode = "autoprefix"; + public const string UrlRewriteCode = "urlrewrite"; + + public static readonly IAssetCache Null = new NullCache(); + const string CacheKeyPrefix = "sm:AssetCacheEntry:"; + + private readonly bool _isEnabled; + private readonly ThemeSettings _themeSettings; + private readonly IApplicationEnvironment _env; + private readonly IThemeContext _themeContext; + private readonly IThemeRegistry _themeRegistry; + private readonly IThemeFileResolver _themeFileResolver; + private readonly ICommonServices _services; + + private VirtualFolder _cacheFolder; + + public DefaultAssetCache( + ThemeSettings themeSettings, + IApplicationEnvironment env, + IThemeFileResolver themeFileResolver, + IThemeContext themeContext, + IThemeRegistry themeRegistry, + ICommonServices services) + { + _themeSettings = themeSettings; + _isEnabled = _themeSettings.AssetCachingEnabled == 2 || (_themeSettings.AssetCachingEnabled == 0 && !HttpContext.Current.IsDebuggingEnabled); + + _env = env; + _themeContext = themeContext; + _themeFileResolver = themeFileResolver; + _themeRegistry = themeRegistry; + _services = services; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public IVirtualFolder CacheFolder + { + get + { + if (_cacheFolder == null) + { + _cacheFolder = new VirtualFolder( + _env.TenantFolder.GetVirtualPath("AssetCache"), + _env.TenantFolder.VirtualPathProvider, + Logger); + } + + return _cacheFolder; + } + } + + public CachedAssetEntry GetAsset(string virtualPath) + { + if (!_isEnabled) + return null; + + Guard.NotEmpty(virtualPath, nameof(virtualPath)); + + var cacheDirectoryName = ResolveCacheDirectoryName(virtualPath, out string themeName, out int storeId); + + if (CacheFolder.DirectoryExists(cacheDirectoryName)) + { + try + { + var deps = CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.dependencies")); + var hash = CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.hash")); + + if (!TryValidate(virtualPath, deps, hash, out IEnumerable parsedDeps, out string currentHash)) + { + Logger.DebugFormat("Invalidating cached asset for '{0}' because it is not valid anymore.", virtualPath); + InvalidateAssetInternal(cacheDirectoryName, themeName, storeId); + return null; + } + + // TODO: determine correct file extension + var content = CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.css")); + if (content == null) + { + using (KeyedLock.Lock(BuildLockKey(cacheDirectoryName))) + { + InvalidateAssetInternal(cacheDirectoryName, themeName, storeId); + return null; + } + } + + var codes = ParseFileContent(CacheFolder.ReadFile(CacheFolder.Combine(cacheDirectoryName, "asset.pcodes"))); + + var entry = new CachedAssetEntry + { + Content = content, + HashCode = currentHash, + OriginalVirtualPath = virtualPath, + VirtualPathDependencies = parsedDeps, + PhysicalPath = CacheFolder.MapPath(cacheDirectoryName), + ThemeName = themeName, + StoreId = storeId, + ProcessorCodes = codes.ToArray() + }; + + SetupEvictionObserver(entry); + + Logger.DebugFormat("Succesfully read asset '{0}' from cache.", virtualPath); + + return entry; + } + catch (Exception ex) + { + Logger.ErrorFormat(ex, "Error while resolving asset '{0}' from the asset cache.", virtualPath); + } + } + + return null; + } + + public CachedAssetEntry InsertAsset(string virtualPath, IEnumerable virtualPathDependencies, string content, params string[] processorCodes) + { + if (!_isEnabled) + return null; + + Guard.NotEmpty(virtualPath, nameof(virtualPath)); + Guard.NotEmpty(content, nameof(content)); + + var cacheDirectoryName = ResolveCacheDirectoryName(virtualPath, out string themeName, out int storeId); + + using (KeyedLock.Lock(BuildLockKey(cacheDirectoryName))) + { + CacheFolder.CreateDirectory(cacheDirectoryName); + + try + { + // Save main content file + // TODO: determine correct file extension + CreateFileFromEntries(cacheDirectoryName, "asset.css", new[] { content }); + + // Save dependencies file + var deps = ResolveVirtualPathDependencies(virtualPath, virtualPathDependencies, themeName); + CreateFileFromEntries(cacheDirectoryName, "asset.dependencies", deps); + + // Save hash file + var currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, deps); + CreateFileFromEntries(cacheDirectoryName, "asset.hash", new[] { currentHash }); + + // Save codes file + CreateFileFromEntries(cacheDirectoryName, "asset.pcodes", processorCodes); + + var entry = new CachedAssetEntry + { + Content = content, + HashCode = currentHash, + OriginalVirtualPath = virtualPath, + VirtualPathDependencies = deps, + PhysicalPath = CacheFolder.MapPath(cacheDirectoryName), + ThemeName = themeName, + StoreId = storeId, + ProcessorCodes = processorCodes + }; + + SetupEvictionObserver(entry); + + Logger.DebugFormat("Succesfully inserted asset '{0}' to cache.", virtualPath); + + return entry; + } + catch (Exception ex) + { + Logger.ErrorFormat(ex, "Error while inserting asset '{0}' to the asset cache.", virtualPath); + InvalidateAssetInternal(cacheDirectoryName, themeName, storeId); + } + } + + return null; + } + + public void Clear() + { + if (_env.TenantFolder.DirectoryExists("AssetCache")) + { + _env.TenantFolder.TryDeleteDirectory("AssetCache"); + // Remove the eviction observer also + HttpRuntime.Cache.RemoveByPattern(CacheKeyPrefix); + } + } + + public bool InvalidateAsset(string virtualPath) + { + Guard.NotEmpty(virtualPath, nameof(virtualPath)); + + var cacheDirectoryName = ResolveCacheDirectoryName(virtualPath, out string themeName, out int storeId); + + using (KeyedLock.Lock(BuildLockKey(cacheDirectoryName))) + { + if (CacheFolder.DirectoryExists(cacheDirectoryName)) + { + return InvalidateAssetInternal(cacheDirectoryName, themeName, storeId); + } + + return false; + } + } + + private string BuildLockKey(string dirName) + { + return "AssetCache.Dir." + dirName; + } + + /// + /// Invalidates a cached asset when any themevar was changed or the theme was touched on file system + /// + /// + private static void SetupEvictionObserver(CachedAssetEntry entry) + { + if (entry.ThemeName == null) + return; + + var cacheKey = CacheKeyPrefix + "{0}:{1}".FormatInvariant(entry.ThemeName, entry.StoreId); + + var cacheDependency = new CacheDependency( + new string[0], + new[] { FrameworkCacheConsumer.BuildThemeVarsCacheKey(entry.ThemeName, entry.StoreId) }, + DateTime.UtcNow); + + HttpRuntime.Cache.Insert( + cacheKey, + entry.PhysicalPath, + cacheDependency, + Cache.NoAbsoluteExpiration, + Cache.NoSlidingExpiration, + CacheItemPriority.Default, + OnCacheItemRemoved); + } + + private static void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason) + { + if (HostingEnvironment.ShutdownReason > ApplicationShutdownReason.None) + { + // Don't evict cached files during app shutdown. Dependant cache keys change + // during a shutdown caused by HttpRuntime.Cache full eviction. + return; + } + + // Keep this low level + var path = value as string; + if (path.HasValue() && reason == CacheItemRemovedReason.DependencyChanged) + { + try + { + Directory.Delete(path, true); + } + catch { } + } + } + + private IEnumerable ResolveVirtualPathDependencies(string virtualPath, IEnumerable deps, string themeName) + { + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var d in (deps ?? new string[0]).Concat(new[] { virtualPath })) + { + var result = _themeFileResolver.Resolve(d); + list.Add(result != null ? result.ResultVirtualPath : d); + } + + // Add all theme.config files in the theme hierarchy chain + var manifest = _themeRegistry.GetThemeManifest(themeName); + while (manifest != null) + { + list.Add(VirtualPathUtility.ToAbsolute(CacheFolder.Combine(manifest.Location, manifest.ThemeName, "theme.config"))); + manifest = manifest.BaseTheme; + } + + return list; + } + + private bool InvalidateAssetInternal(string cacheDirectoryName, string theme, int storeId) + { + if (CacheFolder.TryDeleteDirectory(cacheDirectoryName)) + { + if (theme.HasValue()) + { + // Remove the eviction observer + var cacheKey = CacheKeyPrefix + "{0}:{1}".FormatInvariant(theme, storeId); + HttpRuntime.Cache.Remove(cacheKey); + + } + + return true; + } + + return false; + } + + /// + /// Checks whether asset is up-to-date. + /// + /// + /// + /// + /// + /// false if asset is not valid anymore and must be evicted from cache + private bool TryValidate(string virtualPath, string lastDeps, string lastHash, out IEnumerable parsedDeps, out string currentHash) + { + parsedDeps = null; + currentHash = null; + + try + { + if (lastDeps.IsEmpty() || lastHash.IsEmpty()) + { + return false; + } + + parsedDeps = ParseFileContent(lastDeps); + + // Check if dependency files hash matches the last saved hash + currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, parsedDeps); + + return lastHash == currentHash; + } + catch + { + return false; + } + } + + private string ResolveCacheDirectoryName(string virtualPath, out string themeName, out int storeId) + { + themeName = null; + storeId = 0; + + if (virtualPath[0] != '~') + { + virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); + } + + // Build a dir name: ~/Themes/Flex/Content/theme.scss > themes-flex-content-theme.scss-flex-1 + var folderName = virtualPath.TrimStart('~', '/', '\\') + .Replace('/', '-') + .Replace('\\', '-'); + + if (ThemeHelper.PathIsInheritableThemeFile(virtualPath)) + { + themeName = _themeContext.CurrentTheme.ThemeName; + storeId = _services.StoreContext.CurrentStore.Id; + folderName += "-" + themeName + "-" + storeId; + } + + return folderName.ToLowerInvariant(); + } + + private IEnumerable ParseFileContent(string content) + { + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + if (content.IsEmpty()) return list; + + var sr = new StringReader(content); + while (true) + { + var f = sr.ReadLine(); + if (f != null && f.HasValue()) + { + list.Add(f); + } + else + { + break; + } + } + + return list; + } + + private void CreateFileFromEntries(string dirName, string fileName, IEnumerable entries) + { + if (entries == null || !entries.Any()) + return; + + var sb = new StringBuilder(); + foreach (var f in entries) + { + sb.AppendLine(f); + } + + var content = sb.ToString().TrimEnd(); + if (content.HasValue()) + { + CacheFolder.CreateTextFile(CacheFolder.Combine(dirName, fileName), content); + } + } + + class NullCache : IAssetCache + { + public void Clear() + { + } + + public CachedAssetEntry GetAsset(string virtualPath) + { + return null; + } + + public CachedAssetEntry InsertAsset(string virtualPath, IEnumerable virtualPathDependencies, string content, params string[] processorCodes) + { + return null; + } + + public bool InvalidateAsset(string virtualPath) + { + return false; + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/IAssetCache.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/IAssetCache.cs new file mode 100644 index 0000000000..b0f888ef03 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/IAssetCache.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + public class CachedAssetEntry + { + public string Content { get; set; } + public string OriginalVirtualPath { get; set; } + public string PhysicalPath { get; set; } + public IEnumerable VirtualPathDependencies { get; set; } + public string HashCode { get; set; } + public string ThemeName { get; set; } + public int StoreId { get; set; } + public string[] ProcessorCodes { get; set; } + } + + /// + /// A file system based caching mechanism for dynamically translated assets like Sass, Less etc. + /// + public interface IAssetCache + { + CachedAssetEntry GetAsset(string virtualPath); + + CachedAssetEntry InsertAsset(string virtualPath, IEnumerable virtualPathDependencies, string content, params string[] processorCodes); + + bool InvalidateAsset(string virtualPath); + + void Clear(); + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs new file mode 100644 index 0000000000..34b4557fe5 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs @@ -0,0 +1,159 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Linq; +using System.Web.Hosting; +using SmartStore.Core.Plugins; +using SmartStore.Core.Data; +using System; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Theming.Assets +{ + public sealed class ModuleImport + { + public string VirtualPath { get; internal set; } + public string PhysicalPath { get; internal set; } + public PluginDescriptor PluginDescriptor { get; internal set; } + public bool IsAdmin { get; internal set; } + + public override string ToString() + { + return VirtualPath; + } + + public override int GetHashCode() + { + return VirtualPath.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (!(obj is ModuleImport other)) + return false; + + return string.Equals(this.VirtualPath, other.VirtualPath, StringComparison.OrdinalIgnoreCase); + } + } + + public class ModuleImportsVirtualFile : VirtualFile, IFileDependencyProvider + { + private static readonly HashSet _adminImports; + private static readonly HashSet _publicImports; + + static ModuleImportsVirtualFile() + { + _adminImports = new HashSet(); + _publicImports = new HashSet(); + + if (DataSettings.DatabaseIsInstalled()) + { + CollectModuleImports(); + } + } + + private static void CollectModuleImports() + { + var installedPlugins = PluginManager.ReferencedPlugins.Where(x => x.Installed); + var root = PluginManager.PluginsLocation; + + foreach (var plugin in installedPlugins) + { + var contentDir = Path.Combine(plugin.PhysicalPath, "Content"); + if (!Directory.Exists(contentDir)) + continue; + + TryAddImport(plugin, _publicImports, "public.scss"); + TryAddImport(plugin, _adminImports, "admin.scss"); + } + + void TryAddImport(PluginDescriptor plugin, HashSet imports, string name) + { + var physicalPath = Path.Combine(plugin.PhysicalPath, "Content", name); + if (File.Exists(physicalPath)) + { + imports.Add(new ModuleImport + { + PhysicalPath = physicalPath, + VirtualPath = $"{root}/{plugin.FolderName}/Content/{name}", + PluginDescriptor = plugin, + IsAdmin = name.Contains("admin") + }); + } + } + } + + public static ModuleImport[] PublicImports + { + get => _publicImports.ToArray(); + } + + public static ModuleImport[] AdminImports + { + get => _adminImports.ToArray(); + } + + public ModuleImportsVirtualFile(string virtualPath, bool isAdmin) + : base(virtualPath) + { + IsAdmin = isAdmin; + } + + public override bool IsDirectory + { + get { return false; } + } + + public bool IsAdmin + { + get; + private set; + } + + public override Stream Open() + { + var sb = new StringBuilder(); + + var imports = IsAdmin ? _adminImports : _publicImports; + foreach (var imp in imports) + { + sb.AppendLine($"@import '{imp.VirtualPath}';"); + } + + return GenerateStreamFromString(sb.ToString()); + } + + public void HashCombine(HashCodeCombiner combiner) + { + Guard.NotNull(combiner, nameof(combiner)); + + var imports = IsAdmin ? _adminImports : _publicImports; + foreach (var imp in imports) + { + combiner.Add(new FileInfo(imp.PhysicalPath)); + } + } + + private Stream GenerateStreamFromString(string value) + { + var stream = new MemoryStream(); + + using (var writer = new StreamWriter(stream, Encoding.Unicode, 1024, true)) + { + writer.Write(value); + writer.Flush(); + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + } + + public void AddFileDependencies(ICollection mappedPaths, ICollection cacheKeys) + { + var imports = IsAdmin ? _adminImports : _publicImports; + foreach (var imp in imports) + { + mappedPaths.Add(imp.PhysicalPath); + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/SmartStyleBundle.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/SmartStyleBundle.cs similarity index 81% rename from src/Presentation/SmartStore.Web.Framework/Theming/SmartStyleBundle.cs rename to src/Presentation/SmartStore.Web.Framework/Theming/Assets/SmartStyleBundle.cs index 5c44cb71d7..d2930486a7 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/SmartStyleBundle.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/SmartStyleBundle.cs @@ -6,7 +6,7 @@ using BundleTransformer.Core; using BundleTransformer.Core.Builders; -namespace SmartStore.Web.Framework.Theming +namespace SmartStore.Web.Framework.Theming.Assets { internal class SmartStyleBundle : Bundle { @@ -20,19 +20,19 @@ public SmartStyleBundle(string virtualPath) [SuppressMessage("ReSharper", "VirtualMemberCallInContructor")] public SmartStyleBundle(string virtualPath, string cdnPath) - : base(virtualPath, cdnPath, new IBundleTransform[] { BundleTransformerContext.Current.GetCssTransformerInstance() }) + : base(virtualPath, cdnPath, new IBundleTransform[] { BundleTransformerContext.Current.Styles.GetDefaultTransformInstance() }) { Builder = new NullBuilder(); } public override BundleResponse GenerateBundleResponse(BundleContext context) { - // This is overridden, because BudleTransformer adds LESS @imports + // This is overridden, because BudleTransformer adds LESS/SASS @imports // to the Bundle.Files collection. This is bad as we allow switching // Optimization mode per UI. Switching from true to false would also include - // ALL LESS imports in the generated output ('link' tags) + // ALL LESS/SASS imports in the generated output ('link' tags) - // get all ORIGINAL bundle parts (including LESS parents, no @imports) + // get all ORIGINAL bundle parts (including LESS/SASS parents, no @imports) var files = this.EnumerateFiles(context); // replace file pattern like {version} and let Bundler resolve min/debug extensions. @@ -41,7 +41,7 @@ public override BundleResponse GenerateBundleResponse(BundleContext context) _originalBundleFilePathes = files.Select(x => x.IncludedVirtualPath.TrimStart('~')).ToArray(); var response = base.GenerateBundleResponse(context); - // at this stage, BundleTransformer pushed ALL LESS @imports to Bundle.Files, which is bad... + // at this stage, BundleTransformer pushed ALL LESS/SASS @imports to Bundle.Files, which is bad... _transformedBundleFiles = response.Files; @@ -60,8 +60,10 @@ public override void UpdateCache(BundleContext context, BundleResponse response) { response.Files = _transformedBundleFiles; } - // update cache WITH LESS @imports, because they need to be monitored for cache invalidation + + // update cache WITH Sass/Less @imports, because they need to be monitored for cache invalidation base.UpdateCache(context, response); + // now clean. @imports are not needed anymore CleanBundleFiles(response); @@ -81,16 +83,15 @@ private void CleanBundleFiles(BundleResponse response) let virtualPath = file.IncludedVirtualPath where _originalBundleFilePathes.Any(x => virtualPath.EndsWith(x, StringComparison.OrdinalIgnoreCase)) select file; + response.Files = cleanFiles; } private static bool CacheIsEnabled(BundleContext context) { - if (context == null) - { - throw new ArgumentNullException("context"); - } - return (((context.HttpContext != null) && (context.HttpContext.Cache != null)) && !context.EnableInstrumentation); + Guard.NotNull(context, nameof(context)); + + return context.HttpContext?.Cache != null && !context.EnableInstrumentation; } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs b/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs new file mode 100644 index 0000000000..e2fd7aa151 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs @@ -0,0 +1,135 @@ +using System; +using System.Web; +using System.Web.Caching; +using BundleTransformer.Core; +using BundleTransformer.Core.Assets; +using BundleTransformer.Core.Configuration; +using BundleTransformer.Core.FileSystem; +using BundleTransformer.Core.Transformers; +using SmartStore.Collections; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Themes; +using SmartStore.Web.Framework.Theming.Assets; + +namespace SmartStore.Web.Framework.Theming +{ + public class SassCssHttpHandler : CssHttpHandlerBase + { + protected override IAsset TranslateAssetCore(IAsset asset, ITransformer transformer, bool isDebugMode) + { + return InnerTranslateAsset("SassTranslator", asset, transformer, isDebugMode); + } + } + + public abstract class CssHttpHandlerBase : BundleTransformer.Core.HttpHandlers.StyleAssetHandlerBase + { + protected CssHttpHandlerBase() + : this(HttpContext.Current.Cache, + BundleTransformerContext.Current.FileSystem.GetVirtualFileSystemWrapper(), + BundleTransformerContext.Current.Configuration.GetCoreSettings().AssetHandler) + { + } + + protected CssHttpHandlerBase( + Cache cache, + IVirtualFileSystemWrapper virtualFileSystemWrapper, + AssetHandlerSettings assetHandlerConfig) + : base(cache, virtualFileSystemWrapper, assetHandlerConfig) + { + } + + protected override bool IsStaticAsset + { + get { return false; } + } + + private bool IsThemeableRequest() + { + if (!DataSettings.DatabaseIsInstalled()) + { + return false; + } + else + { + var webHelper = EngineContext.Current.Resolve(); + + var requestUrl = webHelper.GetThisPageUrl(false); + string themeUrl = string.Format("{0}themes", webHelper.GetStoreLocation()); + var isThemeableRequest = requestUrl.StartsWith(themeUrl + "/", StringComparison.InvariantCultureIgnoreCase); + + return isThemeableRequest; + } + } + + protected override string GetCacheKey(string assetVirtualPath, string bundleVirtualPath) + { + string cacheKey = base.GetCacheKey(assetVirtualPath, bundleVirtualPath); + + if (IsThemeableRequest()) + { + if (HttpContext.Current?.Request != null) + { + var qs = QueryString.Current; + if (qs.Count > 0) + { + // required for Theme editing validation: See Admin.Controllers.ThemeController.ValidateLess() + if (qs.Contains("theme")) + { + EngineContext.Current.Resolve().SetRequestTheme(qs["theme"]); + } + if (qs.Contains("storeId")) + { + EngineContext.Current.Resolve().SetRequestStore(qs["storeId"].ToInt()); + } + } + } + + cacheKey += "_" + EngineContext.Current.Resolve().CurrentTheme.ThemeName + "_" + EngineContext.Current.Resolve().CurrentStore.Id; + + if (ThemeHelper.IsStyleValidationRequest()) + { + cacheKey += "_Validation"; + } + } + + return cacheKey; + } + + protected override IAsset TranslateAsset(IAsset asset, ITransformer transformer, bool isDebugMode) + { + bool validationMode = ThemeHelper.IsStyleValidationRequest(); + + try + { + var processedAsset = TranslateAssetCore(asset, transformer, validationMode || isDebugMode); + + if (transformer == null && !validationMode) + { + // BundleTransformer does NOT PostProcess when transformer instance is null, + // therefore we handle it ourselves, because we desperately need + // UrlRewrite even in debug mode. + return AssetTranslorUtil.PostProcessAsset(processedAsset, isDebugMode); + } + else + { + return processedAsset; + } + } + catch (Exception ex) + { + if (validationMode) + { + _context.Response.Write(ex.Message); + _context.Response.StatusCode = 500; + _context.Response.End(); + } + + throw; + } + } + + protected abstract IAsset TranslateAssetCore(IAsset asset, ITransformer transformer, bool isDebugMode); + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs b/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs new file mode 100644 index 0000000000..dbbc5a5ab4 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs @@ -0,0 +1,309 @@ +using System; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Hosting; +using System.Collections.Generic; +using System.Threading; +using SmartStore.Utilities.Threading; +using SmartStore.Core; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Themes; + +namespace SmartStore.Web.Framework.Theming +{ + public class DefaultThemeFileResolver : DisposableObject, IThemeFileResolver + { + private readonly Dictionary _files = new Dictionary(); + private readonly IThemeRegistry _themeRegistry; + private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); + + public DefaultThemeFileResolver(IThemeRegistry themeRegistry) + { + _themeRegistry = themeRegistry; + + // listen to file monitoring events + _themeRegistry.ThemeFolderDeleted += OnThemeFolderDeleted; + _themeRegistry.ThemeFolderRenamed += OnThemeFolderRenamed; + _themeRegistry.BaseThemeChanged += OnBaseThemeChanged; + _themeRegistry.ThemeFileChanged += OnThemeFileChanged; + } + + private void OnThemeFolderDeleted(object sender, ThemeFolderDeletedEventArgs e) + { + OnThemeRemoved(e.Name); + } + + private void OnThemeFolderRenamed(object sender, ThemeFolderRenamedEventArgs e) + { + OnThemeRemoved(e.OldName); + } + + private void OnBaseThemeChanged(object sender, BaseThemeChangedEventArgs e) + { + // We should be smarter than just clearing the whole cache here. BUT: + // Changing the base theme is a very rare case, whereas determining all dependant files is rather sophisticated. + // So, who cares ;-) + _files.Clear(); + } + + private void OnThemeRemoved(string themeName) + { + using (_rwLock.GetWriteLock()) + { + var keys = _files.Keys.Where(x => x.ThemeName.IsCaseInsensitiveEqual(themeName)).ToList(); + foreach (var key in keys) + { + if (key.ThemeName.IsCaseInsensitiveEqual(themeName) || _themeRegistry.IsChildThemeOf(key.ThemeName, themeName)) + { + // remove all cached pathes for this theme (also in all derived themes) + _files.Remove(key); + } + } + } + } + + private void OnThemeFileChanged(object sender, ThemeFileChangedEventArgs e) + { + if (e.IsConfigurationFile) + return; + + using (_rwLock.GetWriteLock()) + { + // get keys with same relative path + var keys = _files.Keys.Where(x => x.RelativePath.IsCaseInsensitiveEqual(e.RelativePath)).ToList(); + foreach (var key in keys) + { + if (key.ThemeName.IsCaseInsensitiveEqual(e.ThemeName) || _themeRegistry.IsChildThemeOf(key.ThemeName, e.ThemeName)) + { + // remove all cached pathes for this file/theme combination (also in all derived themes) + InheritedThemeFileResult result; + if (_files.TryGetValue(key, out result)) + { + _files.Remove(key); + if (e.ChangeType == ThemeFileChangeType.Created) + { + // The file is new: no chance that our VPP dependencies + // could have been notified about this (only deletions/changes are monitored). + // Therefore we brutally set the result file's last write time + // to enforece VPP to invalidate cache. + try + { + File.SetLastWriteTimeUtc(result.ResultPhysicalPath, DateTime.UtcNow); + } + catch { } + } + } + } + } + } + } + + protected override void OnDispose(bool disposing) + { + if (disposing) + { + _themeRegistry.ThemeFileChanged -= OnThemeFileChanged; + _themeRegistry.ThemeFolderDeleted -= OnThemeFolderDeleted; + _themeRegistry.BaseThemeChanged -= OnBaseThemeChanged; + _themeRegistry.ThemeFolderRenamed -= OnThemeFolderRenamed; + } + } + + /// + /// Tries to resolve a file up in the current theme's hierarchy chain. + /// + /// The original virtual path of the theme file + /// + /// If the current working themme is based on another theme AND the requested file + /// was physically found in the theme's hierarchy chain, an instance of will be returned. + /// In any other case the return value is null. + /// + public InheritedThemeFileResult Resolve(string virtualPath) + { + Guard.NotEmpty(virtualPath, nameof(virtualPath)); + + if (virtualPath[0] != '~') + { + virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); + } + + if (!ThemeHelper.PathIsInheritableThemeFile(virtualPath)) + { + return null; + } + + bool isBased = false; + + virtualPath = ThemeHelper.TokenizePath(virtualPath, out var requestedThemeName, out var relativePath, out var query); + + Func nullOrFile = () => + { + return isBased + ? new InheritedThemeFileResult { IsBased = true, OriginalVirtualPath = virtualPath, Query = query } + : null; + }; + + ThemeManifest currentTheme = ResolveTheme(requestedThemeName, relativePath, query, out isBased); + + if (currentTheme?.BaseTheme == null) + { + // dont't bother resolving files: the current theme is not inherited. + // Let the current VPP do the work. + return nullOrFile(); + } + + if (!currentTheme.ThemeName.Equals(requestedThemeName, StringComparison.OrdinalIgnoreCase)) + { + if (!_themeRegistry.IsChildThemeOf(currentTheme.ThemeName, requestedThemeName)) + { + return nullOrFile(); + } + } + else if (isBased && currentTheme.BaseTheme != null) + { + // A file from the base theme has been requested + currentTheme = currentTheme.BaseTheme; + } + + var fileKey = new FileKey(currentTheme.ThemeName, relativePath, query); + InheritedThemeFileResult result; + + using (_rwLock.GetUpgradeableReadLock()) + { + if (!_files.TryGetValue(fileKey, out result)) + { + using (_rwLock.GetWriteLock()) + { + // ALWAYS begin the search with the current working theme's location! + string actualLocation = LocateFile(currentTheme.ThemeName, relativePath, out var resultVirtualPath, out var resultPhysicalPath); + + if (actualLocation != null) + { + result = new InheritedThemeFileResult + { + RelativePath = relativePath, + OriginalVirtualPath = virtualPath, + ResultVirtualPath = resultVirtualPath, + ResultPhysicalPath = resultPhysicalPath, + OriginalThemeName = requestedThemeName, + ResultThemeName = actualLocation, + IsBased = isBased, + Query = query + }; + } + + _files[fileKey] = result; + } + } + } + + if (result == null) + return nullOrFile(); + + return result; + } + + private ThemeManifest ResolveTheme(string requestedThemeName, string relativePath, string query, out bool isBased) + { + isBased = false; + ThemeManifest currentTheme; + + var isAdmin = EngineContext.Current.Resolve().IsAdmin; + if (isAdmin) + { + currentTheme = _themeRegistry.GetThemeManifest(requestedThemeName); + } + else + { + var styleResult = ThemeHelper.IsStyleSheet(relativePath); + var isPreprocessor = styleResult != null && styleResult.IsPreprocessor; + if (isPreprocessor) + { + // special consideration for SASS/LESS files: they can be validated + // in the backend. For validation, a "theme" query is appended + // to the url. During validation we must work with the actual + // requested theme instead of dynamically resolving the working theme. + var httpContext = HttpContext.Current; + if (httpContext != null && httpContext.Request != null) + { + var qs = httpContext.Request.QueryString; + if (qs["theme"].HasValue()) + { + EngineContext.Current.Resolve().SetRequestTheme(qs["theme"]); + } + } + } + + currentTheme = ThemeHelper.ResolveCurrentTheme(); + + if (isPreprocessor && query != null && query.StartsWith("base", StringComparison.OrdinalIgnoreCase)) + { + // special case to support SASS @import declarations + // within inherited SASS files. Snenario: an inheritor wishes to + // include the same file from it's base theme (e.g. custom.scss) just to tweak it + // a bit for his child theme. Without the 'base' query the resolution starting point + // for custom.scss would be the CURRENT theme's folder, and NOT the requested one's, + // which inevitably would result in a cyclic dependency. + currentTheme = _themeRegistry.GetThemeManifest(requestedThemeName); + isBased = true; + } + } + + return currentTheme; + } + + /// + /// Tries to locate the file + /// + /// the theme's name where the file is actually located + private string LocateFile(string themeName, string relativePath, out string virtualPath, out string physicalPath) + { + virtualPath = null; + physicalPath = null; + + virtualPath = VirtualPathUtility.Combine("{0}{1}/".FormatInvariant(ThemeHelper.ThemesBasePath, themeName), relativePath); + physicalPath = HostingEnvironment.MapPath(virtualPath); + if (File.Exists(physicalPath)) + { + return themeName; + } + + var manifest = _themeRegistry.GetThemeManifest(themeName); + if (manifest != null && manifest.BaseTheme != null) + { + var baseLocation = LocateFile(manifest.BaseTheme.ThemeName, relativePath, out virtualPath, out physicalPath); + return baseLocation; + } + + virtualPath = null; + physicalPath = null; + return null; + } + + + private class FileKey : Tuple + { + public FileKey(string themeName, string relativePath, string query) + : base(themeName.ToLower(), relativePath.ToLower(), query?.ToLower()) + { + } + + public string ThemeName + { + get { return base.Item1; } + } + + public string RelativePath + { + get { return base.Item2; } + } + + public string Query + { + get { return base.Item3; } + } + } + + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/IFileDependencyProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/IFileDependencyProvider.cs new file mode 100644 index 0000000000..e36f9c9fd4 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/IFileDependencyProvider.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Web.Framework.Theming +{ + internal interface IFileDependencyProvider + { + void AddFileDependencies(ICollection mappedPaths, ICollection cacheKeys); + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs b/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs index a3101627bb..ad49bed1d2 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs @@ -1,22 +1,29 @@ using System; +using System.Collections.Generic; using System.IO; using System.Web.Hosting; +using SmartStore.Core.Themes; namespace SmartStore.Web.Framework.Theming { - internal class InheritedVirtualThemeFile : VirtualFile + internal class InheritedVirtualThemeFile : VirtualFile, IFileDependencyProvider { - private readonly InheritedThemeFileResult _resolveResult; - public InheritedVirtualThemeFile(InheritedThemeFileResult resolveResult) : base(DetermineVirtualPath(resolveResult)) { - this._resolveResult = resolveResult; + ResolveResult = resolveResult; } - public override Stream Open() + public InheritedThemeFileResult ResolveResult { get; } + + public string ResultVirtualPath + { + get { return ResolveResult.ResultVirtualPath ?? ResolveResult.OriginalVirtualPath; } + } + + public override Stream Open() { - return new FileStream(_resolveResult.ResultPhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return new FileStream(ResolveResult.ResultPhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } private static string DetermineVirtualPath(InheritedThemeFileResult resolveResult) @@ -33,5 +40,9 @@ private static string DetermineVirtualPath(InheritedThemeFileResult resolveResul } } - } + public void AddFileDependencies(ICollection mappedPaths, ICollection cacheKeys) + { + mappedPaths.Add(ResolveResult.ResultPhysicalPath); + } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/LessCssHttpHandler.cs b/src/Presentation/SmartStore.Web.Framework/Theming/LessCssHttpHandler.cs deleted file mode 100644 index efc3d31a41..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Theming/LessCssHttpHandler.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Web; -using System.Web.Caching; -using BundleTransformer.Core; -using BundleTransformer.Core.Configuration; -using BundleTransformer.Core.FileSystem; -using SmartStore.Collections; -using SmartStore.Core; -using SmartStore.Core.Data; -using SmartStore.Core.Infrastructure; - -namespace SmartStore.Web.Framework.Theming -{ - public class LessCssHttpHandler : BundleTransformer.Less.HttpHandlers.LessAssetHandlerBase - { - - public LessCssHttpHandler() - : this(HttpContext.Current.Cache, - BundleTransformerContext.Current.GetVirtualFileSystemWrapper(), - BundleTransformerContext.Current.GetCoreConfiguration().AssetHandler) - { } - - public LessCssHttpHandler( - Cache cache, - IVirtualFileSystemWrapper virtualFileSystemWrapper, - AssetHandlerSettings assetHandlerConfig) - : base(cache, virtualFileSystemWrapper, assetHandlerConfig) - { - } - - private bool IsThemeableRequest() - { - if (!DataSettings.DatabaseIsInstalled()) - { - return false; - } - else - { - var webHelper = EngineContext.Current.Resolve(); - - var requestUrl = webHelper.GetThisPageUrl(false); - string themeUrl = string.Format("{0}themes", webHelper.GetStoreLocation()); - var isThemeableRequest = requestUrl.StartsWith(themeUrl + "/", StringComparison.InvariantCultureIgnoreCase); - - return isThemeableRequest; - } - } - - public override string GetCacheKey(string assetUrl) - { - string cacheKey = base.GetCacheKey(assetUrl); - - if (IsThemeableRequest()) - { - var httpContext = HttpContext.Current; - if (httpContext != null && httpContext.Request != null) - { - var qs = QueryString.Current; - if (qs.Count > 0) - { - // required for Theme editing validation: See Admin.Controllers.ThemeController.ValidateLess() - if (qs.Contains("theme")) - { - EngineContext.Current.Resolve().SetRequestTheme(qs["theme"]); - } - if (qs.Contains("storeId")) - { - EngineContext.Current.Resolve().SetRequestStore(qs["storeId"].ToInt()); - } - } - } - - cacheKey += "_" + EngineContext.Current.Resolve().CurrentTheme.ThemeName + "_" + EngineContext.Current.Resolve().CurrentStore.Id; - } - - return cacheKey; - } - - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs new file mode 100644 index 0000000000..32c6d31fe8 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Web; +using System.Web.Hosting; +using System.Threading; +using SmartStore.Core.Infrastructure; +using SmartStore.Utilities.Threading; +using SmartStore.Utilities; +using SmartStore.Core.Themes; + +namespace SmartStore.Web.Framework.Theming +{ + public abstract class SmartVirtualPathProvider : VirtualPathProvider + { + private readonly IThemeRegistry _themeRegistry; + private readonly Dictionary _cachedDebugFilePaths = new Dictionary(); + private readonly ContextState> _requestState = new ContextState>("PluginDebugViewVPP.RequestCache", () => new Dictionary()); + private readonly DirectoryInfo _pluginsDebugDir; + private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); + + protected static readonly bool _isDebug; + + static SmartVirtualPathProvider() + { + _isDebug = HttpContext.Current.IsDebuggingEnabled && CommonHelper.IsDevEnvironment; + } + + protected SmartVirtualPathProvider() + { + var appRootPath = HostingEnvironment.MapPath("~/").EnsureEndsWith("\\"); + + var pluginsDebugPath = Path.GetFullPath(Path.Combine(appRootPath, @"..\..\Plugins")); + if (Directory.Exists(pluginsDebugPath)) + { + _pluginsDebugDir = new DirectoryInfo(pluginsDebugPath); + } + + _themeRegistry = EngineContext.Current.Resolve(); + } + + protected internal string ResolveDebugFilePath(string virtualPath) + { + if (!_isDebug) + return null; + + // Two-Level caching: RequestCache > AppCache + var d = _requestState.GetState(); + + if (!d.TryGetValue(virtualPath, out var debugPath)) + { + if (!IsExtensionPath(virtualPath, out var root, out var appRelativePath)) + { + // don't query again in this request + d[virtualPath] = null; + return null; + } + + if (!d.TryGetValue(appRelativePath, out debugPath)) + { + // (perf) concurrency with ReaderWriterLockSlim seems way faster than ConcurrentDictionary + using (_rwLock.GetUpgradeableReadLock()) + { + if (!_cachedDebugFilePaths.TryGetValue(appRelativePath, out debugPath)) + { + using (_rwLock.GetWriteLock()) + { + debugPath = FindDebugFile(appRelativePath, root); + _cachedDebugFilePaths[appRelativePath] = d[appRelativePath] = debugPath; + } + } + } + } + } + + return debugPath; + } + + private string FindDebugFile(string appRelativePath, string root) + { + if (_pluginsDebugDir == null) + return null; + + // strip "~/Plugins/" or "~/Themes/" + var unrooted = appRelativePath.Substring(root.Length); + + // either plugin or theme name + var extensionName = unrooted.Substring(0, unrooted.IndexOf('/')); + + // get "Views/Something/View.cshtml" + var relativePath = unrooted.Substring(extensionName.Length + 1); + + if (root == "~/Themes/") + { + var theme = _themeRegistry.GetThemeManifest(extensionName); + if (theme != null && theme.IsSymbolicLink) + { + // Linked theme folders cannot compute cache dependencies correctly when + // working with source paths. We must determine the link target path, + var finalPath = Path.Combine(theme.Path, relativePath.Replace('/', '\\')); + return File.Exists(finalPath) ? finalPath : null; + } + } + else + { + // Root is "~/Plugin/" + var foldersToCheck = new[] { extensionName, extensionName + "-sym" }; + + foreach (var folder in foldersToCheck) + { + var pluginDir = new DirectoryInfo(Path.Combine(_pluginsDebugDir.FullName, folder)); + if (pluginDir != null && pluginDir.Exists) + { + var result = Path.Combine(pluginDir.FullName, relativePath).Replace("/", "\\"); + return File.Exists(result) ? result : null; + } + } + } + + return null; + } + + private bool IsExtensionPath(string virtualPath, out string root, out string appRelativePath) + { + root = null; + appRelativePath = virtualPath; + + if (virtualPath != null && virtualPath.Length > 0 && virtualPath[0] != '~') + { + appRelativePath = VirtualPathUtility.ToAppRelative(virtualPath); + } + + if (appRelativePath.StartsWith("~/Plugins/", StringComparison.InvariantCultureIgnoreCase)) + { + root = "~/Plugins/"; + return true; + } + + if (appRelativePath.StartsWith("~/Themes/", StringComparison.InvariantCultureIgnoreCase)) + { + root = "~/Themes/"; + return true; + } + + return false; + } + } + + internal class DebugVirtualFile : VirtualFile, IFileDependencyProvider + { + public DebugVirtualFile(string virtualPath, string debugPath) + : base(virtualPath) + { + this.PhysicalPath = debugPath; + } + + public string PhysicalPath { get; } + + public override bool IsDirectory + { + get { return false; } + } + + public override Stream Open() + { + var fileStream = new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return fileStream; + } + + public void AddFileDependencies(ICollection mappedPaths, ICollection cacheKeys) + { + mappedPaths.Add(PhysicalPath); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeContext.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeContext.cs index 77f8b4124d..ce5b268dbf 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeContext.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeContext.cs @@ -8,9 +8,6 @@ namespace SmartStore.Web.Framework.Theming { - /// - /// Theme context - /// public partial class ThemeContext : IThemeContext { internal const string OverriddenThemeNameKey = "OverriddenThemeName"; @@ -23,11 +20,9 @@ public partial class ThemeContext : IThemeContext private readonly IMobileDeviceHelper _mobileDeviceHelper; private readonly HttpContextBase _httpContext; - private bool _desktopThemeIsCached; - private string _cachedDesktopThemeName; + private bool _themeIsCached; + private string _cachedThemeName; - private bool _mobileThemeIsCached; - private string _cachedMobileThemeName; private ThemeManifest _currentTheme; public ThemeContext( @@ -48,16 +43,13 @@ public ThemeContext( this._httpContext = httpContext; } - /// - /// Get or set current theme for desktops (e.g. Alpha) - /// - public string WorkingDesktopTheme + public string WorkingThemeName { get { - if (_desktopThemeIsCached) + if (_themeIsCached) { - return _cachedDesktopThemeName; + return _cachedThemeName; } var customer = _workContext.CurrentCustomer; @@ -73,7 +65,7 @@ public string WorkingDesktopTheme { if (customer != null) { - theme = customer.GetAttribute(SystemCustomerAttributeNames.WorkingDesktopThemeName, _genericAttributeService, _storeContext.CurrentStore.Id); + theme = customer.GetAttribute(SystemCustomerAttributeNames.WorkingThemeName, _genericAttributeService, _storeContext.CurrentStore.Id); } } @@ -83,17 +75,17 @@ public string WorkingDesktopTheme // default store theme if (string.IsNullOrEmpty(theme)) { - theme = _themeSettings.DefaultDesktopTheme; + theme = _themeSettings.DefaultTheme; } // ensure that theme exists if (!_themeRegistry.ThemeManifestExists(theme)) { - var manifest = _themeRegistry.GetThemeManifests().Where(x => !x.MobileTheme).FirstOrDefault(); + var manifest = _themeRegistry.GetThemeManifests().FirstOrDefault(); if (manifest == null) { // no active theme in system. Throw! - throw Error.Application("At least one desktop theme must be in active state, but the theme registry does not contain a valid theme package."); + throw Error.Application("At least one theme must be in active state, but the theme registry does not contain a valid theme package."); } theme = manifest.ThemeName; if (isUserSpecific) @@ -102,14 +94,14 @@ public string WorkingDesktopTheme _httpContext.SetUserThemeChoiceInCookie(null); if (customer != null) { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.WorkingDesktopThemeName, string.Empty, _storeContext.CurrentStore.Id); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.WorkingThemeName, string.Empty, _storeContext.CurrentStore.Id); } } } // cache theme - this._cachedDesktopThemeName = theme; - this._desktopThemeIsCached = true; + this._cachedThemeName = theme; + this._themeIsCached = true; return theme; } set @@ -124,38 +116,11 @@ public string WorkingDesktopTheme if (_workContext.CurrentCustomer != null) { - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.WorkingDesktopThemeName, value.EmptyNull(), _storeContext.CurrentStore.Id); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.WorkingThemeName, value.EmptyNull(), _storeContext.CurrentStore.Id); } // clear cache - this._desktopThemeIsCached = false; - } - } - - /// - /// Get current theme for mobile (e.g. Mobile) - /// - public string WorkingMobileTheme - { - get - { - if (_mobileThemeIsCached) - return _cachedMobileThemeName; - - // default store theme - string theme = _themeSettings.DefaultMobileTheme; - - // ensure that theme exists - if (!_themeRegistry.ThemeManifestExists(theme)) - theme = _themeRegistry.GetThemeManifests() - .Where(x => x.MobileTheme) - .FirstOrDefault() - .ThemeName; - - // cache theme - this._cachedMobileThemeName = theme; - this._mobileThemeIsCached = true; - return theme; + this._themeIsCached = false; } } @@ -182,7 +147,7 @@ public string GetRequestTheme() { try { - return (string)_httpContext.Request.RequestContext.RouteData.DataTokens[OverriddenThemeNameKey]; + return (string)_httpContext.Request?.RequestContext?.RouteData?.DataTokens[OverriddenThemeNameKey]; } catch { @@ -232,18 +197,7 @@ public ThemeManifest CurrentTheme } else { - bool useMobileDevice = _mobileDeviceHelper.IsMobileDevice() - && _mobileDeviceHelper.MobileDevicesSupported() - && !_mobileDeviceHelper.CustomerDontUseMobileVersion(); - - if (useMobileDevice) - { - _currentTheme = _themeRegistry.GetThemeManifest(this.WorkingMobileTheme); - } - else - { - _currentTheme = _themeRegistry.GetThemeManifest(this.WorkingDesktopTheme); - } + _currentTheme = _themeRegistry.GetThemeManifest(this.WorkingThemeName); } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeFileResolver.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeFileResolver.cs deleted file mode 100644 index b4599a4de3..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeFileResolver.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Linq; -using System.Web; -using System.Web.Hosting; -using SmartStore.Core; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Themes; - -namespace SmartStore.Web.Framework.Theming -{ - public interface IThemeFileResolver - { - InheritedThemeFileResult Resolve(string virtualPath); - } - - public class ThemeFileResolver : DisposableObject, IThemeFileResolver - { - private readonly ConcurrentDictionary _files = new ConcurrentDictionary(); - private readonly IThemeRegistry _themeRegistry; - - public ThemeFileResolver(IThemeRegistry themeRegistry) - { - this._themeRegistry = themeRegistry; - - // listen to file monitoring events - this._themeRegistry.ThemeFolderDeleted += OnThemeFolderDeleted; - this._themeRegistry.ThemeFolderRenamed += OnThemeFolderRenamed; - this._themeRegistry.BaseThemeChanged += OnBaseThemeChanged; - this._themeRegistry.ThemeFileChanged += OnThemeFileChanged; - } - - private void OnThemeFolderDeleted(object sender, ThemeFolderDeletedEventArgs e) - { - OnThemeRemoved(e.Name); - } - - private void OnThemeFolderRenamed(object sender, ThemeFolderRenamedEventArgs e) - { - OnThemeRemoved(e.OldName); - } - - private void OnBaseThemeChanged(object sender, BaseThemeChangedEventArgs e) - { - // We should be smarter than just clearing the whole cache here. BUT: - // Changing the base theme is a very rare case, whereas determining all dependant files is rather sophisticated. - // So, who cares ;-) - _files.Clear(); - } - - private void OnThemeRemoved(string themeName) - { - var keys = _files.Keys.Where(x => x.ThemeName.IsCaseInsensitiveEqual(themeName)).ToList(); - foreach (var key in keys) - { - if (key.ThemeName.IsCaseInsensitiveEqual(themeName) || _themeRegistry.IsChildThemeOf(key.ThemeName, themeName)) - { - // remove all cached pathes for this theme (also in all derived themes) - InheritedThemeFileResult result; - _files.TryRemove(key, out result); - } - } - } - - private void OnThemeFileChanged(object sender, ThemeFileChangedEventArgs e) - { - if (e.IsConfigurationFile) - return; - - // get keys with same relative path - var keys = _files.Keys.Where(x => x.RelativePath.IsCaseInsensitiveEqual(e.RelativePath)).ToList(); - foreach (var key in keys) - { - if (key.ThemeName.IsCaseInsensitiveEqual(e.ThemeName) || _themeRegistry.IsChildThemeOf(key.ThemeName, e.ThemeName)) - { - // remove all cached pathes for this file/theme combination (also in all derived themes) - InheritedThemeFileResult result; - if (_files.TryRemove(key, out result)) - { - if (e.ChangeType == ThemeFileChangeType.Created) - { - // The file is new: no chance that our VPP dependencies - // could have been notified about this (only deletions/changes are monitored). - // Therefore we brutally set the result file's last write time - // to enforece VPP to invalidate cache. - try - { - File.SetLastWriteTimeUtc(result.ResultPhysicalPath, DateTime.UtcNow); - } - catch { } - } - } - } - } - - } - - protected override void OnDispose(bool disposing) - { - if (disposing) - { - this._themeRegistry.ThemeFileChanged -= OnThemeFileChanged; - this._themeRegistry.ThemeFolderDeleted -= OnThemeFolderDeleted; - this._themeRegistry.BaseThemeChanged -= OnBaseThemeChanged; - this._themeRegistry.ThemeFolderRenamed -= OnThemeFolderRenamed; - } - } - - /// - /// Tries to resolve a file up in the current theme's hierarchy chain. - /// - /// The original virtual path of the theme file - /// - /// If the current working themme is based on another theme AND the requested file - /// was physically found in the theme's hierarchy chain, an instance of will be returned. - /// In any other case the return value is null. - /// - public InheritedThemeFileResult Resolve(string virtualPath) - { - Guard.ArgumentNotEmpty(() => virtualPath); - - virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); - - if (!ThemeHelper.PathIsInheritableThemeFile(virtualPath)) - { - return null; - } - - bool isExplicit = false; - - string requestedThemeName; - string relativePath; - string query; - - virtualPath = TokenizePath(virtualPath, out requestedThemeName, out relativePath, out query); - - Func nullOrFile = () => - { - if (isExplicit) - { - return new InheritedThemeFileResult { IsExplicit = true, OriginalVirtualPath = virtualPath }; - } - return null; - }; - - ThemeManifest currentTheme; - var isAdmin = EngineContext.Current.Resolve().IsAdmin; // ThemeHelper.IsAdminArea() - if (isAdmin) - { - currentTheme = _themeRegistry.GetThemeManifest(requestedThemeName); - } - else - { - bool isLess; - bool isBundle; - if (ThemeHelper.IsStyleSheet(relativePath, out isLess, out isBundle) && isLess) - { - // special consideration for LESS files: they can be validated - // in the backend. For validation, a "theme" query is appended - // to the url. During validation we must work with the actual - // requested theme instead dynamically resolving the working theme. - var httpContext = HttpContext.Current; - if (httpContext != null && httpContext.Request != null) - { - var qs = httpContext.Request.QueryString; - if (qs["theme"].HasValue()) - { - EngineContext.Current.Resolve().SetRequestTheme(qs["theme"]); - } - } - } - - if (isLess && query != null && query.StartsWith("explicit", StringComparison.OrdinalIgnoreCase)) - { - // special case to support LESS @import declarations - // within inherited LESS files. Snenario: an inheritor wishes to - // include the same file from it's base theme (e.g. custom.less) just to tweak it - // a bit for his child theme. Without the 'explicit' query the resolution starting point - // for custom.less would be the CURRENT theme's folder, and NOT the requested one's, - // which inevitably would result in a cyclic dependency. - currentTheme = _themeRegistry.GetThemeManifest(requestedThemeName); - isExplicit = true; - } - else - { - currentTheme = ThemeHelper.ResolveCurrentTheme(); - } - - if (currentTheme.BaseTheme == null) - { - // dont't bother resolving files: the current theme is not inherited. - // Let the current VPP do the work. - return nullOrFile(); - } - } - - if (!currentTheme.ThemeName.Equals(requestedThemeName, StringComparison.OrdinalIgnoreCase)) - { - if (!_themeRegistry.IsChildThemeOf(currentTheme.ThemeName, requestedThemeName)) - { - return nullOrFile(); - } - } - - var fileKey = new FileKey(currentTheme.ThemeName, relativePath); - - var result = _files.GetOrAdd(fileKey, (k) => - { - // ALWAYS begin the search with the current working theme's location! - string resultVirtualPath; - string resultPhysicalPath; - string actualLocation = LocateFile(currentTheme.ThemeName, relativePath, out resultVirtualPath, out resultPhysicalPath); - - if (actualLocation != null) - { - return new InheritedThemeFileResult - { - RelativePath = relativePath, - OriginalVirtualPath = virtualPath, - ResultVirtualPath = resultVirtualPath, - ResultPhysicalPath = resultPhysicalPath, - OriginalThemeName = requestedThemeName, - ResultThemeName = actualLocation - }; - } - - return null; - }); - - if (result == null) - { - return nullOrFile(); - } - - return result; - } - - private string TokenizePath(string virtualPath, out string themeName, out string relativePath, out string query) - { - themeName = null; - relativePath = null; - query = null; - - var unrooted = virtualPath.Substring(ThemeHelper.ThemesBasePath.Length); // strip "~/Themes/" - themeName = unrooted.Substring(0, unrooted.IndexOf('/')); - relativePath = unrooted.Substring(themeName.Length + 1); - - var idx = relativePath.IndexOf('?'); - if (idx > 0) - { - query = relativePath.Substring(idx + 1); - relativePath = relativePath.Substring(0, idx); - } - - // strip out query - return "{0}{1}/{2}".FormatCurrent(ThemeHelper.ThemesBasePath, themeName, relativePath); - } - - /// - /// Tries to locate the file - /// - /// the theme's name where the file is actually located - private string LocateFile(string themeName, string relativePath, out string virtualPath, out string physicalPath) - { - virtualPath = null; - physicalPath = null; - - virtualPath = VirtualPathUtility.Combine("{0}{1}/".FormatInvariant(ThemeHelper.ThemesBasePath, themeName), relativePath); - physicalPath = HostingEnvironment.MapPath(virtualPath); - if (File.Exists(physicalPath)) - { - return themeName; - } - - var manifest = _themeRegistry.GetThemeManifest(themeName); - if (manifest != null && manifest.BaseTheme != null) - { - var baseLocation = LocateFile(manifest.BaseTheme.ThemeName, relativePath, out virtualPath, out physicalPath); - return baseLocation; - } - - virtualPath = null; - physicalPath = null; - return null; - } - - - private class FileKey : Tuple - { - public FileKey(string themeName, string relativePath) - : base(themeName.ToLower(), relativePath.ToLower()) - { - } - - public string ThemeName - { - get { return base.Item1; } - } - - public string RelativePath - { - get { return base.Item2; } - } - } - - } - - public class InheritedThemeFileResult - { - /// - /// The unrooted relative path of the file (without ~/Themes/ThemeName/) - /// - public string RelativePath { get; set; } - - /// - /// The original virtual path - /// - public string OriginalVirtualPath { get; set; } - - /// - /// The result virtual path (the path in which the file is actually located) - /// - public string ResultVirtualPath { get; set; } - - /// - /// The result physical path (the path in which the file is actually located) - /// - public string ResultPhysicalPath { get; set; } - - /// - /// The name of the requesting theme - /// - public string OriginalThemeName { get; set; } - - /// - /// The name of the resulting theme where the file is actually located - /// - public string ResultThemeName { get; set; } - - internal bool IsExplicit { get; set; } - } - -} diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs index cf733a8f85..074b0e48cb 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Web; @@ -8,6 +9,7 @@ using SmartStore.Core.Infrastructure; using SmartStore.Core.Themes; using SmartStore.Utilities; +using SmartStore.Web.Framework.Theming.Assets; namespace SmartStore.Web.Framework.Theming { @@ -15,6 +17,7 @@ internal static class ThemeHelper { private static readonly Regex s_inheritableThemeFilePattern; private static readonly Regex s_themeVarsPattern; + private static readonly Regex s_moduleImportsPattern; private static readonly Regex s_extensionlessPathPattern; public static readonly string ThemesBasePath; @@ -23,38 +26,71 @@ static ThemeHelper() { ThemesBasePath = CommonHelper.GetAppSetting("sm:ThemesBasePath", "~/Themes/").EnsureEndsWith("/"); - var pattern = @"^{0}(.*)/(.+)(\.)(png|gif|jpg|jpeg|css|less|js|cshtml|svg|json)$".FormatInvariant(ThemesBasePath); + var pattern = @"^{0}(.*)/(.+)(\.)(png|gif|jpg|jpeg|css|scss|js|cshtml|svg|json|liquid)(\?base)*$".FormatInvariant(ThemesBasePath); s_inheritableThemeFilePattern = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - s_themeVarsPattern = new Regex(@"^~/\.db/themevars.less$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - s_extensionlessPathPattern = new Regex(@"/(.+)/([^/.]*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - + s_themeVarsPattern = new Regex(@"\.(db|app)/[_]?themevars(.scss)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + s_moduleImportsPattern = new Regex(@"\.app/[_]?moduleimports.scss", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + s_extensionlessPathPattern = new Regex(@"~/(.+)/([^/.]*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); } - internal static bool PathListContainsThemeVars(IEnumerable pathes) - { - Guard.ArgumentNotNull(() => pathes); + internal static IEnumerable RemoveVirtualImports(IEnumerable virtualPathDependencies) + { + Guard.NotNull(virtualPathDependencies, nameof(virtualPathDependencies)); - return pathes.Any(x => PathIsThemeVars(x)); - } + // determine the virtual themevars scss import reference + var themeVarsFile = virtualPathDependencies.Where(x => ThemeHelper.PathIsThemeVars(x)).FirstOrDefault(); + var moduleImportsFile = virtualPathDependencies.Where(x => ThemeHelper.PathIsModuleImports(x)).FirstOrDefault(); + + if (themeVarsFile == null && moduleImportsFile == null) + { + // no themevars or moduleimports import... so no special considerations here + return virtualPathDependencies; + } + + // exclude the special imports from the file dependencies list, + // 'cause this one cannot be monitored by the physical file system + return virtualPathDependencies + .Except((new string[] { themeVarsFile, moduleImportsFile }) + .Where(x => x.HasValue())) + .ToArray(); + } internal static bool PathIsThemeVars(string virtualPath) { - if (string.IsNullOrEmpty(virtualPath)) - return false; + string extension = null; + return PathIsThemeVars(virtualPath, out extension); + } - if (virtualPath[0] != '~') + internal static bool PathIsThemeVars(string virtualPath, out string extension) + { + extension = null; + + if (string.IsNullOrEmpty(virtualPath)) + return false; + + var match = s_themeVarsPattern.Match(virtualPath); + if (match.Success) { - virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); + extension = match.Groups[2].Value; + return true; } - return s_themeVarsPattern.IsMatch(virtualPath); - } + return false; + } - internal static bool PathIsInheritableThemeFile(string virtualPath) + internal static bool PathIsModuleImports(string virtualPath) { if (string.IsNullOrEmpty(virtualPath)) return false; + return s_moduleImportsPattern.IsMatch(virtualPath); + } + + internal static bool PathIsInheritableThemeFile(string virtualPath) + { + if (string.IsNullOrWhiteSpace(virtualPath)) + return false; + if (virtualPath[0] != '~') { virtualPath = VirtualPathUtility.ToAppRelative(virtualPath); @@ -73,32 +109,65 @@ internal static bool IsAdminArea() return false; } - internal static bool IsStyleSheet(string path, out bool isLess, out bool isBundle) + internal static bool IsStyleValidationRequest() + { + return HttpContext.Current?.Request?.QueryString["validate"] != null; + } + + internal static StyleSheetResult IsStyleSheet(string path) { - bool isCss = false; - isBundle = false; - isLess = path.EndsWith(".less", StringComparison.OrdinalIgnoreCase); + // Handle virtual Sass imports with '?base' query + // TBD: (mc) other query params could exist + var qindex = path.IndexOf('?'); + if (qindex > -1) + { + var pathWithoutQuery = path.Substring(0, qindex); + var query = path.Substring(pathWithoutQuery.Length); + if (query.StartsWith("?base", StringComparison.OrdinalIgnoreCase)) + { + // Process again, this time without query + var result = IsStyleSheet(pathWithoutQuery); + result.IsBaseImport = true; + return result; + } + } + + var extension = Path.GetExtension(path).ToLowerInvariant(); - if (!isLess) + if (extension == ".cshtml") + { + // Perf + return null; + } + else if (extension == ".css") { - isCss = path.EndsWith(".css", StringComparison.OrdinalIgnoreCase); - if (!isCss && s_extensionlessPathPattern.IsMatch(path)) + return new StyleSheetResult { Path = path, IsCss = true, Extension = extension }; + } + else if (extension == ".scss" || extension == ".sass") + { + return new StyleSheetResult { Path = path, IsSass = true, Extension = extension }; + } + else if (extension.IsEmpty()) + { + if (path.Contains("/scss/")) + { + // Bootstrap and other libaries may import SASS files without extension + return new StyleSheetResult { Path = path, IsSass = true }; + } + + // StyleBundles are extension-less, so we have to ask 'BundleTable' + // if a style bundle has been registered for the given path. + if (s_extensionlessPathPattern.IsMatch(path)) { - // StyleBundles are extension-less, so we have to ask 'BundleTable' - // if a style bundle has been registered for the given path. var bundle = BundleTable.Bundles.GetBundleFor(path); - if (bundle != null) + if (bundle != null && ((bundle is SmartStyleBundle || bundle is StyleBundle))) { - isBundle = true; - if (bundle is SmartStyleBundle || bundle is StyleBundle) - { - isCss = true; - } + return new StyleSheetResult { Path = path, IsBundle = true }; } } } - return isLess || isCss; + return null; } internal static ThemeManifest ResolveCurrentTheme() @@ -111,5 +180,50 @@ internal static int ResolveCurrentStoreId() return EngineContext.Current.Resolve().CurrentStore.Id; } - } + internal static string TokenizePath(string virtualPath, out string themeName, out string relativePath, out string query) + { + themeName = null; + relativePath = null; + query = null; + + var unrooted = virtualPath.Substring(ThemesBasePath.Length); // strip "~/Themes/" + themeName = unrooted.Substring(0, unrooted.IndexOf('/')); + relativePath = unrooted.Substring(themeName.Length + 1); + + var idx = relativePath.IndexOf('?'); + if (idx > 0) + { + query = relativePath.Substring(idx + 1); + relativePath = relativePath.Substring(0, idx); + } + + // strip out query + return "{0}{1}/{2}".FormatCurrent(ThemesBasePath, themeName, relativePath); + } + } + + internal class StyleSheetResult + { + public string Path { get; set; } + public string Extension { get; set; } + public bool IsCss { get; set; } + public bool IsSass { get; set; } + public bool IsBundle { get; set; } + public bool IsBaseImport { get; set; } + + public bool IsPreprocessor + { + get { return IsSass; } + } + + public bool IsThemeVars + { + get { return IsPreprocessor && ThemeHelper.PathIsThemeVars(Path); } + } + + public bool IsModuleImports + { + get { return IsSass && ThemeHelper.PathIsModuleImports(Path); } + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs index 0682f9ef44..f70718141f 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs @@ -19,23 +19,29 @@ public static class ThemeHtmlExtensions #region ThemeVars - public static MvcHtmlString ThemeVarLabel(this HtmlHelper html, ThemeVariableInfo info) + public static MvcHtmlString ThemeVarLabel(this HtmlHelper html, ThemeVariableInfo info, string hint = null) { - Guard.ArgumentNotNull(info, "info"); + Guard.NotNull(info, "info"); var result = new StringBuilder(); var resKey = "ThemeVar.{0}.{1}".FormatInvariant(info.Manifest.ThemeName, info.Name); var langId = EngineContext.Current.Resolve().WorkingLanguage.Id; var locService = EngineContext.Current.Resolve(); - var displayName = locService.GetResource(resKey, langId, false, Inflector.Titleize(info.Name)); + var displayName = locService.GetResource(resKey, langId, false, "", true); - var hint = locService.GetResource(resKey + ".Hint", langId, false, "", true); - hint = "@(var_){0}{1}".FormatInvariant(info.Name, hint.HasValue() ? "\n" + hint : ""); + if (displayName.HasValue() && hint.IsEmpty()) + { + hint = locService.GetResource(resKey + ".Hint", langId, false, "", true); + hint = "${0}{1}".FormatInvariant(info.Name, hint.HasValue() ? "\n" + hint : ""); + } result.Append("
    "); - result.Append(html.Label(html.NameForThemeVar(info), displayName)); - result.Append(html.Hint(hint).ToHtmlString()); + result.Append(html.Label(html.NameForThemeVar(info), displayName.NullEmpty() ?? "$" + info.Name, new { @class = "x-col-form-label" })); + if (hint.HasValue()) + { + result.Append(html.Hint(hint).ToHtmlString()); + } result.Append("
    "); return MvcHtmlString.Create(result.ToString()); @@ -43,7 +49,7 @@ public static MvcHtmlString ThemeVarLabel(this HtmlHelper html, ThemeVariableInf public static MvcHtmlString ThemeVarEditor(this HtmlHelper html, ThemeVariableInfo info, object value) { - Guard.ArgumentNotNull(info, "info"); + Guard.NotNull(info, "info"); string expression = html.NameForThemeVar(info); @@ -59,18 +65,26 @@ public static MvcHtmlString ThemeVarEditor(this HtmlHelper html, ThemeVariableIn strValue = value.ToString(); } - var currentTheme = ThemeHelper.ResolveCurrentTheme(); var isDefault = strValue.IsCaseInsensitiveEqual(info.DefaultValue); + var isValidColor = info.Type == ThemeVariableType.Color + && ((strValue.HasValue() && ThemeVarsRepository.IsValidColor(strValue)) || (strValue.IsEmpty() && ThemeVarsRepository.IsValidColor(info.DefaultValue))); MvcHtmlString control; - if (info.Type == ThemeVariableType.Color) + if (isValidColor) { control = html.ColorBox(expression, strValue, info.DefaultValue); } else if (info.Type == ThemeVariableType.Boolean) { + var locService = EngineContext.Current.Resolve(); control = html.CheckBox(expression, strValue.ToBool()); + var custom = "".FormatInvariant( + control.ToString(), + locService.GetResource("Common.On").Truncate(3), + locService.GetResource("Common.Off").Truncate(3)); + + control = MvcHtmlString.Create(custom); } else if (info.Type == ThemeVariableType.Select) { @@ -78,22 +92,29 @@ public static MvcHtmlString ThemeVarEditor(this HtmlHelper html, ThemeVariableIn } else { - control = html.TextBox(expression, isDefault ? "" : strValue, new { placeholder = info.DefaultValue }); + control = html.TextBox(expression, isDefault ? "" : strValue, new { placeholder = info.DefaultValue, @class = "form-control" }); } + return control; + } + + public static MvcHtmlString ThemeVarChainInfo(this HtmlHelper html, ThemeVariableInfo info) + { + Guard.NotNull(info, "info"); + + var currentTheme = ThemeHelper.ResolveCurrentTheme(); + if (currentTheme != info.Manifest) { // the variable is inherited from a base theme: display an info badge - var chainInfo = " {0}".FormatCurrent(info.Manifest.ThemeName); - return MvcHtmlString.Create(control.ToString() + chainInfo); + var chainInfo = "{0}".FormatCurrent(info.Manifest.ThemeName); + return MvcHtmlString.Create(chainInfo); } - else - { - return control; - } - } - private static MvcHtmlString ThemeVarSelectEditor(HtmlHelper html, ThemeVariableInfo info, string expression, string value) + return MvcHtmlString.Empty; + } + + private static MvcHtmlString ThemeVarSelectEditor(HtmlHelper html, ThemeVariableInfo info, string expression, string value) { var manifest = info.Manifest; @@ -108,11 +129,11 @@ private static MvcHtmlString ThemeVarSelectEditor(HtmlHelper html, ThemeVariable select new SelectListItem { Value = x, - Text = x, // TODO: (MC) Localize + Text = x, // TODO: (mc) Localize Selected = x.IsCaseInsensitiveEqual(value) }; - return html.DropDownList(expression, selectList, new { placeholder = info.DefaultValue }); + return html.DropDownList(expression, selectList, new { placeholder = info.DefaultValue, @class = "form-control" }); } public static string NameForThemeVar(this HtmlHelper html, ThemeVariableInfo info) diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs index 9dbb24d8b3..29cc34de82 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs @@ -3,30 +3,35 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; -using SmartStore.Core.Caching; using SmartStore.Core.Infrastructure; using SmartStore.Services.Themes; using System.Web; -using System.Web.Caching; +using System.IO; namespace SmartStore.Web.Framework.Theming { internal class ThemeVarsRepository { - private static readonly Regex s_keyWhitelist = new Regex(@"^[a-zA-Z0-9_-]+$"); - private static readonly Regex s_valueWhitelist = new Regex(@"^[#@]?[a-zA-Z0-9""' _\.,-]*$"); + private static readonly Regex s_keyWhitelist = new Regex(@"^[a-zA-Z0-9_-]+$", RegexOptions.Compiled); + private static readonly Regex s_valueBlacklist = new Regex(@"[:;]+", RegexOptions.Compiled); + private static readonly Regex s_valueSassVars = new Regex(@"[$][a-zA-Z0-9_-]+", RegexOptions.Compiled); + private static readonly Regex s_valueLessVars = new Regex(@"[@][a-zA-Z0-9_-]+", RegexOptions.Compiled); + //private static readonly Regex s_valueWhitelist = new Regex(@"^[#@]?[a-zA-Z0-9""' _\.,-]*$"); - public string GetVariablesAsLess(string themeName, int storeId) + const string SassVarPrefix = "$"; + + public string GetPreprocessorCss(string extension, string themeName, int storeId) { - Guard.ArgumentNotEmpty(() => themeName); - Guard.ArgumentIsPositive(storeId, "storeId"); + Guard.NotEmpty(themeName, nameof(themeName)); + Guard.IsPositive(storeId, nameof(storeId)); + + var variables = GetVariables(themeName, storeId); + var css = Transform(variables); - var variables = GetLessCssVariables(themeName, storeId); - var lessCss = TransformToLess(variables); - return lessCss; + return css; } - private IDictionary GetLessCssVariables(string themeName, int storeId) + private IDictionary GetVariables(string themeName, int storeId) { var result = new Dictionary(); @@ -41,10 +46,13 @@ private IDictionary GetLessCssVariables(string themeName, int st string value = v.Value.ToString(); - if (!s_valueWhitelist.IsMatch(value)) - continue; + if (s_valueBlacklist.IsMatch(value)) + continue; - result.Add("var_" + key, value); + //if (!s_valueWhitelist.IsMatch(value)) + // continue; + + result.Add(key, value); } return result; @@ -52,31 +60,75 @@ private IDictionary GetLessCssVariables(string themeName, int st internal virtual ExpandoObject GetRawVariables(string themeName, int storeId) { - // we need the Asp.Net here cache in order to define cacheKey dependencies - - string cacheKey = FrameworkCacheConsumer.BuildThemeVarsCacheKey(themeName, storeId); + // we need the Asp.Net cache here in order to define cacheKey dependencies + bool validationMode = ThemeHelper.IsStyleValidationRequest(); - return HttpRuntime.Cache.GetOrAdd(cacheKey, () => + if (validationMode) { - var themeVarService = EngineContext.Current.Resolve(); - return themeVarService.GetThemeVariables(themeName, storeId) ?? new ExpandoObject(); - }); + // Return uncached fresh data (the variables is not nuked yet) + return GetRawVariablesCore(themeName, storeId); + } + else + { + string cacheKey = FrameworkCacheConsumer.BuildThemeVarsCacheKey(themeName, storeId); + return HttpRuntime.Cache.GetOrAdd(cacheKey, () => + { + return GetRawVariablesCore(themeName, storeId); + }); + } } - private string TransformToLess(IDictionary parameters) - { - if (parameters.Count == 0) - return string.Empty; + private ExpandoObject GetRawVariablesCore(string themeName, int storeId) + { + var themeVarService = EngineContext.Current.Resolve(); + return themeVarService.GetThemeVariables(themeName, storeId) ?? new ExpandoObject(); + } - var sb = new StringBuilder(); - foreach (var parameter in parameters.Where(kvp => kvp.Value.HasValue())) - { - sb.AppendFormat("@{0}: {1};\n", parameter.Key, parameter.Value); - } + private string Transform(IDictionary parameters) + { + if (parameters.Count == 0) + return string.Empty; - return sb.ToString(); - } + var prefix = SassVarPrefix; + + var sb = new StringBuilder(); + foreach (var parameter in parameters.Where(kvp => kvp.Value.HasValue())) + { + sb.AppendFormat("{0}{1}: {2};\n", prefix, parameter.Key, parameter.Value); + } + + return sb.ToString(); + } + + /// + /// Checks whether the passed SASS value is a valid, displayable HTML color, + /// e.g.: "lighten($red, 20%)" would return false. + /// + /// The SASS value to test + /// + /// We need this helper during theme configuration: we just can't render + /// color pickers for color values containing var references or SASS functions. + /// + internal static bool IsValidColor(string value) + { + if (value.IsEmpty()) + { + return true; + } + + if (s_valueSassVars.IsMatch(value)) + { + return false; + } + + if (value[0] == '#' || value.StartsWith("rgb(") || value.StartsWith("rgba(") || value.StartsWith("hsl(") || value.StartsWith("hsla(")) + { + return true; + } - } + // Let pass all color names (red, blue etc.), but reject functions, e.g. "lighten(#fff, 10%)" + return !value.Contains("("); + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsVirtualFile.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsVirtualFile.cs index 8d307aeedf..b4bd7ab49b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsVirtualFile.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsVirtualFile.cs @@ -1,41 +1,49 @@ +using System.Collections.Generic; using System.IO; using System.Text; using System.Web.Hosting; namespace SmartStore.Web.Framework.Theming { - public class ThemeVarsVirtualFile : VirtualFile + public class ThemeVarsVirtualFile : VirtualFile, IFileDependencyProvider { - private readonly string _themeName; - private readonly int _storeId; - public ThemeVarsVirtualFile(string virtualPath, string themeName, int storeId) - : base(virtualPath) + : this(virtualPath, Path.GetExtension(virtualPath), themeName, storeId) { - _themeName = themeName; - _storeId = storeId; } - public override bool IsDirectory + internal ThemeVarsVirtualFile(string virtualPath, string extension, string themeName, int storeId) + : base(virtualPath) + { + Extension = extension; + ThemeName = themeName; + StoreId = storeId; + } + + public override bool IsDirectory { get { return false; } } - - public override Stream Open() + + public string Extension { get; private set; } + public string ThemeName { get; private set; } + public int StoreId { get; private set; } + + public override Stream Open() { var repo = new ThemeVarsRepository(); - if (_themeName.IsEmpty()) + if (ThemeName.IsEmpty()) return GenerateStreamFromString(string.Empty); - var lessCss = repo.GetVariablesAsLess(_themeName, _storeId); - return GenerateStreamFromString(lessCss); + var css = repo.GetPreprocessorCss(Extension, ThemeName, StoreId); + return GenerateStreamFromString(css); } private Stream GenerateStreamFromString(string value) { var stream = new MemoryStream(); - + using (var writer = new StreamWriter(stream, Encoding.Unicode, 1024, true)) { writer.Write(value); @@ -45,5 +53,9 @@ private Stream GenerateStreamFromString(string value) } } - } + public void AddFileDependencies(ICollection mappedPaths, ICollection cacheKeys) + { + cacheKeys.Add(FrameworkCacheConsumer.BuildThemeVarsCacheKey(ThemeName, StoreId)); + } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableRazorViewEngine.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableRazorViewEngine.cs index 8004a1aec5..de832a6cd2 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableRazorViewEngine.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableRazorViewEngine.cs @@ -14,34 +14,33 @@ public ThemeableRazorViewEngine() var areaBasePaths = areaBasePathsSetting.Split(',').Select(x => x.Trim().EnsureEndsWith("/")).ToArray(); // 0: view, 1: controller, 2: area - var areaFormats = new string[] { "{2}/Views/{1}/{0}", "{2}/Views/Shared/{0}" }; - var areaViewLocationFormats = ExpandLocationFormats(areaBasePaths.SelectMany(x => areaFormats.Select(f => x + f))); - - AreaViewLocationFormats = areaViewLocationFormats.ToArray(); - AreaMasterLocationFormats = areaViewLocationFormats.ToArray(); - AreaPartialViewLocationFormats = areaViewLocationFormats.ToArray(); - - // 0: view, 1: controller, 2: theme - var locationFormats = ExpandLocationFormats(new[] - { - "~/Themes/{2}/Views/{1}/{0}", - "~/Views/{1}/{0}", - "~/Themes/{2}/Views/Shared/{0}", - "~/Views/Shared/{0}" - }); - - ViewLocationFormats = locationFormats.ToArray(); - MasterLocationFormats = locationFormats.ToArray(); - PartialViewLocationFormats = locationFormats.ToArray(); - - if (EnableVbViews) - { - FileExtensions = new[] { "cshtml", "vbhtml" }; - } - else + var areaFormats = new[] + { + "{2}/Views/{1}/{0}", + "{2}/Views/Shared/{0}" + }; + var areaLocationFormats = areaBasePaths.SelectMany(x => areaFormats.Select(f => x + f)); + + AreaViewLocationFormats = ExpandLocationFormats(areaLocationFormats, ViewType.Layout).ToArray(); + AreaMasterLocationFormats = ExpandLocationFormats(areaLocationFormats, ViewType.Layout).ToArray(); + AreaPartialViewLocationFormats = ExpandLocationFormats(areaLocationFormats, ViewType.Partial).ToArray(); + + // 0: view, 1: controller, 2: theme + var locationFormats = new[] { - FileExtensions = new[] { "cshtml" }; - } + "~/Themes/{2}/Views/{1}/{0}", + "~/Views/{1}/{0}", + "~/Themes/{2}/Views/Shared/{0}", + "~/Views/Shared/{0}" + }; + + ViewLocationFormats = ExpandLocationFormats(locationFormats, ViewType.Layout).ToArray(); + MasterLocationFormats = ExpandLocationFormats(locationFormats, ViewType.Layout).ToArray(); + PartialViewLocationFormats = ExpandLocationFormats(locationFormats, ViewType.Partial).ToArray(); + + FileExtensions = EnableVbViews + ? new[] { "cshtml", "vbhtml" } + : new[] { "cshtml" }; } protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableVirtualPathProviderViewEngine.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableVirtualPathProviderViewEngine.cs index cf07e30c28..d7b0e936d2 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableVirtualPathProviderViewEngine.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeableVirtualPathProviderViewEngine.cs @@ -6,6 +6,8 @@ using System.Web.Mvc; using System.Web.WebPages; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Logging; +using SmartStore.Core.Themes; using SmartStore.Services.Common; using SmartStore.Utilities; @@ -13,23 +15,18 @@ namespace SmartStore.Web.Framework.Theming { public abstract class ThemeableVirtualPathProviderViewEngine : BuildManagerViewEngine { - #region Fields - internal Func GetExtensionThunk = VirtualPathUtility.GetExtension; private static readonly string[] _emptyLocations = new string[0]; + private static bool? _enableLocalizedViews; private static bool? _enableVbViews; private readonly string _cacheKeyType = typeof(ThemeableRazorViewEngine).Name; private readonly string _cacheKeyEntry = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:{5}"; - #endregion - - #region Ctor - protected ThemeableVirtualPathProviderViewEngine() { - this.ViewLocationCache = new TwoLevelViewLocationCache(base.ViewLocationCache); + this.ViewLocationCache = new TwoLevelViewLocationCache(); // prepare localized mobile & desktop display modes DisplayModeProvider.Modes.Clear(); @@ -38,108 +35,139 @@ protected ThemeableVirtualPathProviderViewEngine() ContextCondition = IsMobileDevice }; var desktopDisplayMode = new LocalizedDisplayMode(DisplayModeProvider.DefaultDisplayModeId, EnableLocalizedViews); - + DisplayModeProvider.Modes.Add(mobileDisplayMode); DisplayModeProvider.Modes.Add(desktopDisplayMode); } - #endregion - - #region Methods - - public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + public static bool EnableLocalizedViews { - if (controllerContext == null) - { - throw new ArgumentNullException("controllerContext"); - } - if (string.IsNullOrEmpty(viewName)) + get { - throw new ArgumentException("View name cannot be null or empty.", "viewName"); - } - - string[] viewLocationsSearched; - string[] masterLocationsSearched; - - var theme = GetCurrentThemeName(controllerContext); - - string controllerName = controllerContext.RouteData.GetRequiredString("controller"); - string viewPath = this.GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, theme, "View", useCache, out viewLocationsSearched); - string masterPath = this.GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, theme, "Master", useCache, out masterLocationsSearched); + if (!_enableLocalizedViews.HasValue) + { + _enableLocalizedViews = CommonHelper.GetAppSetting("sm:EnableLocalizedViews", false); + } - if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) + return _enableLocalizedViews.Value; + } + set { - return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); + _enableLocalizedViews = value; } - - return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); } - public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) + public static bool EnableVbViews { - if (controllerContext == null) + get { - throw new ArgumentNullException("controllerContext"); + if (!_enableVbViews.HasValue) + { + _enableVbViews = CommonHelper.GetAppSetting("sm:EnableVbViews", false); + } + + return _enableVbViews.Value; } - if (string.IsNullOrEmpty(partialViewName)) + set { - throw new ArgumentException("Partial view name cannot be null or empty.", "partialViewName"); + _enableVbViews = value; } + } - string[] searched; - - var theme = GetCurrentThemeName(controllerContext); - - string controllerName = controllerContext.RouteData.GetRequiredString("controller"); - string partialPath = this.GetPath(controllerContext, PartialViewLocationFormats, AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, theme, "Partial", useCache, out searched); + public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + { + Guard.NotNull(controllerContext, nameof(controllerContext)); + Guard.NotEmpty(viewName, nameof(viewName)); - if (string.IsNullOrEmpty(partialPath)) + var chronometer = EngineContext.Current.Resolve(); + using (chronometer.Step("Find view '{0}'".FormatInvariant(viewName))) { - return new ViewEngineResult(searched); - } + var themeName = GetCurrentThemeName(controllerContext); + var controllerName = controllerContext.RouteData.GetRequiredString("controller"); + var areaName = controllerContext.RouteData.GetAreaName(); + + var viewPath = ResolveViewPath( + controllerContext, + areaName, + ViewLocationFormats, + AreaViewLocationFormats, + "ViewLocationFormats", + viewName, + controllerName, + themeName, + "View", + useCache, + out var viewLocationsSearched); + var masterPath = ResolveViewPath( + controllerContext, + areaName, + MasterLocationFormats, + AreaMasterLocationFormats, + "MasterLocationFormats", + masterName, + controllerName, + themeName, + "Master", + useCache, + out var masterLocationsSearched); + + if (!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) || string.IsNullOrEmpty(masterName))) + { + return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); + } - return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this); + return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); + } } - #endregion - - #region Utilities - - public static bool EnableLocalizedViews + public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { - get + Guard.NotNull(controllerContext, nameof(controllerContext)); + Guard.NotEmpty(partialViewName, nameof(partialViewName)); + + var chronometer = EngineContext.Current.Resolve(); + using (chronometer.Step("Find partial view '{0}'".FormatInvariant(partialViewName))) { - if (!_enableLocalizedViews.HasValue) + string[] searchedLocations; + + var themeName = GetCurrentThemeName(controllerContext); + var controllerName = controllerContext.RouteData.GetRequiredString("controller"); + var areaName = controllerContext.RouteData.GetAreaName(); + + string partialPath = ResolveViewPath( + controllerContext, + areaName, + PartialViewLocationFormats, + AreaPartialViewLocationFormats, + "PartialViewLocationFormats", + partialViewName, + controllerName, + themeName, + "Partial", + useCache, + out searchedLocations); + + if (string.IsNullOrEmpty(partialPath)) { - _enableLocalizedViews = CommonHelper.GetAppSetting("sm:EnableLocalizedViews", false); + return new ViewEngineResult(searchedLocations); } - return _enableLocalizedViews.Value; - } - set - { - _enableLocalizedViews = value; + return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this); } } - public static bool EnableVbViews - { - get - { - if (!_enableVbViews.HasValue) - { - _enableVbViews = CommonHelper.GetAppSetting("sm:EnableVbViews", false); - } - - return _enableVbViews.Value; - } - set - { - _enableVbViews = value; - } - } - - protected virtual string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string theme, string cacheKeyPrefix, bool useCache, out string[] searchedLocations) + protected virtual string ResolveViewPath( + ControllerContext controllerContext, + string areaName, + string[] locations, + string[] areaLocations, + string locationsPropertyName, + string name, + string controllerName, + string theme, + string cacheKeyPrefix, + bool useCache, + out string[] searchedLocations) { searchedLocations = _emptyLocations; @@ -148,7 +176,6 @@ protected virtual string GetPath(ControllerContext controllerContext, string[] l return String.Empty; } - string areaName = controllerContext.RouteData.GetAreaName(); bool usingAreas = !String.IsNullOrEmpty(areaName); if (usingAreas) @@ -158,24 +185,34 @@ protected virtual string GetPath(ControllerContext controllerContext, string[] l // "ExtraAreaViewLocations" gets injected by AdminThemedAttribute var extraAreaViewLocations = controllerContext.RouteData.DataTokens["ExtraAreaViewLocations"] as string[]; + if (isAdminArea && extraAreaViewLocations == null && controllerContext.HttpContext.Request.IsAjaxRequest()) + { + // In admin area AJAX requests DataTokens["ExtraAreaViewLocations"] is not set. + extraAreaViewLocations = AdminThemedAttribute.ExtraAreaViewLocations; + } + if (extraAreaViewLocations != null && extraAreaViewLocations.Length > 0) { var newLocations = areaLocations.ToList(); + var viewType = cacheKeyPrefix == "Partial" + ? ViewType.Partial + : ViewType.Layout; + if (isAdminArea) { // the admin area cannot fallback to itself. Prepend to list. - ExpandLocationFormats(extraAreaViewLocations).Reverse().Each(x => newLocations.Insert(0, x)); + ExpandLocationFormats(extraAreaViewLocations, viewType).Reverse().Each(x => newLocations.Insert(0, x)); } else { - newLocations.AddRange(ExpandLocationFormats(extraAreaViewLocations)); + newLocations.AddRange(ExpandLocationFormats(extraAreaViewLocations, viewType)); } areaLocations = newLocations.ToArray(); } } - List viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null); + var viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null); if (viewLocations.Count == 0) { @@ -188,8 +225,8 @@ protected virtual string GetPath(ControllerContext controllerContext, string[] l if (useCache) { // Only look at cached display modes that can handle the context. - IEnumerable possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode); - foreach (IDisplayMode displayMode in possibleDisplayModes) + var possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode); + foreach (var displayMode in possibleDisplayModes) { string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId)); @@ -212,7 +249,7 @@ protected virtual string GetPath(ControllerContext controllerContext, string[] l // An empty cachedLocation value indicates that we don't have a matching file on disk. Keep going down the list of possible display modes. } - // GetPath is called again without using the cache. + // ResolveViewPath is called again without using the cache. return null; } else @@ -223,19 +260,6 @@ protected virtual string GetPath(ControllerContext controllerContext, string[] l } } - protected virtual bool FilePathIsSupported(string virtualPath) - { - if (this.FileExtensions == null) - { - // legacy behavior for custom ViewEngine that might not set the FileExtensions property - return true; - } - - // get rid of the '.' because the FileExtensions property expects extensions withouth a dot. - string extension = GetExtensionThunk(virtualPath).TrimStart('.'); - return FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); - } - protected virtual string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations) { string result = name; @@ -250,7 +274,15 @@ protected virtual string GetPathFromSpecificName(ControllerContext controllerCon return result; } - protected virtual string GetPathFromGeneralName(ControllerContext controllerContext, List locations, string name, string controllerName, string areaName, string theme, string cacheKey, ref string[] searchedLocations) + protected virtual string GetPathFromGeneralName( + ControllerContext controllerContext, + List locations, + string name, + string controllerName, + string areaName, + string theme, + string cacheKey, + ref string[] searchedLocations) { string result = String.Empty; searchedLocations = new string[locations.Count]; @@ -294,6 +326,7 @@ protected virtual string GetPathFromGeneralName(ControllerContext controllerCont { displayModeId = displayMode.DisplayModeId; } + ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayModeId), cacheValue); } } @@ -306,20 +339,31 @@ protected virtual string GetPathFromGeneralName(ControllerContext controllerCont return result; } - private string GetDisplayModeId(DisplayInfo displayInfo) + protected virtual bool FilePathIsSupported(string virtualPath) { - var localizedDisplayInfo = displayInfo as LocalizedDisplayInfo; - if (localizedDisplayInfo != null) + if (this.FileExtensions == null) { - return localizedDisplayInfo.DisplayModeId; + // legacy behavior for custom ViewEngine that might not set the FileExtensions property + return true; } - return displayInfo.DisplayMode.DisplayModeId; + // get rid of the '.' because the FileExtensions property expects extensions withouth a dot. + string extension = GetExtensionThunk(virtualPath).TrimStart('.'); + return FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } + + private string GetDisplayModeId(DisplayInfo displayInfo) + { + var localizedDisplayInfo = displayInfo as LocalizedDisplayInfo; + + return localizedDisplayInfo != null + ? localizedDisplayInfo.DisplayModeId + : displayInfo.DisplayMode.DisplayModeId; } protected virtual string CreateCacheKey(string prefix, string name, string controllerName, string areaName, string theme/*, string lang*/) { - return _cacheKeyEntry.FormatInvariant( + return string.Format(_cacheKeyEntry, _cacheKeyType, prefix, name, @@ -331,34 +375,51 @@ protected virtual string CreateCacheKey(string prefix, string name, string contr internal static string AppendDisplayModeToCacheKey(string cacheKey, string displayMode) { // key format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:{theme}" - // so append "{displayMode}:" to the key - return cacheKey + displayMode + ":"; + // so append ":{displayMode}" to the key + return string.IsNullOrWhiteSpace(displayMode) + ? cacheKey + : cacheKey + ":" + displayMode; } - protected virtual IEnumerable ExpandLocationFormats(IEnumerable formats) - { - // appends razor view file extensions to location formats - Guard.ArgumentNotNull(() => formats); - - foreach (var format in formats) - { - yield return format + ".cshtml"; - if (EnableVbViews) - { - yield return format + ".vbhtml"; - } - } - } + protected virtual IEnumerable ExpandLocationFormats(IEnumerable formats, ViewType viewType) + { + // Appends razor view file extensions to location formats + Guard.NotNull(formats, nameof(formats)); + + var subfolder = viewType == ViewType.Layout ? "Layouts" : "Partials"; + + foreach (var format in formats) + { + if (viewType > ViewType.View) + { + yield return format.Replace("{0}", subfolder + "/{0}.cshtml"); + + if (EnableVbViews) + { + yield return format.Replace("{0}", subfolder + "/{0}.vbhtml"); + } + } + + yield return format + ".cshtml"; + + if (EnableVbViews) + { + yield return format + ".vbhtml"; + } + } + } protected virtual List GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats) { - List allLocations = new List(); + List locations = new List( + (viewLocationFormats?.Length ?? 0) + + (areaViewLocationFormats?.Length ?? 0)); if (areaViewLocationFormats != null) { foreach (string areaViewLocationFormat in areaViewLocationFormats) { - allLocations.Add(new AreaAwareViewLocation(areaViewLocationFormat)); + locations.Add(new AreaAwareViewLocation(areaViewLocationFormat)); } } @@ -366,11 +427,11 @@ protected virtual List GetViewLocations(string[] viewLocationForma { foreach (string viewLocationFormat in viewLocationFormats) { - allLocations.Add(new ViewLocation(viewLocationFormat)); + locations.Add(new ViewLocation(viewLocationFormat)); } } - return allLocations; + return locations; } protected virtual bool IsSpecificPath(string name) @@ -382,9 +443,7 @@ protected virtual bool IsSpecificPath(string name) protected virtual bool IsMobileDevice(HttpContextBase httpContext) { var mobileDeviceHelper = EngineContext.Current.Resolve(); - var result = mobileDeviceHelper.MobileDevicesSupported() - && mobileDeviceHelper.IsMobileDevice() - && !mobileDeviceHelper.CustomerDontUseMobileVersion(); + var result = mobileDeviceHelper.IsMobileDevice(); return result; } @@ -394,7 +453,12 @@ protected virtual string GetCurrentThemeName(ControllerContext controllerContext return theme.ThemeName; } - #endregion + public enum ViewType + { + View, + Layout, + Partial + } } public class AreaAwareViewLocation : ViewLocation @@ -406,7 +470,7 @@ public AreaAwareViewLocation(string virtualPathFormatString) public override string Format(string viewName, string controllerName, string areaName, string theme) { - return _virtualPathFormatString.FormatInvariant( + return string.Format(_virtualPathFormatString, viewName, controllerName, areaName, @@ -425,7 +489,7 @@ public ViewLocation(string virtualPathFormatString) public virtual string Format(string viewName, string controllerName, string areaName, string theme) { - return _virtualPathFormatString.FormatInvariant( + return string.Format(_virtualPathFormatString, viewName, controllerName, theme); diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs index 1acb69f4fa..cef62c8959 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs @@ -1,40 +1,53 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Web.Caching; using System.Web.Hosting; -using SmartStore.Core.Caching; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Themes; +using SmartStore.Utilities; +using SmartStore.Web.Framework.Theming.Assets; namespace SmartStore.Web.Framework.Theming { - public class ThemingVirtualPathProvider : VirtualPathProvider + public class ThemingVirtualPathProvider : SmartVirtualPathProvider { private readonly VirtualPathProvider _previous; + private static readonly ContextState> _requestState; - public ThemingVirtualPathProvider(VirtualPathProvider previous) + static ThemingVirtualPathProvider() + { + _requestState = new ContextState>("ThemeFileResolver.RequestCache", () => new Dictionary()); + } + + public ThemingVirtualPathProvider(VirtualPathProvider previous) { _previous = previous; - } + } public override bool FileExists(string virtualPath) { - if (ThemeHelper.PathIsThemeVars(virtualPath)) - { - return true; - } - var result = GetResolveResult(virtualPath); if (result != null) { - if (!result.IsExplicit) + if (!result.IsBased) { return true; } else { - virtualPath = result.OriginalVirtualPath; + if (result.Query.HasValue() && result.Query.IndexOf('.') >= 0) + { + // libSass tries to locate files by appending .[s]css extension to our querystring. Prevent this shit! + return false; + } + else + { + // Let system VPP check for this file + virtualPath = result.ResultVirtualPath ?? result.OriginalVirtualPath; + } } } @@ -43,102 +56,104 @@ public override bool FileExists(string virtualPath) public override VirtualFile GetFile(string virtualPath) { - if (ThemeHelper.PathIsThemeVars(virtualPath)) - { - var theme = ThemeHelper.ResolveCurrentTheme(); - int storeId = ThemeHelper.ResolveCurrentStoreId(); - return new ThemeVarsVirtualFile(virtualPath, theme.ThemeName, storeId); - } + VirtualFile file = null; + string debugPath = null; var result = GetResolveResult(virtualPath); if (result != null) { - if (!result.IsExplicit) + // File is an inherited theme file. Set the result virtual path. + virtualPath = result.ResultVirtualPath ?? result.OriginalVirtualPath; + if (!result.IsBased) { - return new InheritedVirtualThemeFile(result); + file = new InheritedVirtualThemeFile(result); } - else + } + + if (result == null || file is InheritedVirtualThemeFile) + { + // Handle plugin and symlinked theme folders in debug mode. + debugPath = ResolveDebugFilePath(virtualPath); + if (debugPath != null) { - virtualPath = result.OriginalVirtualPath; + file = new DebugVirtualFile(file?.VirtualPath ?? virtualPath, debugPath); } } - return _previous.GetFile(virtualPath); + return file ?? _previous.GetFile(virtualPath); } - - public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) - { - bool isLess; - bool isBundle; - if (!ThemeHelper.IsStyleSheet(virtualPath, out isLess, out isBundle)) + + public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) + { + if (virtualPathDependencies == null) { - return GetCacheDependencyInternal(virtualPath, virtualPathDependencies, utcStart); + return _previous.GetFileHash(virtualPath, virtualPathDependencies); } - else - { - if (!isLess && !isBundle) - { - // it's a static css file (no bundle, no less) - return GetCacheDependencyInternal(virtualPath, virtualPathDependencies, utcStart); - } - - var arrPathDependencies = virtualPathDependencies.Cast().ToArray(); - - // determine the virtual themevars.less import reference - var themeVarsFile = arrPathDependencies.Where(x => ThemeHelper.PathIsThemeVars(x)).FirstOrDefault(); - - if (themeVarsFile.IsEmpty()) - { - // no themevars import... so no special considerations here - return GetCacheDependencyInternal(virtualPath, virtualPathDependencies, utcStart); - } - - // exclude the themevars import from the file dependencies list, - // 'cause this one cannot be monitored by the physical file system - var fileDependencies = arrPathDependencies.Except(new string[] { themeVarsFile }); - - if (arrPathDependencies.Any()) - { - int storeId = ThemeHelper.ResolveCurrentStoreId(); - var theme = ThemeHelper.ResolveCurrentTheme(); - // invalidate the cache when variables change - string cacheKey = FrameworkCacheConsumer.BuildThemeVarsCacheKey(theme.ThemeName, storeId); - var cacheDependency = new CacheDependency(MapDependencyPaths(fileDependencies), new string[] { cacheKey }, utcStart); - return cacheDependency; - } - - return null; - } - } - private CacheDependency GetCacheDependencyInternal(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) + var fileNames = MapDependencyPaths(virtualPathDependencies.Cast(), out _); + var combiner = HashCodeCombiner.Start(); + + foreach (var fileName in fileNames) + { + combiner.Add(new FileInfo(fileName)); + } + + return combiner.CombinedHashString; + } + + public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { - return new CacheDependency(MapDependencyPaths(virtualPathDependencies.Cast()), utcStart); + if (virtualPathDependencies == null) + { + return null; + } + + var mappedPaths = MapDependencyPaths(virtualPathDependencies.Cast(), out var cacheKeys); + + return new CacheDependency(mappedPaths, cacheKeys, utcStart); } - private string[] MapDependencyPaths(IEnumerable virtualPathDependencies) + /// + /// Maps virtual to physical paths. Used to compute cache dependecies and file hashes. + /// + internal string[] MapDependencyPaths(IEnumerable virtualPathDependencies, out string[] cacheKeys) { - var fileNames = new List(); + cacheKeys = null; + + var mappedPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + var cacheKeySet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var dep in virtualPathDependencies) { - var result = GetResolveResult(dep); - if (result != null) + var file = GetFile(dep); + + if (file is IFileDependencyProvider provider) { - fileNames.Add(result.IsExplicit ? HostingEnvironment.MapPath(result.OriginalVirtualPath) : result.ResultPhysicalPath); + provider.AddFileDependencies(mappedPaths, cacheKeySet); } - else + else if (file != null) { - fileNames.Add(HostingEnvironment.MapPath(dep)); + mappedPaths.Add(HostingEnvironment.MapPath(file.VirtualPath)); } } - return fileNames.ToArray(); + cacheKeys = cacheKeySet.ToArray(); + + var paths = mappedPaths.ToArray(); + Array.Sort(paths); + + return paths; } - private InheritedThemeFileResult GetResolveResult(string virtualPath) + private static InheritedThemeFileResult GetResolveResult(string virtualPath) { - var result = EngineContext.Current.Resolve().Resolve(virtualPath); + var d = _requestState.GetState(); + + if (!d.TryGetValue(virtualPath, out var result)) + { + result = d[virtualPath] = EngineContext.Current.Resolve().Resolve(virtualPath); + } + return result; } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/TwoLevelViewLocationCache.cs b/src/Presentation/SmartStore.Web.Framework/Theming/TwoLevelViewLocationCache.cs index ad9be046a4..165e3b194d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/TwoLevelViewLocationCache.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/TwoLevelViewLocationCache.cs @@ -1,17 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Web; using System.Web.Mvc; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.Theming { public class TwoLevelViewLocationCache : IViewLocationCache { private static readonly object s_key = new object(); - private readonly IViewLocationCache _cache; + private readonly IViewLocationCache _innerCache; - public TwoLevelViewLocationCache(IViewLocationCache cache) + public TwoLevelViewLocationCache() { - _cache = cache; + var cacheDuringDebug = CommonHelper.GetAppSetting("sm:EnableViewLocationCacheDuringDebug"); + + if (!cacheDuringDebug && (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled)) + { + _innerCache = DefaultViewLocationCache.Null; + } + else + { + _innerCache = new DefaultViewLocationCache(TimeSpan.FromHours(2)); + } + } private static IDictionary GetRequestCache(HttpContextBase httpContext) @@ -31,7 +43,7 @@ public string GetViewLocation(HttpContextBase httpContext, string key) string location; if (!d.TryGetValue(key, out location)) { - location = _cache.GetViewLocation(httpContext, key); + location = _innerCache.GetViewLocation(httpContext, key); d[key] = location; } return location; @@ -39,7 +51,8 @@ public string GetViewLocation(HttpContextBase httpContext, string key) public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath) { - _cache.InsertViewLocation(httpContext, key, virtualPath); + _innerCache.InsertViewLocation(httpContext, key, virtualPath); + GetRequestCache(httpContext)[key] = virtualPath; } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs index b6fade194d..63cf24718d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs @@ -1,52 +1,28 @@ using System; using System.Collections.Generic; -using System.Dynamic; using System.IO; using System.Linq; using System.Web.Mvc; using System.Web.WebPages; using SmartStore.Core; -using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Themes; -using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Filters; -using SmartStore.Web.Framework.Localization; +using SmartStore.Services; +using SmartStore.Services.Cms; namespace SmartStore.Web.Framework.Theming { public abstract class WebViewPage : System.Web.Mvc.WebViewPage { - private IText _text; - private IWorkContext _workContext; - - private IList _internalNotifications; - private IThemeRegistry _themeRegistry; - private IThemeContext _themeContext; - private ExpandoObject _themeVars; - private bool? _isHomePage; - private int? _currentCategoryId; - private int? _currentManufacturerId; - private int? _currentProductId; + private WebViewPageHelper _helper; protected int CurrentCategoryId { get { - if (!_currentCategoryId.HasValue) - { - int id = 0; - var routeValues = this.Url.RequestContext.RouteData.Values; - if (routeValues["controller"].ToString().IsCaseInsensitiveEqual("catalog") && routeValues["action"].ToString().IsCaseInsensitiveEqual("category")) - { - id = Convert.ToInt32(routeValues["categoryId"].ToString()); - } - _currentCategoryId = id; - } - - return _currentCategoryId.Value; + return _helper.CurrentCategoryId; } } @@ -54,105 +30,58 @@ protected int CurrentManufacturerId { get { - if (!_currentManufacturerId.HasValue) - { - var routeValues = this.Url.RequestContext.RouteData.Values; - int id = 0; - if (routeValues["controller"].ToString().IsCaseInsensitiveEqual("catalog") && routeValues["action"].ToString().IsCaseInsensitiveEqual("manufacturer")) - { - id = Convert.ToInt32(routeValues["manufacturerId"].ToString()); - } - _currentManufacturerId = id; - } - - return _currentManufacturerId.Value; - } + return _helper.CurrentManufacturerId; + } } protected int CurrentProductId { get { - if (!_currentProductId.HasValue) - { - var routeValues = this.Url.RequestContext.RouteData.Values; - int id = 0; - if (routeValues["controller"].ToString().IsCaseInsensitiveEqual("product") && routeValues["action"].ToString().IsCaseInsensitiveEqual("productdetails")) - { - id = Convert.ToInt32(routeValues["productId"].ToString()); - } - _currentProductId = id; - } - - return _currentProductId.Value; - } + return _helper.CurrentProductId; + } } protected bool IsHomePage { get { - if (!_isHomePage.HasValue) - { - var routeData = this.Url.RequestContext.RouteData; - _isHomePage = routeData.GetRequiredString("controller").IsCaseInsensitiveEqual("Home") && - routeData.GetRequiredString("action").IsCaseInsensitiveEqual("Index"); - } - - return _isHomePage.Value; - } + return _helper.IsHomePage; + } } - protected bool HasMessages + protected bool IsMobileDevice { get { - return ResolveNotifications(null).Any(); + return _helper.IsMobileDevice; } } - protected ICollection GetMessages(NotifyType type) + protected bool IsStoreClosed + { + get + { + return _helper.IsStoreClosed; + } + } + + public bool EnableHoneypotProtection { - return ResolveNotifications(type).AsReadOnly(); + get { return _helper.EnableHoneypotProtection; } } - private IEnumerable ResolveNotifications(NotifyType? type) - { - - IEnumerable result = Enumerable.Empty(); - - if (_internalNotifications == null) - { - string key = NotifyAttribute.NotificationsKey; - IList entries; - - if (this.TempData.ContainsKey(key)) - { - entries = this.TempData[key] as IList; - if (entries != null) - { - result = result.Concat(entries); - } - } - - if (this.ViewData.ContainsKey(key)) - { - entries = this.ViewData[key] as IList; - if (entries != null) - { - result = result.Concat(entries); - } - } - - _internalNotifications = new List(result); - } - - if (type == null) + protected bool HasMessages + { + get { - return _internalNotifications.Select(x => x.Message); + return _helper.ResolveNotifications(null).Any(); } + } - return _internalNotifications.Where(x => x.Type == type.Value).Select(x => x.Message); + protected ICollection GetMessages(NotifyType type) + { + return _helper.ResolveNotifications(type).AsReadOnly(); } /// @@ -162,45 +91,58 @@ public Localizer T { get { - return _text.Get; + return _helper.T; } } - public IWorkContext WorkContext + public ICommonServices CommonServices + { + get + { + return _helper.Services; + } + } + + public IWorkContext WorkContext { get { - return _workContext; + return _helper.Services.WorkContext; } } - - public override void InitHelpers() + + public ILinkResolver LinkResolver + { + get + { + return _helper.LinkResolver; + } + } + + public override void InitHelpers() { base.InitHelpers(); - if (DataSettings.DatabaseIsInstalled()) - { - _text = EngineContext.Current.Resolve(); - _workContext = EngineContext.Current.Resolve(); - } + _helper = EngineContext.Current.Resolve(); + _helper.Initialize(this.ViewContext); } public HelperResult RenderWrappedSection(string name, object wrapperHtmlAttributes) { Action action = delegate(TextWriter tw) - { - var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(wrapperHtmlAttributes); - var tagBuilder = new TagBuilder("div"); - tagBuilder.MergeAttributes(htmlAttributes); - - var section = this.RenderSection(name, false); - if (section != null) - { - tw.Write(tagBuilder.ToString(TagRenderMode.StartTag)); - section.WriteTo(tw); - tw.Write(tagBuilder.ToString(TagRenderMode.EndTag)); - } - }; + { + var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(wrapperHtmlAttributes); + var tagBuilder = new TagBuilder("div"); + tagBuilder.MergeAttributes(htmlAttributes); + + var section = this.RenderSection(name, false); + if (section != null) + { + tw.Write(tagBuilder.ToString(TagRenderMode.StartTag)); + section.WriteTo(tw); + tw.Write(tagBuilder.ToString(TagRenderMode.EndTag)); + } + }; return new HelperResult(action); } @@ -234,30 +176,14 @@ public override string Layout } } - /// - /// Return a value indicating whether the working language and theme support RTL (right-to-left) - /// - /// - public bool ShouldUseRtlTheme() - { - var supportRtl = _workContext.WorkingLanguage.Rtl; - if (supportRtl) - { - //ensure that the active theme also supports it - supportRtl = this.ThemeManifest.SupportRtl; - } - return supportRtl; - } - - /// - /// Gets the manifest of the current active theme - /// - protected ThemeManifest ThemeManifest + /// + /// Gets the manifest of the current active theme + /// + protected ThemeManifest ThemeManifest { get { - EnsureThemeContextInitialized(); - return _themeContext.CurrentTheme; + return _helper.ThemeContext?.CurrentTheme; } } @@ -269,27 +195,7 @@ protected virtual string ThemeName { get { - EnsureThemeContextInitialized(); - return _themeContext.WorkingDesktopTheme; - } - } - - /// - /// Gets the runtime theme variables as specified in the theme's config file - /// alongside the merged user-defined variables - /// - public dynamic ThemeVariables - { - get - { - if (_themeVars == null) - { - var storeContext = EngineContext.Current.Resolve(); - var repo = new ThemeVarsRepository(); - _themeVars = repo.GetRawVariables(this.ThemeManifest.ThemeName, storeContext.CurrentStore.Id); - } - - return _themeVars; + return _helper.ThemeContext?.WorkingThemeName; } } @@ -306,7 +212,7 @@ public string GetThemeVariable(string varname, string defaultValue = "") /// The theme variable value public T GetThemeVariable(string varName, T defaultValue = default(T)) { - Guard.ArgumentNotEmpty(varName, "varName"); + Guard.NotEmpty(varName, "varName"); var vars = this.ThemeVariables as IDictionary; if (vars != null && vars.ContainsKey(varName)) @@ -322,20 +228,133 @@ public string GetThemeVariable(string varname, string defaultValue = "") return defaultValue; } + /// + /// Gets the runtime theme variables as specified in the theme's config file + /// alongside the merged user-defined variables + /// + public dynamic ThemeVariables + { + get + { + return _helper.ThemeVariables; + } + } + + /// + /// Modifies a URL (appends/updates a query string part and optionally removes another query string). + /// + /// The URL to modifiy. If null, the current page's URL is resolved. + /// The new query string part. + /// A query string name to remove. + /// The modified URL. + public string ModifyUrl(string url, string query, string removeQueryName = null) + { + var webHelper = _helper.Services?.WebHelper; + if (webHelper == null) + { + return url; + } + + url = url.NullEmpty() ?? webHelper.GetThisPageUrl(true); + var url2 = webHelper.ModifyQueryString(url, query, null); + + if (removeQueryName.HasValue()) + { + url2 = webHelper.RemoveQueryString(url2, removeQueryName); + } + + return url2; + } + + public string GenerateHelpUrl(HelpTopic topic) + { + var seoCode = WorkContext?.WorkingLanguage?.UniqueSeoCode; + if (seoCode.IsEmpty()) + { + return topic?.EnPath; + } + + return SmartStoreVersion.GenerateHelpUrl(seoCode, topic); + } + public string GenerateHelpUrl(string path) { - return SmartStoreVersion.GenerateHelpUrl(WorkContext.WorkingLanguage.UniqueSeoCode, path); + var seoCode = WorkContext?.WorkingLanguage?.UniqueSeoCode; + if (seoCode.IsEmpty()) + { + return path; + } + + return SmartStoreVersion.GenerateHelpUrl(seoCode, path); } - private void EnsureThemeContextInitialized() - { - if (_themeRegistry == null) - _themeRegistry = EngineContext.Current.Resolve(); - if (_themeContext == null) - _themeContext = EngineContext.Current.Resolve(); - } + /// + /// Tries to find a matching localization file for a given culture in the following order + /// (assuming is 'de-DE', is 'lang-*.js' and is 'en-US'): + /// + /// Exact match > lang-de-DE.js + /// Neutral culture > lang-de.js + /// Any region for language > lang-de-CH.js + /// Exact match for fallback culture > lang-en-US.js + /// Neutral fallback culture > lang-en.js + /// Any region for fallback language > lang-en-GB.js + /// + /// + /// The ISO culture code to get a localization file for, e.g. 'de-DE' + /// The virtual path to search in + /// The pattern to match, e.g. 'lang-*.js'. The wildcard char MUST exist. + /// Optional. + /// Result + public LocalizationFileResolveResult ResolveLocalizationFile( + string culture, + string virtualPath, + string pattern, + string fallbackCulture = "en") + { + return _helper.LocalizationFileResolver.Resolve(culture, virtualPath, pattern, true, fallbackCulture); + } - } + public bool HasMetadata(string name) + { + return TryGetMetadata(name, out _); + } + + /// + /// Looks up an entry in ViewData dictionary first, then in ViewData.ModelMetadata.AdditionalValues dictionary + /// + /// Actual type of value + /// Name of entry + /// Result + public T GetMetadata(string name) + { + TryGetMetadata(name, out var value); + return value; + } + + /// + /// Looks up an entry in ViewData dictionary first, then in ViewData.ModelMetadata.AdditionalValues dictionary + /// + /// Actual type of value + /// Name of entry + /// true if the entry exists in any of the dictionaries, false otherwise + public bool TryGetMetadata(string name, out T value) + { + value = default(T); + + var exists = ViewData.TryGetValue(name, out var raw); + if (!exists) + { + exists = ViewData.ModelMetadata?.AdditionalValues?.TryGetValue(name, out raw) == true; + } + + if (raw != null) + { + value = raw.Convert(); + } + + return exists; + } + } public abstract class WebViewPage : WebViewPage { diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs new file mode 100644 index 0000000000..912fe7c258 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Web.Mvc; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Core.Themes; +using SmartStore.Services; +using SmartStore.Services.Common; +using SmartStore.Web.Framework.Filters; +using SmartStore.Core.Domain; +using SmartStore.Services.Customers; +using SmartStore.Core.Domain.Security; +using SmartStore.Services.Cms; + +namespace SmartStore.Web.Framework.Theming +{ + public class WebViewPageHelper + { + private bool _initialized; + private ControllerContext _controllerContext; + private ExpandoObject _themeVars; + private ICollection _internalNotifications; + + private int? _currentCategoryId; + private int? _currentManufacturerId; + private int? _currentProductId; + + private bool? _isHomePage; + private bool? _isMobileDevice; + private bool? _isStoreClosed; + private bool? _enableHoneypot; + + public WebViewPageHelper() + { + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + public ILocalizationFileResolver LocalizationFileResolver { get; set; } + public ICommonServices Services { get; set; } + public IThemeRegistry ThemeRegistry { get; set; } + public IThemeContext ThemeContext { get; set; } + public IMobileDeviceHelper MobileDeviceHelper { get; set; } + public ILinkResolver LinkResolver { get; set; } + + public void Initialize(ViewContext viewContext) + { + if (!_initialized) + { + _controllerContext = viewContext.GetRootControllerContext(); + _initialized = true; + } + } + + + public int CurrentCategoryId + { + get + { + if (!_currentCategoryId.HasValue) + { + int id = 0; + var routeValues = _controllerContext.RequestContext.RouteData.Values; + + if (routeValues["controller"].ToString().IsCaseInsensitiveEqual("catalog") + && routeValues["action"].ToString().IsCaseInsensitiveEqual("category") + && routeValues.ContainsKey("categoryId")) + { + id = Convert.ToInt32(routeValues["categoryId"].ToString()); + } + _currentCategoryId = id; + } + + return _currentCategoryId.Value; + } + } + + public int CurrentManufacturerId + { + get + { + if (!_currentManufacturerId.HasValue) + { + var routeValues = _controllerContext.RequestContext.RouteData.Values; + int id = 0; + if (routeValues["controller"].ToString().IsCaseInsensitiveEqual("catalog") + && routeValues["action"].ToString().IsCaseInsensitiveEqual("manufacturer") + && routeValues.ContainsKey("manufacturerId")) + { + id = Convert.ToInt32(routeValues["manufacturerId"].ToString()); + } + _currentManufacturerId = id; + } + + return _currentManufacturerId.Value; + } + } + + public int CurrentProductId + { + get + { + if (!_currentProductId.HasValue) + { + var routeValues = _controllerContext.RequestContext.RouteData.Values; + int id = 0; + if (routeValues["controller"].ToString().IsCaseInsensitiveEqual("product") + && routeValues["action"].ToString().IsCaseInsensitiveEqual("productdetails") + && routeValues.ContainsKey("productId")) + { + id = Convert.ToInt32(routeValues["productId"].ToString()); + } + _currentProductId = id; + } + + return _currentProductId.Value; + } + } + + public bool IsHomePage + { + get + { + if (!_isHomePage.HasValue) + { + var routeData = _controllerContext.RequestContext.RouteData; + _isHomePage = routeData.GetRequiredString("controller").IsCaseInsensitiveEqual("Home") && + routeData.GetRequiredString("action").IsCaseInsensitiveEqual("Index"); + } + + return _isHomePage.Value; + } + } + + public bool IsMobileDevice + { + get + { + if (!_isMobileDevice.HasValue) + { + _isMobileDevice = MobileDeviceHelper.IsMobileDevice(); + } + + return _isMobileDevice.Value; + } + } + + public bool IsStoreClosed + { + get + { + if (!_isStoreClosed.HasValue) + { + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); + _isStoreClosed = Services.WorkContext.CurrentCustomer.IsAdmin() && settings.StoreClosedAllowForAdmins ? false : settings.StoreClosed; + } + + return _isStoreClosed.Value; + } + } + + public bool EnableHoneypotProtection + { + get + { + if (!_enableHoneypot.HasValue) + { + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); + _enableHoneypot = settings.EnableHoneypotProtection; + } + + return _enableHoneypot.Value; + } + } + + public IEnumerable ResolveNotifications(NotifyType? type) + { + IEnumerable result = Enumerable.Empty(); + + if (_internalNotifications == null) + { + string key = NotifyAttribute.NotificationsKey; + ICollection entries; + + var tempData = _controllerContext.Controller.TempData; + if (tempData.ContainsKey(key)) + { + entries = tempData[key] as ICollection; + if (entries != null) + { + result = result.Concat(entries); + } + } + + var viewData = _controllerContext.Controller.ViewData; + if (viewData.ContainsKey(key)) + { + entries = viewData[key] as ICollection; + if (entries != null) + { + result = result.Concat(entries); + } + } + + _internalNotifications = new HashSet(result); + } + + if (type == null) + { + return _internalNotifications.Select(x => x.Message); + } + + return _internalNotifications.Where(x => x.Type == type.Value).Select(x => x.Message); + } + + public dynamic ThemeVariables + { + get + { + if (_themeVars == null) + { + var storeContext = Services?.StoreContext; + var themeManifest = ThemeContext?.CurrentTheme; + + if (storeContext == null || themeManifest == null) + { + _themeVars = new ExpandoObject(); + } + else + { + var repo = new ThemeVarsRepository(); + _themeVars = repo.GetRawVariables(themeManifest.ThemeName, storeContext.CurrentStore.Id); + } + } + + return _themeVars; + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs b/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs new file mode 100644 index 0000000000..43b71c4ea0 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Web.Framework.UI +{ + public interface IBreadcrumb + { + void Track(MenuItem item, bool prepend = false); + IReadOnlyList Trail { get; } + } + + public class DefaultBreadcrumb : IBreadcrumb + { + private List _trail; + + public void Track(MenuItem item, bool prepend = false) + { + Guard.NotNull(item, nameof(item)); + + if (_trail == null) + { + _trail = new List(); + } + + if (prepend) + { + _trail.Insert(0, item); + } + else + { + _trail.Add(item); + } + } + + public IReadOnlyList Trail + { + get + { + return _trail; + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs index 40a5117a6c..744553f48d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs @@ -8,12 +8,13 @@ using BundleTransformer.Core.Orderers; using BundleTransformer.Core.Bundles; using SmartStore.Core; -using SmartStore.Web.Framework.Theming; using SmartStore.Services.Seo; +using SmartStore.Web.Framework.Theming.Assets; +using SmartStore.Core.Themes; +using SmartStore.Utilities.Threading; namespace SmartStore.Web.Framework.UI { - public enum BundleType { Script, @@ -31,13 +32,11 @@ public class BundleBuilder : IBundleBuilder private readonly IWorkContext _workContext; private readonly IThemeContext _themeContext; - private static readonly object s_lock = new object(); - public BundleBuilder(IStoreContext storeContext, IWorkContext workContext, IThemeContext themeContext) { - this._storeContext = storeContext; - this._workContext = workContext; - this._themeContext = themeContext; + _storeContext = storeContext; + _workContext = workContext; + _themeContext = themeContext; } public string Build(BundleType type, IEnumerable files) @@ -45,23 +44,23 @@ public string Build(BundleType type, IEnumerable files) if (files == null || !files.Any()) return string.Empty; - string bundleVirtualPath = this.GetBundleVirtualPath(type, files); + var bundleVirtualPath = this.GetBundleVirtualPath(type, files); var bundleFor = BundleTable.Bundles.GetBundleFor(bundleVirtualPath); if (bundleFor == null) { - lock (s_lock) + using (KeyedLock.Lock("BundleBuilder.Build." + bundleVirtualPath)) { bundleFor = BundleTable.Bundles.GetBundleFor(bundleVirtualPath); if (bundleFor == null) { - var nullOrderer = new NullOrderer(); + var nullOrderer = new NullOrderer(); - Bundle bundle = (type == BundleType.Script) ? + Bundle bundle = (type == BundleType.Script) ? new CustomScriptBundle(bundleVirtualPath) as Bundle : new SmartStyleBundle(bundleVirtualPath) as Bundle; - bundle.Orderer = nullOrderer; + bundle.Orderer = nullOrderer; - bundle.Include(files.ToArray()); + bundle.Include(files.ToArray()); BundleTable.Bundles.Add(bundle); } @@ -69,7 +68,13 @@ public string Build(BundleType type, IEnumerable files) } if (type == BundleType.Script) - return Scripts.Render(bundleVirtualPath).ToString(); + { + return Scripts.Render(bundleVirtualPath).ToString(); + + //// Uncomment this if you want to bypass asset caching on mobile browsers + //return Scripts.RenderFormat("", + // files.Select(x => VirtualPathUtility.ToAppRelative(x)).ToArray()).ToString(); + } return Styles.Render(bundleVirtualPath).ToString(); } @@ -95,7 +100,7 @@ protected virtual string GetBundleVirtualPath(BundleType type, IEnumerable x)) + foreach (var file in files) { hashInput += file; hashInput += ","; @@ -105,7 +110,7 @@ protected virtual string GetBundleVirtualPath(BundleType type, IEnumerable x.EndsWith(".less", StringComparison.OrdinalIgnoreCase))) + if (type == BundleType.Stylesheet && !_workContext.IsAdmin && files.Any(x => x.EndsWith(".scss", StringComparison.OrdinalIgnoreCase))) { hash += "-s" + _storeContext.CurrentStore.Id; hash += "-t" + _themeContext.CurrentTheme.ThemeName; @@ -120,7 +125,5 @@ protected virtual string GetBundleVirtualPath(BundleType type, IEnumerable CaptchaSettings { get; set; } - public Lazy Logger { get; set; } - public Lazy LocalizationService { get; set; } - - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - var valid = false; - - try - { - var captchaSettings = CaptchaSettings.Value; - var verifyUrl = CommonHelper.GetAppSetting("g:RecaptchaVerifyUrl"); - var recaptchaResponse = filterContext.HttpContext.Request.Form["g-recaptcha-response"]; - - var url = "{0}?secret={1}&response={2}".FormatInvariant( - verifyUrl, - HttpUtility.UrlEncode(captchaSettings.ReCaptchaPrivateKey), - HttpUtility.UrlEncode(recaptchaResponse) - ); - - using (var client = new WebClient()) - { - var jsonResponse = client.DownloadString(url); - using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonResponse))) - { - var serializer = new DataContractJsonSerializer(typeof(GoogleRecaptchaApiResponse)); - var result = serializer.ReadObject(memoryStream) as GoogleRecaptchaApiResponse; - - if (result == null) - { - Logger.Value.Error(LocalizationService.Value.GetResource("Common.CaptchaUnableToVerify")); - } - else - { - if (result.ErrorCodes == null) - { - valid = result.Success; - } - } - } - } - } - catch (Exception exception) - { - Logger.Value.ErrorsAll(exception); - } - - // this will push the result value into a parameter in our Action - filterContext.ActionParameters["captchaValid"] = valid; - - base.OnActionExecuting(filterContext); - } - } - - - [DataContract] - public class GoogleRecaptchaApiResponse - { - [DataMember(Name = "success")] - public bool Success { get; set; } - - [DataMember(Name = "error-codes")] - public List ErrorCodes { get; set; } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs deleted file mode 100644 index 77d7b1cb7a..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Text; -using System.Web.Mvc; -using SmartStore.Core.Infrastructure; -using SmartStore.Utilities; - -namespace SmartStore.Web.Framework.UI.Captcha -{ - public static class HtmlExtensions - { - public static string GenerateCaptcha(this HtmlHelper helper) - { - var sb = new StringBuilder(); - var captchaSettings = EngineContext.Current.Resolve(); - var widgetUrl = CommonHelper.GetAppSetting("g:RecaptchaWidgetUrl"); - var elementId = "GoogleRecaptchaWidget"; - - sb.AppendLine(""); - sb.AppendLine("
    ".FormatInvariant(elementId)); - sb.AppendLine("".FormatInvariant(widgetUrl)); - - return sb.ToString(); - } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Choices/ChoiceItemModel.cs b/src/Presentation/SmartStore.Web.Framework/UI/Choices/ChoiceItemModel.cs new file mode 100644 index 0000000000..6811ceac29 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Choices/ChoiceItemModel.cs @@ -0,0 +1,21 @@ +using System; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Web.Framework.UI.Choices +{ + public abstract class ChoiceItemModel : EntityModelBase + { + public string Name { get; set; } + public string SeName { get; set; } + public string Alias { get; set; } + public string Color { get; set; } + public string PriceAdjustment { get; set; } + public decimal PriceAdjustmentValue { get; set; } + public int QuantityInfo { get; set; } + public bool IsPreSelected { get; set; } + public bool IsDisabled { get; set; } + public string ImageUrl { get; set; } + + public abstract string GetItemLabel(); + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Choices/ChoiceModel.cs b/src/Presentation/SmartStore.Web.Framework/UI/Choices/ChoiceModel.cs new file mode 100644 index 0000000000..0b4b689452 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Choices/ChoiceModel.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Web.Framework.UI.Choices +{ + public abstract class ChoiceModel : EntityModelBase + { + protected ChoiceModel() + { + this.AllowedFileExtensions = new List(); + this.Values = new List(); + } + + public AttributeControlType AttributeControlType { get; set; } + + public string Alias { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string TextPrompt { get; set; } + + public bool IsRequired { get; set; } + + public bool IsDisabled { get; set; } + + /// + /// Allowed file extensions for customer uploaded files + /// + public IList AllowedFileExtensions { get; set; } + + /// + /// Selected value for textboxes + /// + public string TextValue { get; set; } + /// + /// Selected day value for datepicker + /// + public int? SelectedDay { get; set; } + /// + /// Selected month value for datepicker + /// + public int? SelectedMonth { get; set; } + /// + /// Selected year value for datepicker + /// + public int? SelectedYear { get; set; } + /// + /// Begin year for datepicker + /// + public int? BeginYear { get; set; } + /// + /// End year for datepicker + /// + public int? EndYear { get; set; } + + public string UploadedFileGuid { get; set; } + public string UploadedFileName { get; set; } + + public virtual IList Values { get; set; } + + public abstract string BuildControlId(); + + public virtual string GetLabel() + { + return TextPrompt.NullEmpty() ?? Name; + } + + public virtual string GetDescription() + { + var containsImg = Description.IsEmpty() ? false : Description.Contains(" _controlIdFn; + + public GenericChoiceModel(Func controlIdFn) + { + Guard.NotNull(controlIdFn, nameof(controlIdFn)); + + _controlIdFn = controlIdFn; + } + + public override string BuildControlId() + { + return _controlIdFn(this); + } + } + + public class GenericChoiceItemModel : ChoiceItemModel + { + private readonly Func _itemLabelFn; + + public GenericChoiceItemModel(Func itemLabelFn) + { + Guard.NotNull(itemLabelFn, nameof(itemLabelFn)); + + _itemLabelFn = itemLabelFn; + } + + public override string GetItemLabel() + { + return _itemLabelFn(this); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/BadgeStyle.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/BadgeStyle.cs deleted file mode 100644 index d97d995a4f..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/BadgeStyle.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace SmartStore.Web.Framework.UI -{ - - public enum BadgeStyle - { - Default, - Success, - Warning, - Important, - Info, - Inverse - } - -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs new file mode 100644 index 0000000000..8b72005580 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/BoxSpacing.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; + +namespace SmartStore.Web.Framework.UI +{ + public class BoxSpacing + { + [Range(0, 6)] + [JsonProperty("t")] + public byte? Top { get; set; } + + [Range(0, 6)] + [JsonProperty("r")] + public byte? Right { get; set; } + + [Range(0, 6)] + [JsonProperty("b")] + public byte? Bottom { get; set; } + + [Range(0, 6)] + [JsonProperty("l")] + public byte? Left { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Button/Button.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Button/Button.cs deleted file mode 100644 index ac1947139f..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Button/Button.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Mvc; -using System.Web; - -namespace SmartStore.Web.Framework.UI { - - public class Button : IHtmlString { - - TagBuilder _builder; - - public Button(string tagName = "a", string text = "Mein Text") { - _builder = new TagBuilder(tagName); - this.Text = text; - } - - public string Text { get; set; } - - public virtual string ToHtmlString() { - _builder.AddCssClass("btn"); - _builder.InnerHtml = this.Text; - _builder.MergeAttribute("href", "#"); - return _builder.ToString(TagRenderMode.Normal); - } - - public override string ToString() { - return this.ToHtmlString(); - } - - } - -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs index ea50203ae6..10806732cb 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs @@ -1,18 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Mvc; -using System.Web.UI; -using System.IO; using System.Web.Routing; namespace SmartStore.Web.Framework.UI { - public abstract class Component : IUiComponent { - protected Component() { this.HtmlAttributes = new RouteValueDictionary(); @@ -53,6 +46,5 @@ public virtual bool NameIsRequired return false; } } - } - -} + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs index 1ca2fb9a77..bb034c7061 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs @@ -1,32 +1,28 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web; -using System.Web.UI; using SmartStore.Utilities; using SmartStore.Core.Infrastructure; using System.Web.Mvc; namespace SmartStore.Web.Framework.UI { - - public abstract class ComponentBuilder : IHtmlString, IHideObjectMembers + public abstract class ComponentBuilder : IHtmlString, IHideObjectMembers where TComponent : Component - where TBuilder : ComponentBuilder + where TBuilder : ComponentBuilder { private ComponentRenderer _renderer; - protected ComponentBuilder(TComponent component, HtmlHelper htmlHelper) + protected ComponentBuilder(TComponent component, HtmlHelper htmlHelper) { - Guard.ArgumentNotNull(() => component); - Guard.ArgumentNotNull(() => htmlHelper); + Guard.NotNull(component, nameof(component)); + Guard.NotNull(htmlHelper, nameof(htmlHelper)); this.Component = component; this.HtmlHelper = htmlHelper; } - protected internal HtmlHelper HtmlHelper + protected internal HtmlHelper HtmlHelper { get; private set; @@ -72,29 +68,39 @@ private void EnrichRenderer(ComponentRenderer renderer) renderer.ViewData = this.HtmlHelper.ViewData; } - public TBuilder WithRenderer() - where T : ComponentRenderer - { - return this.WithRenderer(typeof(T)); - } + public TBuilder WithRenderer(ComponentRenderer instance) + { + Guard.NotNull(instance, nameof(instance)); + + return this.WithRenderer>(instance); + } - public TBuilder WithRenderer(ComponentRenderer instance) + public TBuilder WithRenderer(ComponentRenderer instance) where T : ComponentRenderer { - Guard.ArgumentNotNull(() => instance); - return this.WithRenderer(typeof(T)); - } + Guard.NotNull(instance, nameof(instance)); + + this.Renderer = instance; + return this as TBuilder; + } + + public TBuilder WithRenderer() + where T : ComponentRenderer + { + return this.WithRenderer(typeof(T)); + } - public TBuilder WithRenderer(Type rendererType) + public TBuilder WithRenderer(Type rendererType) { - Guard.ArgumentNotNull(() => rendererType); + Guard.NotNull(rendererType, nameof(rendererType)); Guard.Implements>(rendererType); - var renderer = Activator.CreateInstance(rendererType) as ComponentRenderer; - if (renderer != null) - { - this.Renderer = renderer; - } - return this as TBuilder; + + if (Activator.CreateInstance(rendererType) is ComponentRenderer renderer) + { + this.Renderer = renderer; + } + + return this as TBuilder; } public virtual TBuilder Name(string name) @@ -103,7 +109,7 @@ public virtual TBuilder Name(string name) return this as TBuilder; } - public virtual TBuilder HtmlAttributes(object attributes) + public virtual TBuilder HtmlAttributes(object attributes) { return this.HtmlAttributes(CommonHelper.ObjectToDictionary(attributes)); } @@ -114,7 +120,24 @@ public virtual TBuilder HtmlAttributes(IDictionary attributes) return this as TBuilder; } - public string ToHtmlString() + public virtual TBuilder HtmlAttribute(string name, object value) + { + Guard.NotEmpty(name, nameof(name)); + Guard.NotNull(value, nameof(value)); + + this.Component.HtmlAttributes[name] = value; + return this as TBuilder; + } + + public virtual TBuilder AddCssClass(string cssClass) + { + Guard.NotEmpty(cssClass, nameof(cssClass)); + + this.Component.HtmlAttributes.AppendCssClass(cssClass); + return this as TBuilder; + } + + public string ToHtmlString() { return this.Renderer.ToHtmlString(); } @@ -129,11 +152,9 @@ public virtual void Render() this.Renderer.Render(); } - public static implicit operator TComponent(ComponentBuilder builder) + public static implicit operator TComponent(ComponentBuilder builder) { return builder.ToComponent(); } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentFactory.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentFactory.cs index fc7b6de2d9..455b58f2f3 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentFactory.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentFactory.cs @@ -1,24 +1,19 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Mvc; using System.ComponentModel; +using System.Web.Mvc; using SmartStore.Core; namespace SmartStore.Web.Framework.UI { - - public class ComponentFactory : IHideObjectMembers + public class ComponentFactory : IHideObjectMembers { - - public ComponentFactory(HtmlHelper helper) + public ComponentFactory(HtmlHelper helper) { this.HtmlHelper = helper; } [EditorBrowsable(EditorBrowsableState.Never)] - public HtmlHelper HtmlHelper + public HtmlHelper HtmlHelper { get; set; @@ -26,48 +21,60 @@ public HtmlHelper HtmlHelper #region Builders - public virtual TabStripBuilder TabStrip() + public virtual TabStripBuilder TabStrip() { - return new TabStripBuilder(new TabStrip(), this.HtmlHelper); + return new TabStripBuilder(new TabStrip(), this.HtmlHelper); } - public virtual WindowBuilder Window() + public virtual WindowBuilder Window() { - return new WindowBuilder(new Window(), this.HtmlHelper); + return new WindowBuilder(new Window(), this.HtmlHelper); } - public virtual PagerBuilder Pager(string viewDataKey) + public virtual PagerBuilder Pager(string viewDataKey) { var dataSource = this.HtmlHelper.ViewContext.ViewData.Eval(viewDataKey) as IPageable; if (dataSource == null) { - throw new InvalidOperationException(string.Format("Item in ViewData with key '{0}' is not an IPageable.", - viewDataKey)); + throw new InvalidOperationException(string.Format("Item in ViewData with key '{0}' is not an IPageable.", viewDataKey)); } return Pager(dataSource); } - public virtual PagerBuilder Pager(int pageIndex, int pageSize, int totalItemsCount) + public virtual PagerBuilder Pager(int pageIndex, int pageSize, int totalItemsCount) { return Pager(new PagedList(pageIndex, pageSize, totalItemsCount)); } - public virtual PagerBuilder Pager(int pageCount) + public virtual PagerBuilder Pager(int pageCount) { // for simple pagers without active state (e.g. forum topic mini pager) return Pager(new PagedList(0, 1, pageCount)); } - public virtual PagerBuilder Pager(IPageable model) + public virtual PagerBuilder Pager(IPageable model) { - Guard.ArgumentNotNull(model, "model"); - return new PagerBuilder(new Pager(), this.HtmlHelper).Model(model); + Guard.NotNull(model, nameof(model)); + return new PagerBuilder(new Pager(), this.HtmlHelper).Model(model); } - #endregion + public virtual EntityPickerBuilder EntityPicker() + { + return new EntityPickerBuilder(new EntityPicker(), this.HtmlHelper); + } - } + public virtual FileUploaderBuilder FileUploader() + { + return new FileUploaderBuilder(new FileUploader(), this.HtmlHelper); + } + + public virtual MenuBuilder Menu() + { + return new MenuBuilder(new Menu(), this.HtmlHelper).WithRenderer(new MenuRenderer()); + } + #endregion + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRenderer.cs index b16b8e703c..36d21bec1a 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRenderer.cs @@ -1,18 +1,13 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Mvc; using System.Web; using System.Web.UI; using System.IO; namespace SmartStore.Web.Framework.UI -{ - +{ public abstract class ComponentRenderer : IHtmlString where TComponent : Component { - protected ComponentRenderer() { } @@ -48,54 +43,48 @@ protected internal ViewDataDictionary ViewData public virtual void VerifyState() { - Guard.NotNull(() => this.Component); + Guard.NotNull(this.Component, nameof(this.Component)); + if (this.Component.NameIsRequired && !this.Component.Id.HasValue()) { throw Error.InvalidOperation("A component must have a unique 'Name'. Please provide a name."); } } - protected void WriteHtml(HtmlTextWriter writer) - { - this.VerifyState(); - this.Component.Id = SanitizeId(this.Component.Id); - - this.WriteHtmlCore(writer); - } + protected string SanitizeId(string id) + { + return id.SanitizeHtmlId(); + } - protected virtual void WriteHtmlCore(HtmlTextWriter writer) + public virtual void Render() { - throw new NotImplementedException(); - } + WriteHtml(ViewContext.Writer); + } - public void Render() + public virtual string ToHtmlString() { - using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(this.ViewContext.Writer)) - { - this.WriteHtml(htmlTextWriter); - } - } - - public string ToHtmlString() - { - string str; using (var stringWriter = new StringWriter()) { - using (var htmlWriter = new HtmlTextWriter(stringWriter)) - { - this.WriteHtml(htmlWriter); - str = stringWriter.ToString(); - } - } - return str; - } - - - protected string SanitizeId(string id) - { - return id.SanitizeHtmlId(); - } - + WriteHtml(stringWriter); + var str = stringWriter.ToString(); + return str; + } + } + + protected void WriteHtml(TextWriter writer) + { + this.VerifyState(); + this.Component.Id = SanitizeId(this.Component.Id); + + using (var htmlWriter = new HtmlTextWriter(writer)) + { + WriteHtmlCore(htmlWriter); + } + } + + protected virtual void WriteHtmlCore(HtmlTextWriter writer) + { + throw new NotImplementedException(); + } } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRendererUtils.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRendererUtils.cs index f8493ff00e..fdcc8c62e1 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRendererUtils.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentRendererUtils.cs @@ -1,14 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace SmartStore.Web.Framework.UI -{ - +{ public static class ComponentRendererUtils { - public static void AppendCssClass(this IDictionary attributes, string @class) { attributes.AppendInValue("class", " ", @class); @@ -18,7 +14,5 @@ public static void PrependCssClass(this IDictionary attributes, { attributes.PrependInValue("class", " ", @class); } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs new file mode 100644 index 0000000000..fcca6a595b --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPicker.cs @@ -0,0 +1,49 @@ +namespace SmartStore.Web.Framework.UI +{ + public class EntityPicker : Component + { + public EntityPicker() + { + EntityType = "product"; + IconCssClass = "fa fa-search"; + HtmlAttributes["type"] = "button"; + HtmlAttributes.AppendCssClass("btn btn-secondary"); + HighlightSearchTerm = true; + AppendMode = true; + Delimiter = ","; + FieldName = "id"; + } + + public string EntityType { get; set; } + public int LanguageId { get; set; } + + public string TargetInputSelector + { + get { return HtmlAttributes["data-target"] as string; } + set { HtmlAttributes["data-target"] = value; } + } + + public string Caption { get; set; } + public string IconCssClass { get; set; } + + public string DialogTitle { get; set; } + public string DialogUrl { get; set; } + + public bool DisableGroupedProducts { get; set; } + public bool DisableBundleProducts { get; set; } + public int[] DisabledEntityIds { get; set; } + public string[] Selected { get; set; } + + public bool EnableThumbZoomer { get; set; } + public bool HighlightSearchTerm { get; set; } + + public int MaxItems { get; set; } + public bool AppendMode { get; set; } + public string Delimiter { get; set; } + public string FieldName { get; set; } + + public string OnDialogLoadingHandlerName { get; set; } + public string OnDialogLoadedHandlerName { get; set; } + public string OnSelectionCompletedHandlerName { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs new file mode 100644 index 0000000000..78b3d4867b --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/EntityPicker/EntityPickerBuilder.cs @@ -0,0 +1,161 @@ +using System; +using System.Linq.Expressions; +using System.Web.Mvc; +using System.Web.Routing; + +namespace SmartStore.Web.Framework.UI +{ + public class EntityPickerBuilder : ComponentBuilder, TModel> + { + public EntityPickerBuilder(EntityPicker component, HtmlHelper htmlHelper) + : base(component, htmlHelper) + { + WithRenderer(new ViewBasedComponentRenderer("EntityPicker")); + DialogUrl(UrlHelper.GenerateUrl( + null, + "Picker", + "Entity", + new RouteValueDictionary { { "area", "" } }, + RouteTable.Routes, + htmlHelper.ViewContext.RequestContext, + false)); + } + + public EntityPickerBuilder EntityType(string value) + { + base.Component.EntityType = value; + return this; + } + + public EntityPickerBuilder LanguageId(int value) + { + base.Component.LanguageId = value; + return this; + } + + public EntityPickerBuilder Caption(string value) + { + base.Component.Caption = value; + return this; + } + + public EntityPickerBuilder IconCssClass(string value) + { + base.Component.IconCssClass = value; + return this; + } + + public EntityPickerBuilder Tooltip(string value) + { + base.Component.HtmlAttributes["title"] = value; + return this; + } + + public EntityPickerBuilder DialogTitle(string value) + { + base.Component.DialogTitle = value; + return this; + } + + public EntityPickerBuilder DialogUrl(string value) + { + base.Component.DialogUrl = value; + return this; + } + + + public EntityPickerBuilder For(Expression> expression) + { + Guard.NotNull(expression, nameof(expression)); + + return For(ExpressionHelper.GetExpressionText(expression)); + } + + public EntityPickerBuilder For(string expression) + { + base.Component.TargetInputSelector = "#" + this.HtmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(expression); + return this; + } + + + public EntityPickerBuilder DisableGroupedProducts(bool value) + { + base.Component.DisableGroupedProducts = value; + return this; + } + + public EntityPickerBuilder DisableBundleProducts(bool value) + { + base.Component.DisableBundleProducts = value; + return this; + } + + public EntityPickerBuilder DisabledEntityIds(params int[] values) + { + base.Component.DisabledEntityIds = values; + return this; + } + + public EntityPickerBuilder Selected(params string[] values) + { + base.Component.Selected = values; + return this; + } + + public EntityPickerBuilder EnableThumbZoomer(bool value) + { + base.Component.EnableThumbZoomer = value; + return this; + } + + public EntityPickerBuilder HighlightSearchTerm(bool value) + { + base.Component.HighlightSearchTerm = value; + return this; + } + + + public EntityPickerBuilder MaxItems(int value) + { + base.Component.MaxItems = value; + return this; + } + + public EntityPickerBuilder AppendMode(bool value) + { + base.Component.AppendMode = value; + return this; + } + + public EntityPickerBuilder Delimiter(string value) + { + base.Component.Delimiter = value; + return this; + } + + public EntityPickerBuilder FieldName(string value) + { + base.Component.FieldName = value; + return this; + } + + + public EntityPickerBuilder OnDialogLoading(string handlerName) + { + base.Component.OnDialogLoadingHandlerName = handlerName; + return this; + } + + public EntityPickerBuilder OnDialogLoaded(string handlerName) + { + base.Component.OnDialogLoadedHandlerName = handlerName; + return this; + } + + public EntityPickerBuilder OnSelectionCompleted(string handlerName) + { + base.Component.OnSelectionCompletedHandlerName = handlerName; + return this; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs new file mode 100644 index 0000000000..75bb64220f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs @@ -0,0 +1,29 @@ +using System; + +namespace SmartStore.Web.Framework.UI +{ + public enum BadgeStyle + { + Secondary, + Primary, + Success, + Info, + Warning, + Danger, + Light, + Dark + } + + public enum ButtonStyle + { + Secondary, + Primary, + Success, + Info, + Warning, + Danger, + Light, + Dark, + Link + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs new file mode 100644 index 0000000000..15e4d6915f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploader.cs @@ -0,0 +1,53 @@ +using System; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; + +namespace SmartStore.Web.Framework.UI +{ + public class FileUploader : Component + { + public FileUploader() + : this(EngineContext.Current.Resolve()) + { + } + + public FileUploader(Localizer localizer) + { + HtmlAttributes.AppendCssClass("fileupload"); + HtmlAttributes.Add("data-accept", "gif|jpe?g|png"); + HtmlAttributes.Add("data-show-remove-after-upload", "false"); + IconCssClass = "fa fa-upload"; + ButtonStyle = ButtonStyle.Secondary; + + if (localizer != null) + { + CancelText = localizer("Common.Fileuploader.Cancel"); + RemoveText = localizer("Common.Remove"); + UploadText = localizer("Common.Fileuploader.Upload"); + } + } + + public string UploadUrl + { + get { return HtmlAttributes["data-upload-url"] as string; } + set { HtmlAttributes["data-upload-url"] = value; } + } + + public string IconCssClass { get; set; } + public ButtonStyle ButtonStyle { get; set; } + public bool ButtonOutlineStyle { get; set; } + public bool ShowRemoveButton { get; set; } + public bool Compact { get; set; } + + public string CancelText { get; set; } + public string RemoveText { get; set; } + public string UploadText { get; set; } + + public string OnUploadingHandlerName { get; set; } + public string OnUploadCompletedHandlerName { get; set; } + public string OnErrorHandlerName { get; set; } + public string OnFileRemoveHandlerName { get; set; } + public string OnAbortedHandlerName { get; set; } + public string OnCompletedHandlerName { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs new file mode 100644 index 0000000000..2784575e45 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/FileUploader/FileUploaderBuilder.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.WebPages; + +namespace SmartStore.Web.Framework.UI +{ + public class FileUploaderBuilder : ComponentBuilder, TModel> + { + public FileUploaderBuilder(FileUploader component, HtmlHelper htmlHelper) + : base(component, htmlHelper) + { + WithRenderer(new ViewBasedComponentRenderer("FileUploader")); + } + + public FileUploaderBuilder UploadUrl(string value) + { + base.Component.UploadUrl = value; + return this; + } + + public FileUploaderBuilder IconCssClass(string value) + { + base.Component.IconCssClass = value; + return this; + } + + public FileUploaderBuilder ButtonStyle(ButtonStyle value) + { + base.Component.ButtonStyle = value; + return this; + } + + public FileUploaderBuilder ButtonOutlineStyle(bool value) + { + base.Component.ButtonOutlineStyle = value; + return this; + } + + public FileUploaderBuilder ShowRemoveButton(bool value) + { + base.Component.ShowRemoveButton = value; + return this; + } + + public FileUploaderBuilder ShowRemoveButtonAfterUpload(bool value) + { + base.Component.HtmlAttributes["data-show-remove-after-upload"] = value.ToString().ToLower(); + return this; + } + + public FileUploaderBuilder Compact(bool value) + { + base.Component.Compact = value; + return this; + } + + public FileUploaderBuilder AcceptedFileTypes(string value) + { + if (value.IsEmpty()) + { + if (base.Component.HtmlAttributes.ContainsKey("data-accept")) + base.Component.HtmlAttributes.Remove("data-accept"); + } + else + { + base.Component.HtmlAttributes["data-accept"] = value; + } + + return this; + } + + public FileUploaderBuilder CancelText(string value) + { + base.Component.CancelText = value; + return this; + } + + public FileUploaderBuilder RemoveText(string value) + { + base.Component.RemoveText = value; + return this; + } + + public FileUploaderBuilder UploadText(string value) + { + base.Component.UploadText = value; + return this; + } + + public FileUploaderBuilder OnUploadingHandlerName(string handlerName) + { + base.Component.OnUploadingHandlerName = handlerName; + return this; + } + + public FileUploaderBuilder OnUploadCompletedHandlerName(string handlerName) + { + base.Component.OnUploadCompletedHandlerName = handlerName; + return this; + } + + public FileUploaderBuilder OnErrorHandlerName(string handlerName) + { + base.Component.OnErrorHandlerName = handlerName; + return this; + } + + public FileUploaderBuilder OnFileRemoveHandlerName(string handlerName) + { + base.Component.OnFileRemoveHandlerName = handlerName; + return this; + } + + public FileUploaderBuilder OnAbortedHandlerName(string handlerName) + { + base.Component.OnAbortedHandlerName = handlerName; + return this; + } + + public FileUploaderBuilder OnCompletedHandlerName(string handlerName) + { + base.Component.OnCompletedHandlerName = handlerName; + return this; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/HtmlHelperExtensions.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/HtmlHelperExtensions.cs index d6aff1aeb3..2d8e269b44 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/HtmlHelperExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/HtmlHelperExtensions.cs @@ -1,20 +1,67 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; +using System.Web; using System.Web.Mvc; namespace SmartStore.Web.Framework.UI { - public static class HtmlHelperExtensions { - - public static ComponentFactory SmartStore(this HtmlHelper helper) + public static ComponentFactory SmartStore(this HtmlHelper helper) { - return new ComponentFactory(helper); + return new ComponentFactory(helper); } - } + public static IHtmlString Attrs(this HtmlHelper html, IDictionary attrs) + { + var sb = new StringBuilder(); + + using (var writer = new StringWriter(sb)) + { + RenderAttrs(html, attrs, writer); + } + + return MvcHtmlString.Create(sb.ToString()); + } + + public static void RenderAttrs(this HtmlHelper html, IDictionary attrs) + { + RenderAttrs(html, attrs, html.ViewContext.Writer); + } + + private static void RenderAttrs(HtmlHelper html, IDictionary attrs, TextWriter output) + { + Guard.NotNull(attrs, nameof(attrs)); + + if (attrs.Count == 0) + { + return; + } + + var first = true; + + foreach (var key in attrs.Keys) + { + var value = attrs[key]; + + if (value == null) + { + continue; + } + + if (!first) + { + output.Write(" "); + } + + first = false; + output.Write(key + "=\""); + HttpUtility.HtmlAttributeEncode(value.ToString(), output); + output.Write("\""); + } + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/IHtmlAttributesContainer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/IHtmlAttributesContainer.cs index cc7aec1fc1..31974274b4 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/IHtmlAttributesContainer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/IHtmlAttributesContainer.cs @@ -3,15 +3,11 @@ namespace SmartStore.Web.Framework.UI { - public interface IHtmlAttributesContainer { - IDictionary HtmlAttributes { get; } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/IUiComponent.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/IUiComponent.cs index 584cf3f6e9..7352829014 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/IUiComponent.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/IUiComponent.cs @@ -2,10 +2,8 @@ namespace SmartStore.Web.Framework.UI { - public interface IUiComponent : IHtmlAttributesContainer { - string Id { get; @@ -20,7 +18,5 @@ bool NameIsRequired { get; } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/AdminMenuProvider.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/AdminMenuProvider.cs index 0689436a61..ccbc2fbbd6 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/AdminMenuProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/AdminMenuProvider.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using SmartStore.Collections; namespace SmartStore.Web.Framework.UI @@ -10,7 +7,7 @@ public abstract class AdminMenuProvider : IMenuProvider { public void BuildMenu(TreeNode rootNode) { - var pluginsNode = rootNode.Children.FirstOrDefault(x => x.Value.Id == "plugins"); + var pluginsNode = rootNode.SelectNodeById("plugins"); BuildMenuCore(pluginsNode); } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/CatalogMenuProvider.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/CatalogMenuProvider.cs new file mode 100644 index 0000000000..4c3e8f10f3 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/CatalogMenuProvider.cs @@ -0,0 +1,21 @@ +using System; +using SmartStore.Collections; + +namespace SmartStore.Web.Framework.UI +{ + public abstract class CatalogMenuProvider : IMenuProvider + { + public abstract void BuildMenu(TreeNode rootNode); + + public string MenuName + { + get { return "catalog"; } + } + + public virtual int Ordinal + { + get { return 0; } + } + + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/IMenuProvider.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/IMenuProvider.cs index 27b59728e5..d48441cc0d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/IMenuProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/IMenuProvider.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; -using System.Linq; using SmartStore.Collections; namespace SmartStore.Web.Framework.UI { - /// /// Enables (plugins) developers to inject menu items to menus /// diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/Menu.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/Menu.cs new file mode 100644 index 0000000000..f17eae41e2 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/Menu.cs @@ -0,0 +1,11 @@ +namespace SmartStore.Web.Framework.UI +{ + public class Menu : Component + { + public override bool NameIsRequired => true; + + public string Template { get; set; } + + public RouteInfo Route { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuBuilder.cs new file mode 100644 index 0000000000..d3b2e75f5e --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuBuilder.cs @@ -0,0 +1,26 @@ +using System.Web.Mvc; + +namespace SmartStore.Web.Framework.UI +{ + public class MenuBuilder : ComponentBuilder, TModel> + { + public MenuBuilder(Menu component, HtmlHelper htmlHelper) + : base(component, htmlHelper) + { + } + + public MenuBuilder Template(string value) + { + Component.Route = null; + Component.Template = value; + return this; + } + + public MenuBuilder Template(string action, string controller, object routeValues = null) + { + Component.Template = null; + Component.Route = new RouteInfo(action, controller, routeValues); + return this; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuExtensions.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuExtensions.cs index b0fa39c9b5..6284b9b052 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuExtensions.cs @@ -1,27 +1,54 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Web.Mvc; +using System.Web.Routing; +using Newtonsoft.Json; using SmartStore.Collections; +using SmartStore.Core.Domain.Cms; +using SmartStore.Core.Localization; +using SmartStore.Services.Localization; namespace SmartStore.Web.Framework.UI { - - public static class MenuExtensions + public static class MenuExtensions { - - public static IList GetBreadcrumb(this TreeNode node) + public static IEnumerable> GetBreadcrumb(this TreeNode node) { - var breadcrumb = new List(); - while (node != null && !node.IsRoot) - { - breadcrumb.Add(node.Value); - node = node.Parent; - } - breadcrumb.Reverse(); + Guard.NotNull(node, nameof(node)); + + return node.Trail.Where(x => !x.IsRoot); + } + + public static string GetItemText(this TreeNode node, Localizer localizer) + { + string result = null; + + if (node.Value.ResKey.HasValue()) + { + result = localizer(node.Value.ResKey).Text; + } + + if (!result.HasValue() || result.IsCaseInsensitiveEqual(node.Value.ResKey)) + { + result = node.Value.Text; + } + + return result; + } - return breadcrumb; + /// + /// Gets the state of node within the passed currentPath, which is the navigation breadcrumb. + /// + /// The node to get the state for + /// The current path/breadcrumb + /// + /// enumeration indicating whether the node is in the current path (Selected or Expanded) + /// and whether it has children (Parent) + /// + public static NodePathState GetNodePathState(this TreeNode node, IEnumerable> currentPath) + { + return GetNodePathState(node, currentPath.Select(x => x.Value).ToList()); } /// @@ -35,7 +62,7 @@ public static IList GetBreadcrumb(this TreeNode node) /// public static NodePathState GetNodePathState(this TreeNode node, IList currentPath) { - Guard.ArgumentNotNull(() => currentPath); + Guard.NotNull(currentPath, nameof(currentPath)); var state = NodePathState.Unknown; @@ -44,17 +71,19 @@ public static NodePathState GetNodePathState(this TreeNode node, IList state = state | NodePathState.Parent; } + var lastInPath = currentPath.LastOrDefault(); + if (currentPath.Count > 0) { - if (node.Value.Equals(currentPath.LastOrDefault())) + if (node.Value.Equals(lastInPath)) { state = state | NodePathState.Selected; } else { - if (node.Depth < currentPath.Count) + if (node.Depth - 1 < currentPath.Count) { - if (currentPath[node.Depth].Equals(node.Value)) + if (currentPath[node.Depth - 1].Equals(node.Value)) { state = state | NodePathState.Expanded; } @@ -65,6 +94,123 @@ public static NodePathState GetNodePathState(this TreeNode node, IList return state; } - } + /// + /// Applies serialized route informations to a tree node. + /// + /// Tree node. + /// JSON serialized route data. + public static void ApplyRouteData(this TreeNode node, string data) + { + if (data.HasValue()) + { + var routeValues = JsonConvert.DeserializeObject(data); + var routeName = string.Empty; + + if (routeValues.TryGetValue("routename", out var val)) + { + routeName = val as string; + routeValues.Remove("routename"); + } + + if (routeName.HasValue()) + { + node.Value.Route(routeName, routeValues); + } + else + { + node.Value.Action(routeValues); + } + } + } + + /// + /// Creates a menu model. + /// + /// Menu. + /// Controller context to resolve current node. Can be null. + /// Menu model. + public static MenuModel CreateModel(this IMenu menu, string template, ControllerContext context) + { + Guard.NotNull(menu, nameof(menu)); + + var model = new MenuModel + { + Name = menu.Name, + Template = template ?? menu.Name, + Root = menu.Root + }; + + var currentNode = menu.ResolveCurrentNode(context); + + model.Path = currentNode != null + ? currentNode.Trail.Where(x => !x.IsRoot).ToList() + : new List>(); + + menu.ResolveElementCounts(model.SelectedNode, false); + + return model; + } + + /// + /// Converts a list of menu items into a tree. + /// + /// Origin of the tree. + /// List of menu items. + /// Menu item providers. + /// Tree of menu items. + public static TreeNode GetTree( + this IEnumerable items, + string origin, + IDictionary> itemProviders) + { + Guard.NotNull(items, nameof(items)); + Guard.NotNull(itemProviders, nameof(itemProviders)); + + if (!items.Any()) + { + return new TreeNode(new MenuItem()); + } + + // Prepare root node. It represents the MenuRecord. + var menu = items.First().Menu; + var rootItem = new MenuItem + { + Text = menu.GetLocalized(x => x.Title), + EntityId = 0 + }; + var root = new TreeNode(rootItem) + { + Id = menu.SystemName + }; + + AddChildItems(root, 0); + + return root; + + void AddChildItems(TreeNode parentNode, int parentItemId) + { + if (parentNode == null) + { + return; + } + + var entities = items.Where(x => x.ParentItemId == parentItemId).OrderBy(x => x.DisplayOrder); + + foreach (var entity in entities) + { + if (entity.ProviderName.HasValue() && itemProviders.TryGetValue(entity.ProviderName, out var provider)) + { + var newNode = provider.Value.Append(new MenuItemProviderRequest + { + Origin = origin, + Parent = parentNode, + Entity = entity + }); + AddChildItems(newNode, entity.Id); + } + } + } + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItem.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItem.cs index f9d76c5dd3..bccc320ead 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItem.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItem.cs @@ -1,31 +1,50 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Web.Routing; -using SmartStore.Collections; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.UI { - public class MenuItem : NavigationItem, ICloneable { + private string _id; public MenuItem() { - this.Attributes = new RouteValueDictionary(); - } + this.Attributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + } /// /// If this menu item refers to an entity, the id of the backed entity (like category, products e.g.) /// public int EntityId { get; set; } + public string EntityName { get; set; } - /// - /// The total count of contained elements (like the count of products within a category) - /// - public int? ElementsCount { get; set; } + public int MenuItemId { get; set; } - public string Id { get; set; } + /// + /// The total count of contained elements (like the count of products within a category) + /// + public int? ElementsCount { get; set; } + + /// + /// Unique identifier. + /// + public string Id + { + get + { + if (_id == null) + { + _id = CommonHelper.GenerateRandomDigitCode(10); + } + + return _id; + } + set + { + _id = value; + } + } public string ResKey { get; set; } @@ -33,9 +52,9 @@ public MenuItem() public bool IsGroupHeader { get; set; } - public IDictionary Attributes { get; private set; } + public IDictionary Attributes { get; set; } - public MenuItemBuilder ToBuilder() + public MenuItemBuilder ToBuilder() { return new MenuItemBuilder(this); } @@ -54,7 +73,5 @@ object ICloneable.Clone() { return this.Clone(); } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItemBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItemBuilder.cs index db03c5b813..785aa05415 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItemBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuItemBuilder.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Utilities; namespace SmartStore.Web.Framework.UI { - public class MenuItemBuilder : NavigationItemBuilder { private readonly IList _permissionNames; diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuModel.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuModel.cs new file mode 100644 index 0000000000..b68a8dd6a9 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuModel.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; + +namespace SmartStore.Web.Framework.UI +{ + public class MenuModel + { + private TreeNode _selectedNode; + private bool _seekedSelectedNode; + + public string Name { get; set; } + public string Template { get; set; } + + public TreeNode Root { get; set; } + public IList> Path { get; set; } + + public TreeNode SelectedNode + { + get + { + if (!_seekedSelectedNode) + { + _selectedNode = Path?.LastOrDefault() ?? Root; + _seekedSelectedNode = true; + } + + return _selectedNode; + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuPublisher.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuPublisher.cs index aba1f47cbb..4078ca66fd 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuPublisher.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuPublisher.cs @@ -21,14 +21,14 @@ public class MenuPublisher : IMenuPublisher public MenuPublisher(ITypeFinder typeFinder, IRequestCache requestCache) { - this._typeFinder = typeFinder; - this._requestCache = requestCache; + _typeFinder = typeFinder; + _requestCache = requestCache; } public void RegisterMenus(TreeNode rootNode, string menuName) { - Guard.ArgumentNotNull(() => rootNode); - Guard.ArgumentNotEmpty(() => menuName); + Guard.NotNull(rootNode, nameof(rootNode)); + Guard.NotEmpty(menuName, nameof(menuName)); var providers = _requestCache.Get("sm.menu.providers.{0}".FormatInvariant(menuName), () => { @@ -41,7 +41,7 @@ public void RegisterMenus(TreeNode rootNode, string menuName) { try { - var provider = Activator.CreateInstance(type) as IMenuProvider; + var provider = EngineContext.Current.ContainerManager.ResolveUnregistered(type) as IMenuProvider; instances.Add(provider); } catch { } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuRenderer.cs new file mode 100644 index 0000000000..7490164935 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuRenderer.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc.Html; +using System.Web.UI; +using SmartStore.Collections; +using SmartStore.Core.Infrastructure; + +namespace SmartStore.Web.Framework.UI +{ + public class MenuRenderer : ComponentRenderer + { + protected override void WriteHtmlCore(HtmlTextWriter writer) + { + var route = Component.Route; + + if (route != null) + { + HtmlHelper.RenderAction(route.Action, route.Controller, route.RouteValues); + } + else + { + HtmlHelper.RenderAction("Menu", "Common", new + { + area = "", + name = Component.Name, + template = Component.Template + }); + } + } + + public override void Render() + { + var c = Component; + var route = c.Route; + + if (route != null) + { + HtmlHelper.RenderAction(route.Action, route.Controller, route.RouteValues); + } + else + { + HtmlHelper.RenderAction("Menu", "Common", new { area = "", name = c.Name, template = c.Template }); + } + } + + public override string ToHtmlString() + { + var c = Component; + var route = c.Route; + + if (route != null) + { + return HtmlHelper.Action(route.Action, route.Controller, route.RouteValues).ToHtmlString(); + } + else + { + return HtmlHelper.Action("Menu", "Common", new { area = "", name = c.Name, template = c.Template }).ToHtmlString(); + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/NavigationModel.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/NavigationModel.cs deleted file mode 100644 index 43041f763f..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/NavigationModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using SmartStore.Collections; - -namespace SmartStore.Web.Framework.UI -{ - - public class NavigationModel - { - public TreeNode Root { get; set; } - public IList Path { get; set; } - - public MenuItem SelectedMenuItem - { - get - { - if (Path == null || Path.Count == 0) - return null; - - return Path.Last(); - } - } - } - -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponent.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponent.cs new file mode 100644 index 0000000000..e3982af7fe --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponent.cs @@ -0,0 +1,84 @@ +using System; +using System.Web.Routing; + +namespace SmartStore.Web.Framework.UI +{ + public abstract class NavigatableComponent : Component, INavigatable + { + private string _actionName; + private string _controllerName; + private string _routeName; + private string _url; + + protected NavigatableComponent() + { + this.RouteValues = new RouteValueDictionary(); + } + + public string ActionName + { + get + { + return _actionName; + } + set + { + _actionName = value; + _routeName = (string)(_url = null); + } + } + + public string ControllerName + { + get + { + return _controllerName; + } + set + { + _controllerName = value; + _routeName = (string)(_url = null); + } + } + + public string RouteName + { + get + { + return _routeName; + } + set + { + _routeName = value; + _controllerName = _actionName = (string)(_url = null); + } + } + + public RouteValueDictionary RouteValues + { + get; + set; + } + + public string Url + { + get + { + return _url; + } + set + { + _url = value; + _routeName = _controllerName = (string)(_actionName = null); + this.RouteValues.Clear(); + + } + } + + public ModifiedParameter ModifiedParam + { + get; + protected set; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponentBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponentBuilder.cs index 44be9592b6..5f6a13e8aa 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponentBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigatableComponentBuilder.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Mvc; using System.Web.Routing; using System.Web.WebPages; @@ -10,13 +8,12 @@ namespace SmartStore.Web.Framework.UI { - - public abstract class NavigatableComponentBuilder : ComponentBuilder + public abstract class NavigatableComponentBuilder : ComponentBuilder where TComponent : Component, INavigatable - where TBuilder : ComponentBuilder + where TBuilder : ComponentBuilder { - public NavigatableComponentBuilder(TComponent component, HtmlHelper htmlHelper) + public NavigatableComponentBuilder(TComponent component, HtmlHelper htmlHelper) : base(component, htmlHelper) { } @@ -75,12 +72,12 @@ public TBuilder Url(string value) } - public abstract class NavigatableComponentWithContentBuilder : NavigatableComponentBuilder + public abstract class NavigatableComponentWithContentBuilder : NavigatableComponentBuilder where TComponent : Component, INavigatable, IContentContainer - where TBuilder : ComponentBuilder + where TBuilder : ComponentBuilder { - public NavigatableComponentWithContentBuilder(TComponent component, HtmlHelper htmlHelper) + public NavigatableComponentWithContentBuilder(TComponent component, HtmlHelper htmlHelper) : base(component, htmlHelper) { } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs index a6dca6e4eb..6a8b2f4d8b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Routing; using System.Web.WebPages; +using Newtonsoft.Json; namespace SmartStore.Web.Framework.UI { - - public abstract class NavigationItem : IHtmlAttributesContainer, INavigatable, IHideObjectMembers + [Serializable] + public abstract class NavigationItem : IHtmlAttributesContainer, INavigatable, IHideObjectMembers { private bool _selected; private bool _enabled; @@ -22,23 +21,29 @@ public NavigationItem() this.Visible = true; this.Encoded = true; this.Enabled = true; - this.HtmlAttributes = new RouteValueDictionary(); - this.LinkHtmlAttributes = new RouteValueDictionary(); + this.HtmlAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + this.LinkHtmlAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); this.RouteValues = new RouteValueDictionary(); this.ModifiedParam = new ModifiedParameter(); } - public IDictionary HtmlAttributes { get; private set; } + public IDictionary HtmlAttributes { get; set; } - public IDictionary LinkHtmlAttributes { get; private set; } + public IDictionary LinkHtmlAttributes { get; set; } public string ImageUrl { get; set; } - public string Icon { get; set; } + public int? ImageId { get; set; } + + public string Icon { get; set; } public string Text { get; set; } - public string BadgeText { get; set; } + public bool Rtl { get; set; } + + public string Summary { get; set; } + + public string BadgeText { get; set; } public BadgeStyle BadgeStyle { get; set; } @@ -79,7 +84,8 @@ public bool Enabled } - public string ActionName + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string ActionName { get { @@ -87,12 +93,16 @@ public string ActionName } set { - _actionName = value; - _routeName = (string)(_url = null); + if (_actionName != value) + { + _actionName = value; + _routeName = (string)(_url = null); + } } } - public string ControllerName + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string ControllerName { get { @@ -100,12 +110,16 @@ public string ControllerName } set { - _controllerName = value; - _routeName = (string)(_url = null); + if (_controllerName != value) + { + _controllerName = value; + _routeName = (string)(_url = null); + } } } - public string RouteName + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string RouteName { get { @@ -113,13 +127,17 @@ public string RouteName } set { - _routeName = value; - _controllerName = _actionName = (string)(_url = null); + if (_routeName != value) + { + _routeName = value; + _controllerName = _actionName = (string)(_url = null); + } } } public RouteValueDictionary RouteValues { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Url { get @@ -128,13 +146,16 @@ public string Url } set { - _url = value; - _routeName = _controllerName = (string)(_actionName = null); - this.RouteValues.Clear(); - + if (_url != value) + { + _url = value; + _routeName = _controllerName = (string)(_actionName = null); + this.RouteValues.Clear(); + } } } + [JsonIgnore] public ModifiedParameter ModifiedParam { get; @@ -166,7 +187,7 @@ public NavigationItemWithContent() public bool Ajax { get; set; } - public IDictionary ContentHtmlAttributes { get; private set; } + public IDictionary ContentHtmlAttributes { get; set; } public HelperResult Content { get; set; } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs index a0b6ab86ea..4479edac15 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs @@ -11,7 +11,6 @@ namespace SmartStore.Web.Framework.UI { - public abstract class NavigationItemBuilder : IHideObjectMembers where TItem : NavigationItem where TBuilder : NavigationItemBuilder @@ -19,7 +18,7 @@ public abstract class NavigationItemBuilder : IHideObjectMember protected NavigationItemBuilder(TItem item) { - Guard.ArgumentNotNull(() => item); + Guard.NotNull(item, nameof(item)); this.Item = item; } @@ -128,7 +127,13 @@ public TBuilder ImageUrl(string value) return (this as TBuilder); } - public TBuilder Icon(string value) + public TBuilder ImageId(int? value) + { + this.Item.ImageId = value; + return (this as TBuilder); + } + + public TBuilder Icon(string value) { this.Item.Icon = value; return (this as TBuilder); @@ -140,7 +145,13 @@ public TBuilder Text(string value) return (this as TBuilder); } - public TBuilder Badge(string value, BadgeStyle style = BadgeStyle.Default, bool condition = true) + public TBuilder Summary(string value) + { + this.Item.Summary = value; + return (this as TBuilder); + } + + public TBuilder Badge(string value, BadgeStyle style = BadgeStyle.Secondary, bool condition = true) { if (condition) { @@ -178,7 +189,6 @@ public TItem ToItem() { return this.Item; } - } public abstract class NavigationItemtWithContentBuilder : NavigationItemBuilder @@ -189,7 +199,7 @@ public abstract class NavigationItemtWithContentBuilder : Navig public NavigationItemtWithContentBuilder(TItem item, HtmlHelper htmlHelper) : base(item) { - Guard.ArgumentNotNull(() => htmlHelper); + Guard.NotNull(htmlHelper, nameof(htmlHelper)); HtmlHelper = htmlHelper; } @@ -275,7 +285,5 @@ public TBuilder ContentHtmlAttributes(IDictionary attributes) this.Item.ContentHtmlAttributes.Merge(attributes); return (this as TBuilder); } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/MobilePagerRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/MobilePagerRenderer.cs deleted file mode 100644 index e2c4672218..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/MobilePagerRenderer.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Routing; -using System.Web.UI; - -namespace SmartStore.Web.Framework.UI -{ - - public class MobilePagerRenderer : PagerRenderer - { - - // TODO: (mc) Public util machen - private void AppendCssClass(IDictionary attributes, string @class) - { - attributes.AppendInValue("class", " ", @class); - } - - protected override void RenderItem(HtmlTextWriter writer, PagerItem item) - { - var attrs = new RouteValueDictionary(); - - if (item.State == PagerItemState.Disabled) - { - //writer.AddAttribute("class", "disabled"); - AppendCssClass(attrs, "disabled"); - } - else if (item.State == PagerItemState.Selected) - { - //writer.AddAttribute("class", "active"); - AppendCssClass(attrs, "active"); - } - - if (item.Type == PagerItemType.Text) - { - AppendCssClass(attrs, "shrinked"); - } - - if (Component.Style == PagerStyle.Blog && item.IsNavButton) - { - AppendCssClass(attrs, (item.Type == PagerItemType.PreviousPage || item.Type == PagerItemType.FirstPage) ? "previous" : "next"); - } - - writer.AddAttributes(attrs); - writer.RenderBeginTag("li"); - - if (item.Type == PagerItemType.Page || item.IsNavButton) - { - // write anchor - writer.AddAttribute("href", item.Url); - if (item.IsNavButton) - { - writer.AddAttribute("title", item.Text.AttributeEncode()); - if (Component.Style != PagerStyle.Blog) - { - writer.AddAttribute("rel", "tooltip"); - writer.AddAttribute("class", "pager-nav"); - } - } - else - { - var formatStr = Component.ItemTitleFormatString; - if (!string.IsNullOrEmpty(formatStr)) - { - writer.AddAttribute("title", string.Format(formatStr, item.Text).AttributeEncode()); - writer.AddAttribute("rel", "tooltip"); - } - } - writer.RenderBeginTag("a"); - } - else - { - // write span - writer.RenderBeginTag("span"); - } - - this.RenderItemInnerContent(writer, item); - - writer.RenderEndTag(); // a || span - - writer.RenderEndTag(); // li - } - - - protected override void RenderItemInnerContent(HtmlTextWriter writer, PagerItem item) - { - var type = item.Type; - - switch (type) - { - case PagerItemType.FirstPage: - writer.AddAttribute("class", "fa fa-step-backward"); - break; - case PagerItemType.PreviousPage: - writer.AddAttribute("class", "fa fa-chevron-left"); - writer.WriteEncodedText(item.Text); - break; - case PagerItemType.NextPage: - writer.AddAttribute("class", "fa fa-chevron-right"); - writer.WriteEncodedText(item.Text); - break; - case PagerItemType.LastPage: - writer.AddAttribute("class", "fa fa-step-forward"); - break; - default: - writer.WriteEncodedText(item.Text); - break; - } - } - - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/Pager.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/Pager.cs index 461608b66b..d023c08cb5 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/Pager.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/Pager.cs @@ -9,11 +9,10 @@ namespace SmartStore.Web.Framework.UI { - public enum PagerSize { - Mini, - Small, + Mini, + Small, Medium, Large } @@ -31,15 +30,10 @@ public enum PagerStyle Blog } - public class Pager : Component, INavigatable + public class Pager : NavigatableComponent { private ILocalizationService _localizationService; - private string _actionName; - private string _controllerName; - private string _routeName; - private string _url; - private string _firstButtonText; private string _lastButtonText; private string _nextButtonText; @@ -52,6 +46,7 @@ public Pager() } public Pager(ILocalizationService localizationService) + : base() { _localizationService = localizationService; @@ -64,8 +59,7 @@ public Pager(ILocalizationService localizationService) this.MaxPagesToDisplay = 8; this.Alignment = PagerAlignment.Centered; this.Size = PagerSize.Medium; - this.RouteValues = new RouteValueDictionary(); - this.ModifiedParam = new ModifiedParameter("page"); + this.ModifiedParam = new ModifiedParameter("i"); } public IPageable Model { get; internal set; } @@ -167,74 +161,5 @@ public string CurrentPageText _currentPageText = value; } } - - - public string ActionName - { - get - { - return _actionName; - } - set - { - _actionName = value; - _routeName = (string)(_url = null); - } - } - - public string ControllerName - { - get - { - return _controllerName; - } - set - { - _controllerName = value; - _routeName = (string)(_url = null); - } - } - - public string RouteName - { - get - { - return _routeName; - } - set - { - _routeName = value; - _controllerName = _actionName = (string)(_url = null); - } - } - - public RouteValueDictionary RouteValues - { - get; - private set; - } - - public string Url - { - get - { - return _url; - } - set - { - _url = value; - _routeName = _controllerName = (string)(_actionName = null); - this.RouteValues.Clear(); - - } - } - - public ModifiedParameter ModifiedParam - { - get; - private set; - } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerBuilder.cs index 544e7dce73..1164c9fa3c 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerBuilder.cs @@ -1,129 +1,122 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Mvc; using SmartStore.Core; namespace SmartStore.Web.Framework.UI { - - public class PagerBuilder : NavigatableComponentBuilder + public class PagerBuilder : NavigatableComponentBuilder, TModel> { - - public PagerBuilder(Pager component, HtmlHelper htmlHelper) + public PagerBuilder(Pager component, HtmlHelper htmlHelper) : base(component, htmlHelper) { } - public PagerBuilder Model(IPageable value) + public PagerBuilder Model(IPageable value) { base.Component.Model = value; return this; } - public PagerBuilder Alignment(PagerAlignment value) + public PagerBuilder Alignment(PagerAlignment value) { base.Component.Alignment = value; return this; } - public PagerBuilder Size(PagerSize value) + public PagerBuilder Size(PagerSize value) { base.Component.Size = value; return this; } - public PagerBuilder Style(PagerStyle value) + public PagerBuilder Style(PagerStyle value) { base.Component.Style = value; return this; } - public PagerBuilder ShowFirst(bool value) + public PagerBuilder ShowFirst(bool value) { base.Component.ShowFirst = value; return this; } - public PagerBuilder ShowLast(bool value) + public PagerBuilder ShowLast(bool value) { base.Component.ShowLast = value; return this; } - public PagerBuilder ShowNext(bool value) + public PagerBuilder ShowNext(bool value) { base.Component.ShowNext = value; return this; } - public PagerBuilder ShowPrevious(bool value) + public PagerBuilder ShowPrevious(bool value) { base.Component.ShowPrevious = value; return this; } - public PagerBuilder ShowSummary(bool value) + public PagerBuilder ShowSummary(bool value) { base.Component.ShowSummary = value; return this; } - public PagerBuilder ShowPaginator(bool value) + public PagerBuilder ShowPaginator(bool value) { base.Component.ShowPaginator = value; return this; } - public PagerBuilder MaxPagesToDisplay(int value) + public PagerBuilder MaxPagesToDisplay(int value) { base.Component.MaxPagesToDisplay = value; return this; } - public PagerBuilder SkipActiveState(bool value) + public PagerBuilder SkipActiveState(bool value) { base.Component.SkipActiveState = value; return this; } - public PagerBuilder FirstButtonText(string value) + public PagerBuilder FirstButtonText(string value) { base.Component.FirstButtonText = value; return this; } - public PagerBuilder LastButtonText(string value) + public PagerBuilder LastButtonText(string value) { base.Component.LastButtonText = value; return this; } - public PagerBuilder NextButtonText(string value) + public PagerBuilder NextButtonText(string value) { base.Component.NextButtonText = value; return this; } - public PagerBuilder PreviousButtonText(string value) + public PagerBuilder PreviousButtonText(string value) { base.Component.PreviousButtonText = value; return this; } - public PagerBuilder CurrentPageText(string value) + public PagerBuilder CurrentPageText(string value) { base.Component.CurrentPageText = value; return this; } - public PagerBuilder ItemTitleFormatString(string value) + public PagerBuilder ItemTitleFormatString(string value) { base.Component.ItemTitleFormatString = value; return this; } - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs index 7b4416eb81..7e024d559d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs @@ -1,19 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.UI; -using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Services.Localization; -using SmartStore.Core.Infrastructure; namespace SmartStore.Web.Framework.UI -{ - +{ public class PagerRenderer : ComponentRenderer { - public PagerRenderer() { } @@ -165,134 +158,6 @@ protected virtual int GetLastPageIndex() return (c.Model.PageIndex + num); } - #region Other renderers - - ///// - ///// Can be overridden in a custom renderer in order to apply - ///// a custom numbering sequence. - ///// - ///// - //protected virtual void AddPageItemsList3(List items) - //{ - // var pager = base.Component; - // var pageNumber = pager.Model.PageNumber; - // var pageCount = pager.Model.TotalPages; - - // int start = 1; - // int end = pageCount; - - // if (pageCount > pager.MaxPagesToDisplay) - // { - // int middle = (int)Math.Ceiling(pager.MaxPagesToDisplay / 2d) - 1; // 5 - // int below = (pageNumber - middle); // 8-5=3 - // int above = (pageNumber + middle); // 8+5=13 - - // if (below < 4) - // { - // above = pager.MaxPagesToDisplay; // 12 - // below = 1; - // } - // else if (above > (pageCount - 4)) - // { - // above = pageCount; // 20 - // below = (pageCount - pager.MaxPagesToDisplay); // 8 - // } - - // start = below; // 1 - // end = above; // 12 - // } - - // if (start > 3) - // { - // items.Add(new PagerItem("1", GenerateUrl(1))); - // items.Add(new PagerItem("2", GenerateUrl(2))); - // items.Add(new PagerItem("...", "", PagerItemType.Text)); - // } - - // for (int i = start; i <= end; i++) - // { - // var item = new PagerItem(i.ToString(), GenerateUrl(i)); - // if (i == pageNumber) - // { - // item.State = PagerItemState.Selected; - // } - // items.Add(item); - // } - - // if (end < (pageCount - 3)) - // { - // items.Add(new PagerItem("...", "", PagerItemType.Text)); - // items.Add(new PagerItem((pageCount - 1).ToString(), GenerateUrl(pageCount - 1))); - // items.Add(new PagerItem(pageCount.ToString(), GenerateUrl(pageCount))); - // } - //} - - ///// - ///// Can be overridden in a custom renderer in order to apply - ///// a custom numbering sequence. - ///// - ///// - //protected virtual void AddPageItemsList2(List items) - //{ - // var pager = base.Component; - // var pageNumber = pager.Model.PageNumber; - // var pageCount = pager.Model.TotalPages; - - // List newList = new List(); - - // int p = 0; - // int q = 0; - - // // the total magnitude ( e.g. 3 for 1.300 entries [ 10^3 = 1.000 ] - // int m = (int)Math.Log10(pageCount); - - // // first item is always the selected currentIndex - // newList.Add(new PagerItem(pageNumber.ToString(), GenerateUrl(pageNumber), PagerItemType.Page, PagerItemState.Selected)); - - // // -2, -1, ..., +1, +2 from the current index - // for (int i = 1; i <= 3; i++) - // { - // p = pageNumber + i; - // if (p <= pageCount) - // newList.Add(new PagerItem(p.ToString(), GenerateUrl(p))); - - // p = pageNumber - i; - // if (p > 0) - // newList.Insert(0, new PagerItem(p.ToString(), GenerateUrl(p))); - // } - - // // ... -1.000, -500, -100, -50, -10, ..., +10, +50, +100, +500, +1.000 .. - // for (var i = 1; i <= m; i++) - // { - // q = (int)Math.Pow(10, i); - // if (i > 1) - // { - // // if > 10, do halfs .. (50, 500, 5000) - // p = pageNumber + q / 2; - // if (p < pageCount) - // newList.Add(new PagerItem(p.ToString(), GenerateUrl(p))); - - // p = pageNumber - q / 2; - // if (p > 1) - // newList.Insert(0, new PagerItem(p.ToString(), GenerateUrl(p))); - // } - - // // do full counts (10, 100, 1.000) - // p = pageNumber + q; - // if (p < pageCount) - // newList.Add(new PagerItem(p.ToString(), GenerateUrl(p))); - - // p = pageNumber - q; - // if (p > 1) - // newList.Insert(0, new PagerItem(p.ToString(), GenerateUrl(p))); - // } - - // items.AddRange(newList); - //} - - #endregion - - protected virtual string GenerateUrl(int pageNumber) { var pager = base.Component; @@ -310,31 +175,59 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) var items = CreateItemList(); - AppendCssClass(pager.HtmlAttributes, pager.Style == PagerStyle.Pagination ? "pagination" : "pager"); - // Size - if (pager.Size != PagerSize.Medium) - { - AppendCssClass(pager.HtmlAttributes, "pagination-{0}".FormatInvariant(pager.Size.ToString().ToLower())); - } - // Alignment - if (pager.Alignment != PagerAlignment.Left) - { - AppendCssClass(pager.HtmlAttributes, "pagination-{0}".FormatInvariant(pager.Alignment.ToString().ToLower())); - } - writer.AddAttributes(pager.HtmlAttributes); - //writer.AddAttribute("id", pager.Id); - writer.RenderBeginTag("div"); + // Alignment + if (pager.Alignment == PagerAlignment.Right) + { + pager.HtmlAttributes.AppendCssClass("text-right"); + } + else if (pager.Alignment == PagerAlignment.Centered) + { + pager.HtmlAttributes.AppendCssClass("text-center"); + } + + writer.AddAttributes(pager.HtmlAttributes); + writer.AddAttribute("id", pager.Id); + writer.AddAttribute("aria-label", "Page navigation"); + writer.RenderBeginTag("nav"); if (pager.ShowSummary && pager.Model.TotalPages > 1) { - writer.AddAttribute("class", "pagination-summary pull-left"); + writer.AddAttribute("class", "pagination-summary float-left"); writer.RenderBeginTag("div"); writer.WriteEncodedText(pager.CurrentPageText.FormatInvariant(pager.Model.PageNumber, pager.Model.TotalPages, pager.Model.TotalCount)); writer.RenderEndTag(); // div } - writer.AddAttribute("class", "unstyled"); - writer.RenderBeginTag("ul"); + var ulAttrs = new RouteValueDictionary(); + + ulAttrs.AppendCssClass(pager.Style == PagerStyle.Pagination ? "pagination" : "pagination" /* formerly "pager" */); // TODO: (mc) Seems there's no "pager" in BS4 anymore + + // Size + if (pager.Size == PagerSize.Large) + { + ulAttrs.AppendCssClass("pagination-lg"); + } + else if (pager.Size == PagerSize.Small) + { + ulAttrs.AppendCssClass("pagination-sm"); + } + else if (pager.Size == PagerSize.Mini) + { + ulAttrs.AppendCssClass("pagination-xs"); + } + + // BS 4 alignment + if (pager.Alignment == PagerAlignment.Centered) + { + ulAttrs.AppendCssClass("justify-content-center"); + } + else if (pager.Alignment == PagerAlignment.Right) + { + ulAttrs.AppendCssClass("justify-content-end"); + } + + writer.AddAttributes(ulAttrs); + writer.RenderBeginTag("ul"); foreach (var item in items) { @@ -343,7 +236,6 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) writer.RenderEndTag(); // ul writer.RenderEndTag(); // div - } protected virtual void RenderItem(HtmlTextWriter writer, PagerItem item) @@ -352,25 +244,26 @@ protected virtual void RenderItem(HtmlTextWriter writer, PagerItem item) if (item.State == PagerItemState.Disabled) { - //writer.AddAttribute("class", "disabled"); - AppendCssClass(attrs, "disabled"); + attrs.AppendCssClass("disabled"); } else if (item.State == PagerItemState.Selected) { - //writer.AddAttribute("class", "active"); - AppendCssClass(attrs, "active"); + attrs.AppendCssClass("active"); } if (item.Type == PagerItemType.Text) { - AppendCssClass(attrs, "shrinked"); + attrs.AppendCssClass("shrinked"); } if (Component.Style == PagerStyle.Blog && item.IsNavButton) { - AppendCssClass(attrs, (item.Type == PagerItemType.PreviousPage || item.Type == PagerItemType.FirstPage) ? "previous" : "next"); + // TODO: (mg) BS4 doesn't seem to support this anymore + attrs.AppendCssClass((item.Type == PagerItemType.PreviousPage || item.Type == PagerItemType.FirstPage) ? "previous" : "next"); } + attrs.AppendCssClass("page-item"); + writer.AddAttributes(attrs); writer.RenderBeginTag("li"); @@ -381,10 +274,12 @@ protected virtual void RenderItem(HtmlTextWriter writer, PagerItem item) if (item.IsNavButton) { writer.AddAttribute("title", item.Text.AttributeEncode()); - if (Component.Style != PagerStyle.Blog) + writer.AddAttribute("aria-label", item.Text.AttributeEncode()); + writer.AddAttribute("tab-index", "-1"); + if (Component.Style != PagerStyle.Blog) { writer.AddAttribute("rel", "tooltip"); - writer.AddAttribute("class", "pager-nav"); + writer.AddAttribute("class", "page-link page-nav"); } } else @@ -396,12 +291,16 @@ protected virtual void RenderItem(HtmlTextWriter writer, PagerItem item) writer.AddAttribute("rel", "tooltip"); } } + + writer.AddAttribute("class", "page-link"); + writer.RenderBeginTag("a"); } else { - // write span - writer.RenderBeginTag("span"); + // write span + writer.AddAttribute("class", "page-link"); + writer.RenderBeginTag("span"); } this.RenderItemInnerContent(writer, item); @@ -418,16 +317,16 @@ protected virtual void RenderItemInnerContent(HtmlTextWriter writer, PagerItem i switch (type) { case PagerItemType.FirstPage: - writer.AddAttribute("class", "fa fa-step-backward"); + writer.AddAttribute("class", "fa fa-angle-double-left"); break; case PagerItemType.PreviousPage: - writer.AddAttribute("class", "fa fa-chevron-left"); + writer.AddAttribute("class", "fa fa-angle-left"); break; case PagerItemType.NextPage: - writer.AddAttribute("class", "fa fa-chevron-right"); + writer.AddAttribute("class", "fa fa-angle-right"); break; case PagerItemType.LastPage: - writer.AddAttribute("class", "fa fa-step-forward"); + writer.AddAttribute("class", "fa fa-angle-double-right"); break; default: writer.WriteEncodedText(item.Text); @@ -451,20 +350,6 @@ protected virtual void RenderItemInnerContent(HtmlTextWriter writer, PagerItem i } } } - - // TODO: (mc) Public util machen - private void AppendCssClass(IDictionary attributes, string @class) - { - attributes.AppendInValue("class", " ", @class); - } - - // TODO: (mc) Public util machen - private void PrependCssClass(IDictionary attributes, string @class) - { - attributes.PrependInValue("class", " ", @class); - } - - } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/Tab.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/Tab.cs index 3cd55fa93f..6b0cc233a0 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/Tab.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/Tab.cs @@ -5,7 +5,6 @@ namespace SmartStore.Web.Framework.UI { - [Serializable] public class SelectedTabInfo { @@ -21,11 +20,10 @@ public enum TabPull public class Tab : NavigationItemWithContent { - public Tab() { - // [...] - } + // [...] + } public string Name { @@ -38,7 +36,5 @@ public TabPull Pull get; set; } - - } - + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabBuilder.cs index 513639d3f2..48443f9690 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabBuilder.cs @@ -2,10 +2,8 @@ namespace SmartStore.Web.Framework.UI { - public class TabBuilder : NavigationItemtWithContentBuilder { - public TabBuilder(Tab item, HtmlHelper htmlHelper) : base(item, htmlHelper) { @@ -22,8 +20,5 @@ public TabBuilder Pull(TabPull value) this.Item.Pull = value; return this; } - - - } - + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabFactory.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabFactory.cs index 99f631bcc0..7fee6c00af 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabFactory.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabFactory.cs @@ -13,7 +13,7 @@ public class TabFactory : IHideObjectMembers public TabFactory(IList items, HtmlHelper htmlHelper) { - Guard.ArgumentNotNull(() => htmlHelper); + Guard.NotNull(htmlHelper, nameof(htmlHelper)); _items = items; _htmlHelper = htmlHelper; @@ -25,5 +25,21 @@ public virtual TabBuilder Add() _items.Add(item); return new TabBuilder(item, _htmlHelper); } + + public virtual TabBuilder Insert(int index) + { + var item = new Tab(); + + if (_items.Count > index) + { + _items.Insert(index, item); + } + else + { + _items.Add(item); + } + + return new TabBuilder(item, _htmlHelper); + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStrip.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStrip.cs index bcc330ce39..7ab56a8bdc 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStrip.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStrip.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Web.WebPages; namespace SmartStore.Web.Framework.UI { - public enum TabsPosition { Top, @@ -17,17 +17,18 @@ public enum TabsPosition public enum TabsStyle { Tabs, - Pills + Pills, + Material } public class TabStrip : Component { - public TabStrip() { this.Items = new List(); this.Fade = true; this.SmartTabSelection = true; + this.Breakpoint = " Items @@ -36,19 +37,31 @@ public List Items private set; } - public TabsPosition Position + public bool IsResponsive { get; set; } - public TabsStyle Style + public string Breakpoint + { + get; + set; + } + + public HelperResult TabContentHeaderContent + { + get; + set; + } + + public TabsPosition Position { get; set; } - public bool Stacked + public TabsStyle Style { get; set; @@ -97,8 +110,6 @@ public override bool NameIsRequired return true; } } - - } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripBuilder.cs index 14b7458c83..390a7732d4 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripBuilder.cs @@ -1,71 +1,95 @@ using System; using System.Web.Mvc; +using System.Web.WebPages; namespace SmartStore.Web.Framework.UI { - public class TabStripBuilder : ComponentBuilder + public class TabStripBuilder : ComponentBuilder, TModel> { - public TabStripBuilder(TabStrip Component, HtmlHelper htmlHelper) + public TabStripBuilder(TabStrip Component, HtmlHelper htmlHelper) : base(Component, htmlHelper) { } - public TabStripBuilder Items(Action addAction) + public TabStripBuilder Items(Action addAction) { var factory = new TabFactory(base.Component.Items, this.HtmlHelper); addAction(factory); return this; } - public TabStripBuilder Position(TabsPosition value) + public TabStripBuilder Responsive(bool value, string breakpoint = " TabContentHeaderContent(string value) + { + if (value.IsEmpty()) + { + // do nothing + return this; + } + + return this.TabContentHeaderContent(x => new HelperResult(writer => writer.Write(value))); + } + + public TabStripBuilder TabContentHeaderContent(Func value) + { + return this.TabContentHeaderContent(value(null)); + } + + public TabStripBuilder TabContentHeaderContent(HelperResult value) + { + base.Component.TabContentHeaderContent = value; + return this; + } + + public TabStripBuilder Position(TabsPosition value) { base.Component.Position = value; return this; } - public TabStripBuilder Style(TabsStyle value) + public TabStripBuilder Style(TabsStyle value) { base.Component.Style = value; return this; } - public TabStripBuilder Stacked(bool value) - { - base.Component.Stacked = value; - return this; - } - - public TabStripBuilder Fade(bool value) + public TabStripBuilder Fade(bool value) { base.Component.Fade = value; return this; } - public TabStripBuilder SmartTabSelection(bool value) + public TabStripBuilder SmartTabSelection(bool value) { base.Component.SmartTabSelection = value; return this; } - public TabStripBuilder OnAjaxBegin(string value) + public TabStripBuilder OnAjaxBegin(string value) { base.Component.OnAjaxBegin = value; return this; } - public TabStripBuilder OnAjaxSuccess(string value) + public TabStripBuilder OnAjaxSuccess(string value) { base.Component.OnAjaxSuccess = value; return this; } - public TabStripBuilder OnAjaxFailure(string value) + public TabStripBuilder OnAjaxFailure(string value) { base.Component.OnAjaxFailure = value; return this; } - public TabStripBuilder OnAjaxComplete(string value) + public TabStripBuilder OnAjaxComplete(string value) { base.Component.OnAjaxComplete = value; return this; diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs index 4c235ab3c1..eb0250e059 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs @@ -5,8 +5,6 @@ using System.Text; using System.Web.UI; using System.Web.Mvc; -using System.Web.Mvc.Html; -using System.Web.Routing; using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Framework.UI @@ -26,6 +24,7 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) var hasContent = tab.Items.Any(x => x.Content != null || x.Ajax); var isTabbable = tab.Position != TabsPosition.Top; + var isStacked = tab.Position == TabsPosition.Left || tab.Position == TabsPosition.Right; var urlHelper = new UrlHelper(this.ViewContext.RequestContext); if (tab.Items.Count == 0) @@ -44,6 +43,11 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) tab.HtmlAttributes.Add("data-tabselector-href", urlHelper.Action("SetSelectedTab", "Common", new { area = "admin" })); } + if (isStacked) + { + tab.HtmlAttributes.AppendCssClass("row"); + } + if (tab.OnAjaxBegin.HasValue()) { tab.HtmlAttributes.Add("data-ajax-onbegin", tab.OnAjaxBegin); @@ -64,6 +68,12 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) tab.HtmlAttributes.Add("data-ajax-oncomplete", tab.OnAjaxComplete); } + if (tab.IsResponsive) + { + tab.HtmlAttributes.AppendCssClass("nav-responsive"); + tab.HtmlAttributes.Add("data-breakpoint", tab.Breakpoint); + } + writer.AddAttributes(tab.HtmlAttributes); writer.RenderBeginTag("div"); // root div @@ -71,12 +81,32 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) if (tab.Position == TabsPosition.Below && hasContent) RenderTabContent(writer); + if (isStacked) + { + writer.AddAttribute("class", "col-lg-auto nav-aside"); + writer.RenderBeginTag("aside"); // opening left/right tabs col + } + // Tabs var ulAttrs = new Dictionary(); - ulAttrs.AppendCssClass("nav nav-{0}".FormatInvariant(tab.Style.ToString().ToLower())); - if (tab.Stacked) + ulAttrs.AppendCssClass("nav"); + + if (tab.Style == TabsStyle.Tabs) + { + ulAttrs.AppendCssClass("nav-tabs"); + } + else if (tab.Style == TabsStyle.Pills) { - ulAttrs.AppendCssClass("nav-stacked"); + ulAttrs.AppendCssClass("nav-pills"); + } + else if (tab.Style == TabsStyle.Material) + { + ulAttrs.AppendCssClass("nav-tabs nav-tabs-line"); + } + + if (isStacked) + { + ulAttrs.AppendCssClass("flex-row flex-lg-column"); } writer.AddAttributes(ulAttrs); @@ -104,16 +134,35 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) writer.RenderEndTag(); // ul } + if (isStacked) + { + writer.RenderEndTag(); // closing left/right tabs col + } + + // TODO: (mc) render right positioned tabs also if (tab.Position != TabsPosition.Below && hasContent) + { + if (isStacked) + { + writer.AddAttribute("class", "col-lg nav-content"); + writer.RenderBeginTag("div"); // opening left/right content col + } + RenderTabContent(writer); + if (isStacked) + { + writer.RenderEndTag(); // closing left/right content col + } + } + if (selector != null) { writer.WriteLine( @"".FormatInvariant(selector)); @@ -127,7 +176,12 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) } } - writer.RenderEndTag(); // div.tabbable + writer.RenderEndTag(); // div.tabbable + + if (tab.IsResponsive /* && tab.TabContentHeaderContent != null*/) + { + writer.WriteLine(@"".FormatInvariant(tab.Id)); + } } } @@ -150,8 +204,7 @@ private string TrySelectRememberedTab() if (tab.Id.IsEmpty()) return null; - var model = ViewContext.ViewData.Model as EntityModelBase; - if (model != null && model.Id == 0) + if (ViewContext.ViewData.Model is EntityModelBase model && model.Id == 0) { // it's a "create" operation: don't select return null; @@ -176,7 +229,7 @@ private string TrySelectRememberedTab() if (tabToSelect.Ajax && tabToSelect.Content == null) { - return ".nav a[data-ajax-url][href=#{0}]".FormatInvariant(rememberedTab.TabId); + return ".nav a[data-ajax-url][href='#{0}']".FormatInvariant(rememberedTab.TabId); } } } @@ -209,13 +262,28 @@ protected virtual void RenderTabContent(HtmlTextWriter writer) writer.AddAttribute("class", "tab-content"); writer.RenderBeginTag("div"); + + // Tab content header + if (tab.IsResponsive && tab.TabContentHeaderContent != null) + { + //writer.WriteLine(item.Content.ToHtmlString()); + writer.AddAttribute("class", "tab-content-header"); + writer.RenderBeginTag("div"); + { + writer.WriteLine(tab.TabContentHeaderContent.ToHtmlString()); + } + writer.RenderEndTag(); // div.tab-content-header + } + + // All tab items int i = 1; foreach (var item in tab.Items) { this.RenderItemContent(writer, item, i); i++; } - writer.RenderEndTag(); // div + + writer.RenderEndTag(); // div.tab-content } protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int index) @@ -223,22 +291,17 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind string temp = ""; string loadedTabName = null; - //
  • {text}
  • - if (item.Selected) - { - item.HtmlAttributes.AppendCssClass("active"); - } - else + //
  • {text}
  • + item.HtmlAttributes.AppendCssClass("nav-item"); // .active for BS2 + + if (!item.Selected && !item.Visible) { - if (!item.Visible) - { - item.HtmlAttributes.AppendCssClass("hide"); - } + item.HtmlAttributes.AppendCssClass("d-none"); } if (item.Pull == TabPull.Right) { - item.HtmlAttributes.AppendCssClass("pull-right"); + item.HtmlAttributes.AppendCssClass("float-right"); } writer.AddAttributes(item.HtmlAttributes); @@ -246,12 +309,13 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.RenderBeginTag("li"); { var itemId = "#" + BuildItemId(item, index); - - if (item.Content != null) + + if (item.Content != null) { writer.AddAttribute("href", itemId); writer.AddAttribute("data-toggle", "tab"); writer.AddAttribute("data-loaded", "true"); + writer.AddAttribute("class", "nav-link" + (item.Selected ? " active" : "")); loadedTabName = GetTabName(item) ?? itemId; } else @@ -270,7 +334,8 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.AddAttribute("href", itemId); writer.AddAttribute("data-ajax-url", url); writer.AddAttribute("data-toggle", "tab"); - } + writer.AddAttribute("class", "nav-link" + (item.Selected ? " active" : "")); + } else { writer.AddAttribute("href", url); @@ -301,38 +366,38 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.RenderBeginTag("img"); writer.RenderEndTag(); // img } - //writer.WriteEncodedText(item.Text); + + // Caption + writer.AddAttribute("class", "tab-caption"); + writer.RenderBeginTag("span"); + writer.WriteEncodedText(item.Text); + writer.RenderEndTag(); // Badge if (item.BadgeText.HasValue()) { - //writer.Write(" "); - - // caption - writer.AddAttribute("class", "tab-caption"); - writer.RenderBeginTag("span"); - writer.WriteEncodedText(item.Text); - writer.RenderEndTag(); // span > badge - - // label - temp = "label"; - if (item.BadgeStyle != BadgeStyle.Default) - { - temp += " label-" + item.BadgeStyle.ToString().ToLower(); - } + // Label/badge + temp = "ml-2 badge"; + temp += " badge-" + item.BadgeStyle.ToString().ToLower(); if (base.Component.Position == TabsPosition.Left) { - temp += " pull-right"; // looks nicer + temp += " float-right"; // looks nicer } writer.AddAttribute("class", temp); writer.RenderBeginTag("span"); writer.WriteEncodedText(item.BadgeText); writer.RenderEndTag(); // span > badge } - else + + // Nav link short summary for collapsed state + if (this.Component.IsResponsive && item.Summary.HasValue()) { - writer.WriteEncodedText(item.Text); + writer.AddAttribute("class", "nav-link-summary"); + writer.RenderBeginTag("span"); + writer.WriteEncodedText(item.Summary); + writer.RenderEndTag(); } + writer.RenderEndTag(); // a } writer.RenderEndTag(); // li @@ -343,8 +408,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind private string GetTabName(Tab tab) { - object value; - if (tab.LinkHtmlAttributes.TryGetValue("data-tab-name", out value)) + if (tab.LinkHtmlAttributes.TryGetValue("data-tab-name", out object value)) { return value.ToString(); } @@ -355,6 +419,7 @@ protected virtual void RenderItemContent(HtmlTextWriter writer, Tab item, int in { //
    {content}
    item.ContentHtmlAttributes.AppendCssClass("tab-pane"); + item.ContentHtmlAttributes.Add("role", "tabpanel"); if (base.Component.Fade) { item.ContentHtmlAttributes.AppendCssClass("fade"); @@ -363,7 +428,7 @@ protected virtual void RenderItemContent(HtmlTextWriter writer, Tab item, int in { if (base.Component.Fade) { - item.ContentHtmlAttributes.AppendCssClass("in"); + item.ContentHtmlAttributes.AppendCssClass("show"); } item.ContentHtmlAttributes.AppendCssClass("active"); } @@ -387,8 +452,5 @@ private string BuildItemId(Tab item, int index) } return "{0}-{1}".FormatInvariant(this.Component.Id, index); } - - } - } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ViewBasedComponentRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ViewBasedComponentRenderer.cs new file mode 100644 index 0000000000..0ef84c00a0 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ViewBasedComponentRenderer.cs @@ -0,0 +1,36 @@ +using System.Web.Mvc.Html; + +namespace SmartStore.Web.Framework.UI +{ + public class ViewBasedComponentRenderer : ComponentRenderer where TComponent : Component + { + private readonly string _viewName; + + public ViewBasedComponentRenderer() + : this(typeof(TComponent).Name) + { + } + + public ViewBasedComponentRenderer(string viewName) + { + Guard.NotEmpty(viewName, nameof(viewName)); + + _viewName = viewName; + } + + protected virtual string GetViewName() + { + return "Components/" + _viewName; + } + + public override void Render() + { + HtmlHelper.RenderPartial(GetViewName(), Component); + } + + public override string ToHtmlString() + { + return HtmlHelper.Partial(GetViewName(), Component).ToString(); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/Window.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/Window.cs index 4df56f3852..154a7e57d3 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/Window.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/Window.cs @@ -1,31 +1,43 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Web.Routing; using System.Web.WebPages; namespace SmartStore.Web.Framework.UI { - - public class Window : Component + public enum WindowSize + { + Small, + Medium, + Large, + Flex, + FlexSmall + } + + public class Window : Component { - public Window() { - this.Fade = true; - this.Modal = true; - this.Visible = true; + this.Size = WindowSize.Medium; + this.Fade = true; + this.Focus = true; this.BackDrop = true; this.ShowClose = true; + this.Show = true; this.CloseOnEscapePress = true; - } + this.CloseOnBackdropClick = true; + this.RenderAtPageEnd = true; + this.BodyHtmlAttributes = new RouteValueDictionary(); + } public override bool NameIsRequired { get { return true; } } - public string Title { get; set; } + public WindowSize Size { get; set; } + + public string Title { get; set; } public HelperResult Content { get; set; } @@ -33,22 +45,24 @@ public override bool NameIsRequired public HelperResult FooterContent { get; set; } - public bool Modal { get; set; } - public bool Fade { get; set; } - public bool BackDrop { get; set; } + public bool Focus { get; set; } + + public bool Show { get; set; } - public bool Visible { get; set; } + public bool BackDrop { get; set; } public bool ShowClose { get; set; } - public bool CloseOnEscapePress { get; set; } + public bool CenterVertically { get; set; } - public int? Width { get; set; } + public bool CloseOnEscapePress { get; set; } - public int? Height { get; set; } + public bool CloseOnBackdropClick { get; set; } - } + public bool RenderAtPageEnd { get; set; } + public IDictionary BodyHtmlAttributes { get; set; } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowBuilder.cs index b4b0b8271f..0b8e9d938b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowBuilder.cs @@ -1,113 +1,132 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.Mvc; using System.Web.WebPages; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.UI { - - public class WindowBuilder : ComponentBuilder + public class WindowBuilder : ComponentBuilder, TModel> { - - public WindowBuilder(Window Component, HtmlHelper htmlHelper) + public WindowBuilder(Window Component, HtmlHelper htmlHelper) : base(Component, htmlHelper) { } - public WindowBuilder Title(string value) + public WindowBuilder Size(WindowSize value) + { + base.Component.Size = value; + return this; + } + + public WindowBuilder Title(string value) { base.Component.Title = value; return this; } - public WindowBuilder LoadContentFrom(string url) + public WindowBuilder LoadContentFrom(string url) { base.Component.ContentUrl = url; return this; } - public WindowBuilder Content(string value) + public WindowBuilder Content(string value) { return this.Content(x => new HelperResult(writer => writer.Write(value))); } - public WindowBuilder Content(Func value) + public WindowBuilder Content(Func value) { return this.Content(value(null)); } - public WindowBuilder Content(HelperResult value) + public WindowBuilder Content(HelperResult value) { this.Component.Content = value; return this; } - public WindowBuilder FooterContent(string value) + public WindowBuilder FooterContent(string value) { return this.FooterContent(x => new HelperResult(writer => writer.Write(value))); } - public WindowBuilder FooterContent(Func value) + public WindowBuilder FooterContent(Func value) { return this.FooterContent(value(null)); } - public WindowBuilder FooterContent(HelperResult value) + public WindowBuilder FooterContent(HelperResult value) { this.Component.FooterContent = value; return this; } - public WindowBuilder Modal(bool value) - { - base.Component.Modal = value; - return this; - } - - public WindowBuilder Fade(bool value) + public WindowBuilder Fade(bool value) { base.Component.Fade = value; return this; } - public WindowBuilder BackDrop(bool value) + public WindowBuilder BackDrop(bool value) { base.Component.BackDrop = value; return this; } - public WindowBuilder Visible(bool value) - { - base.Component.Visible = value; - return this; - } - - public WindowBuilder ShowClose(bool value) + public WindowBuilder ShowClose(bool value) { base.Component.ShowClose = value; return this; } - public WindowBuilder CloseOnEscapePress(bool value) - { - base.Component.CloseOnEscapePress = value; - return this; - } + public WindowBuilder Focus(bool value) + { + base.Component.Focus = value; + return this; + } - public WindowBuilder Width(int value) - { - base.Component.Width = value; - return this; - } + public WindowBuilder CenterVertically(bool value) + { + base.Component.CenterVertically = value; + return this; + } + + public WindowBuilder Show(bool value) + { + base.Component.Show = value; + return this; + } - public WindowBuilder Height(int value) + public WindowBuilder CloseOnEscapePress(bool value) { - base.Component.Height = value; + base.Component.CloseOnEscapePress = value; return this; } - } - -} + public WindowBuilder CloseOnBackdropClick(bool value) + { + base.Component.CloseOnBackdropClick = value; + return this; + } + + public WindowBuilder RenderAtPageEnd(bool value) + { + base.Component.RenderAtPageEnd = value; + return this; + } + + public WindowBuilder BodyHtmlAttributes(object attributes) + { + return this.BodyHtmlAttributes(CommonHelper.ObjectToDictionary(attributes)); + } + + public WindowBuilder BodyHtmlAttributes(IDictionary attributes) + { + base.Component.BodyHtmlAttributes.Clear(); + base.Component.BodyHtmlAttributes.Merge(attributes); + return this; + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs index 13144694b2..86d49ab9eb 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs @@ -1,93 +1,119 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Web.UI; -using System.Web.Mvc; namespace SmartStore.Web.Framework.UI { - - public class WindowRenderer : ComponentRenderer + // TODO: (mc) make modal window renderer BS4 ready (after backend has been updated to BS4) + public class WindowRenderer : ComponentRenderer { - - protected override void WriteHtmlCore(HtmlTextWriter writer) + public override void Render() + { + if (Component.RenderAtPageEnd) + { + using (this.HtmlHelper.BeginZoneContent("end")) + { + base.Render(); + } + } + else + { + base.Render(); + } + } + + protected override void WriteHtmlCore(HtmlTextWriter writer) { var win = base.Component; win.HtmlAttributes.AppendCssClass("modal"); - win.HtmlAttributes["role"] = "dialog"; - win.HtmlAttributes["tabindex"] = "-1"; - win.HtmlAttributes["aria-labelledby"] = win.Id + "Label"; - - if (win.Width.GetValueOrDefault() > 0) - { - win.HtmlAttributes["style"] = "width:{0}px; margin-left:-{1}px".FormatInvariant(win.Width.Value, Math.Ceiling((double)(win.Width.Value / 2))); - } - - if (!win.Visible) - { - win.HtmlAttributes["aria-hidden"] = "true"; - win.HtmlAttributes.AppendCssClass("hide"); - } - else - { - win.HtmlAttributes["aria-hidden"] = "false"; - } + win.HtmlAttributes["role"] = "dialog"; + win.HtmlAttributes["tabindex"] = "-1"; + win.HtmlAttributes["aria-hidden"] = "true"; + win.HtmlAttributes["aria-labelledby"] = win.Id + "Label"; - if (win.Fade) + if (win.Fade) { win.HtmlAttributes.AppendCssClass("fade"); - if (win.Visible) - win.HtmlAttributes.AppendCssClass("in"); } - // other options - win.HtmlAttributes["data-backdrop"] = win.BackDrop.ToString().ToLower(); - win.HtmlAttributes["data-keyboard"] = win.CloseOnEscapePress.ToString().ToLower(); - //win.HtmlAttributes["data-show"] = win.Visible.ToString().ToLower(); - if (win.ContentUrl.HasValue()) - { - win.HtmlAttributes["data-remote"] = win.ContentUrl; - } + // Other options + win.HtmlAttributes["data-keyboard"] = win.CloseOnEscapePress.ToString().ToLower(); + win.HtmlAttributes["data-show"] = win.Show.ToString().ToLower(); + win.HtmlAttributes["data-focus"] = win.Focus.ToString().ToLower(); + win.HtmlAttributes["data-backdrop"] = win.BackDrop + ? (win.CloseOnBackdropClick ? "true" : "static") + : "false"; writer.AddAttributes(win.HtmlAttributes); - writer.RenderBeginTag("div"); // root div - - // HEADER - if (win.ShowClose && win.Title.HasValue()) - { - this.RenderHeader(writer); - } - // BODY - this.RenderBody(writer); - - // FOOTER - if (win.FooterContent != null) - { - this.RenderFooter(writer); - } - - writer.RenderEndTag(); // div.modal - + writer.RenderBeginTag("div"); // div.modal + { + var className = "modal-dialog"; + switch (win.Size) + { + case WindowSize.Small: + className += " modal-sm"; + break; + case WindowSize.Large: + className += " modal-lg"; + break; + case WindowSize.Flex: + className += " modal-flex"; + break; + case WindowSize.FlexSmall: + className += " modal-flex modal-flex-sm"; + break; + } + + if (win.CenterVertically) + { + className += " modal-dialog-centered"; + } + + writer.AddAttribute("class", className); + win.HtmlAttributes["role"] = "document"; + writer.RenderBeginTag("div"); // div.modal-dialog + { + writer.AddAttribute("class", "modal-content"); + writer.RenderBeginTag("div"); // div.modal-content + { + // HEADER + if (win.ShowClose && win.Title.HasValue()) + { + this.RenderHeader(writer); + } + + // BODY + this.RenderBody(writer); + + // FOOTER + if (win.FooterContent != null) + { + this.RenderFooter(writer); + } + } + writer.RenderEndTag(); // div.modal-content + } + writer.RenderEndTag(); // div.modal-dialog + } + writer.RenderEndTag(); // div.modal } protected virtual void RenderHeader(HtmlTextWriter writer) { var win = base.Component; - + writer.AddAttribute("class", "modal-header"); writer.RenderBeginTag("div"); - if (win.ShowClose) - { - writer.Write(""); - } + if (win.Title.HasValue()) + { + writer.Write("".FormatCurrent(win.Id + "Label", win.Title)); + } - if (win.Title.HasValue()) + if (win.ShowClose) { - writer.Write("

    {1}

    ".FormatCurrent(win.Id + "Label", win.Title)); + writer.Write(""); } writer.RenderEndTag(); // div.modal-header @@ -97,17 +123,18 @@ protected virtual void RenderBody(HtmlTextWriter writer) { var win = base.Component; - writer.AddAttribute("class", "modal-body"); - if (win.Height.GetValueOrDefault() > 0) - { - writer.AddAttribute("style", "max-height:{0}px;".FormatInvariant(win.Height.Value)); - } + win.BodyHtmlAttributes.PrependCssClass("modal-body"); + writer.AddAttributes(win.BodyHtmlAttributes); writer.RenderBeginTag("div"); - if (win.ContentUrl.IsEmpty() && win.Content != null) - { - win.Content.WriteTo(writer); - } + if (win.Content != null) + { + win.Content.WriteTo(writer); + } + else if (win.ContentUrl.HasValue()) + { + writer.Write(" -
    -
    - - -
    - Select files to upload
    - -
    -
    -
    - - -
    -
    - -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js index 13b36dad8b..ff67f93a71 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js @@ -19,20 +19,51 @@ Contact: Lyubomir Arsov, liubo (at) web-lobby.com */ -function FileSelected(file){ - /** - * file is an object containing following properties: - * - * fullPath - path to the file - absolute from your site root - * path - directory in which the file is located - absolute from your site root - * size - size of the file in bytes - * time - timestamo of last modification - * name - file name - * ext - file extension - * width - if the file is image, this will be the width of the original image, 0 otherwise - * height - if the file is image, this will be the height of the original image, 0 otherwise - * - */ - alert(file.fullPath + "' selected.\n To integrate with CKEditor or TinyMCE change INTEGRATION setting in conf.json. For more details see the installation instructions at http:www.roxyfileman.com.\n Edit js/custom.js to setup your own implementation."); +function FileSelected(file) { + /** + * file is an object containing following properties: + * + * fullPath - path to the file - absolute from your site root + * path - directory in which the file is located - absolute from your site root + * size - size of the file in bytes + * time - timestamp of last modification + * name - file name + * ext - file extension + * width - if the file is image, this will be the width of the original image, 0 otherwise + * height - if the file is image, this will be the height of the original image, 0 otherwise + * + */ + + var p = (window.opener || window.parent); + + // Set the value of field sent to Fileman via URL param "field". + var fieldId = RoxyUtils.GetUrlParam('field'); + //opener.document.getElementById(fieldId).value = file.fullPath; + p.window.jQuery('#' + fieldId).val(file.fullPath).trigger('change').trigger('input'); + + //// Set the source of an image which id is sent to Fileman via URL param "img". + // opener.document.getElementById(RoxyUtils.GetUrlParam('img')).src = file.fullPath; + + // Close file manager if it's opened in separate window. + if (window.opener) { + self.close(); + } + else { + // We put the modal dialog's ID in "mid" + p.window.closePopup(RoxyUtils.GetUrlParam('mid')); + } } +function GetSelectedValue() { + /** + * This function is called to retrieve selected value when custom integration is used. + * Url parameter selected will override this value. + */ + + var p = (window.opener || window.parent); + var fieldId = RoxyUtils.GetUrlParam('field'); + + if (fieldId) { + return p.window.jQuery('#' + fieldId).val(); + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js index dba2a7e89e..0dd0b780c3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js @@ -19,488 +19,590 @@ Contact: Lyubomir Arsov, liubo (at) web-lobby.com */ -function Directory(fullPath, numDirs, numFiles){ - if(!fullPath) fullPath = ''; - this.fullPath = fullPath; - this.name = RoxyUtils.GetFilename(fullPath); - if(!this.name) - this.name = 'My files'; - this.path = RoxyUtils.GetPath(fullPath); - this.dirs = (numDirs?numDirs:0); - this.files = (numFiles?numFiles:0); - this.filesList = new Array(); - this.Show = function(){ - var html = this.GetHtml(); - var el = null; - el = $('li[data-path="'+this.path+'"]'); - if(el.length == 0) - el = $('#pnlDirList'); - else{ - if(el.children('ul').length == 0) - el.append('
      '); - el = el.children('ul'); - } - if(el){ - el.append(html); - this.SetEvents(); - } - }; - this.SetEvents = function(){ - var el = this.GetElement(); - el.draggable({helper:makeDragDir,start:startDragDir,cursorAt: { left: 10 ,top:10},delay:200}); - el = el.children('div'); - el.click(function(e){ - selectDir(this); - }); +$(function () { + $('#pnlDirList').on('contextmenu', '.dir-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + closeMenus('file'); + selectDir(this, true); + var t = e.pageY; + var menuEnd = t + $('#menuDir').height() + 30; + if (menuEnd > $(window).height()) { + offset = menuEnd - $(window).height() + 30; + t -= offset; + } + if (t < 0) + t = 0; + $('#menuDir').css({ + top: t + 'px', + left: e.pageX + 'px' + }).show(); - el.bind('contextmenu', function(e) { - e.stopPropagation(); - e.preventDefault(); - closeMenus('file'); - selectDir(this); - var t = e.pageY - $('#menuDir').height(); - if(t < 0) - t = 0; - $('#menuDir').css({ - top: t+'px', - left: e.pageX+'px' - }).show(); + return false; + }); - return false; - }); + $('#pnlDirList').on('click', '.dir-item', function (e) { + e.preventDefault(); + selectDir(this); + }); - el.droppable({drop:moveObject,over:dragFileOver,out:dragFileOut}); - el = el.children('.dirPlus'); - el.click(function(e){ - e.stopPropagation(); - var d = Directory.Parse($(this).closest('li').attr('data-path')); - d.Expand(); - }); - }; - this.GetHtml = function(){ - var html = '
    • '; - html += '
      '; - html += ''+this.name+' ('+this.files+')
      '; - html += '
    • '; + $('#pnlDirList').on('click', '.dirPlus', function (e) { + e.stopPropagation(); + e.preventDefault(); + var d = Directory.Parse($(this).closest('li').attr('data-path')); + d.Expand(); + }); +}); - return html; - }; - this.SetStatusBar = function(){ - $('#pnlStatus').html(this.files+' '+(this.files == 1?t('file'):t('files'))); - }; - this.Select = function(){ - var el = this.GetElement(); - el.children('div').addClass('selected'); - $('#pnlDirList li[data-path!="'+this.fullPath+'"] > div').removeClass('selected'); - el.children('img.dir').prop('src', 'images/folder.png'); - this.SetStatusBar(); - var p = this.GetParent(); - while(p){ - p.Expand(true); - p = p.GetParent(); - } - this.Expand(true); - this.ListFiles(true); - }; - this.GetElement = function(){ - return $('li[data-path="'+this.fullPath+'"]'); - }; - this.IsExpanded = function(){ - var el = this.GetElement().children('ul'); - return (el && el.is(":visible")); - }; - this.IsListed = function(){ - if($('#hdDir').val() == this.fullPath) - return true; - return false; - }; - this.GetExpanded = function(el){ - var ret = new Array(); - if(!el) - el = $('#pnlDirList'); - el.children('li').each(function(){ - var path = $(this).attr('data-path'); - var d = new Directory(path); - if(d){ - if(d.IsExpanded() && path) - ret.push(path); - ret = ret.concat(d.GetExpanded(d.GetElement().children('ul'))); - } - }); +function Directory(fullPath, numDirs, numFiles) { + if (!fullPath) fullPath = ''; + this.fullPath = fullPath; + this.name = RoxyUtils.GetFilename(fullPath); + if (!this.name) + this.name = 'My files'; + this.path = RoxyUtils.GetPath(fullPath); + this.dirs = (numDirs ? numDirs : 0); + this.files = (numFiles ? numFiles : 0); + this.filesList = []; - return ret; - }; - this.RestoreExpanded = function(expandedDirs){ - for(i = 0; i < expandedDirs.length; i++){ - var d = Directory.Parse(expandedDirs[i]); - if(d) - d.Expand(true); - } - }; - this.GetParent = function(){ - return Directory.Parse(this.path); - }; - this.SetOpened = function(){ - var li = this.GetElement(); - if(li.find('li').length < 1) - li.children('div').children('.dirPlus').prop('src', 'images/blank.gif'); - else if(this.IsExpanded()) - li.children('div').children('.dirPlus').prop('src', 'images/dir-minus.png'); - else - li.children('div').children('.dirPlus').prop('src', 'images/dir-plus.png'); - }; - this.Update = function(newPath){ - var el = this.GetElement(); - if(newPath){ - this.fullPath = newPath; - this.name = RoxyUtils.GetFilename(newPath); - if(!this.name) - this.name = 'My files'; - this.path = RoxyUtils.GetPath(newPath); - } - el.attr('data-path', this.fullPath); - el.attr('data-dirs', this.dirs); - el.attr('data-files', this.files); - el.children('div').children('.name').html(this.name+' ('+this.files+')'); - this.SetOpened(); - }; - this.LoadAll = function (selectedDir) { - var expanded = this.GetExpanded(); - var dirListURL = RoxyFilemanConf.DIRLIST; - if(!dirListURL){ - alert(t('E_ActionDisabled')); - return; - } - $('#pnlLoadingDirs').show(); - $('#pnlDirList').hide(); - dirListURL = RoxyUtils.AddParam(dirListURL, 'type', RoxyUtils.GetUrlParam('type')); + this.Show = function () { + var html = this.GetHtml(); + var el = null; + el = $('li[data-path="' + this.path + '"]'); + if (el.length == 0) + el = $('#pnlDirList'); + else { + if (el.children('ul').length == 0) + el.append('
        '); + el = el.children('ul'); + } + if (el) { + el.append(html); + this.SetEvents(); + } + }; + this.SetEvents = function () { + var el = this.GetElement(); + el.draggable({ + helper: makeDragDir, + start: startDragDir, + cursorAt: { + left: 10, + top: 10 + }, + delay: 200 + }); + el.find('> .dir-item').droppable({ + drop: moveObject, + over: dragFileOver, + out: dragFileOut + }); + }; + this.GetHtml = function () { + var dirClass = (this.dirs > 0 ? "" : " invisible"); - var dir = this; - $.ajax({ - url: dirListURL, - dataType: 'json', - async:true, - success: function(dirs){ - $('#pnlDirList').children('li').remove(); - for(i = 0; i < dirs.length; i++){ - var d = new Directory(dirs[i].p, dirs[i].d, dirs[i].f); - d.Show(); - } - $('#pnlLoadingDirs').hide(); - $('#pnlDirList').show(); - dir.RestoreExpanded(expanded); - var d = Directory.Parse(selectedDir); - if(d) - d.Select(); - }, - error: function(data){ - $('#pnlLoadingDirs').hide(); - $('#pnlDirList').show(); - alert(t('E_LoadingAjax')+' '+RoxyFilemanConf.DIRLIST); - } - }); - }; - this.Expand = function(show){ - var li = this.GetElement(); - var el = li.children('ul'); - if(this.IsExpanded() && !show) - el.hide(); - else - el.show(); + var html = '
      • '; + html += '
        '; + html += '' + this.name + (parseInt(this.files) ? ' (' + this.files + ')' : '') + '
        '; + html += '
      • '; + + return html; + }; + this.SetStatusBar = function () { + $('#pnlStatus').html(this.files + ' ' + (this.files == 1 ? t('file') : t('files'))); + }; + this.SetSelectedFile = function (path) { + if (path) { + var f = File.Parse(path); + if (f) { + selectFile(f.GetElement()); + } + } + }; + this.Select = function (selectedFile, indeterm) { + var li = this.GetElement(); + var dir = li.find('> .dir-item'); + var currentSelected = getSelectedDir(); - this.SetOpened(); - }; - this.Create = function(newName){ - if(!newName) - return false; - else if(!RoxyFilemanConf.CREATEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.CREATEDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newName); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - item.LoadAll(RoxyUtils.MakePath(item.fullPath, newName)); - ret = true; - } - else{ - alert(data.msg); - } - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.Delete = function(){ - if(!RoxyFilemanConf.DELETEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.DELETEDIR, 'd', this.fullPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var parent = item.GetParent(); - parent.dirs--; - parent.Update(); - parent.Select(); - item.GetElement().remove(); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.Rename = function(newName){ - if(!newName) - return false; - else if(!RoxyFilemanConf.RENAMEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.RENAMEDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newName); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var newPath = RoxyUtils.MakePath(item.path, newName); - item.Update(newPath); - item.Select(); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.Copy = function(newPath){ - if(!RoxyFilemanConf.COPYDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.COPYDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var d = Directory.Parse(newPath); - if(d){ - d.LoadAll(d.fullPath); - } - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; - this.Move = function(newPath){ - if(!newPath) - return false; - else if(!RoxyFilemanConf.MOVEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.MOVEDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - item.LoadAll(RoxyUtils.MakePath(newPath, item.name)); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.ListFiles = function(refresh){ - $('#pnlLoading').show(); - $('#pnlEmptyDir').hide(); - $('#pnlFileList').hide(); - $('#pnlSearchNoFiles').hide(); - this.LoadFiles(refresh); - }; - this.FilesLoaded = function(filesList){ - filesList = this.SortFiles(filesList); - $('#pnlFileList').html(''); - for(i = 0; i < filesList.length; i++){ - var f = filesList[i]; - f.Show(); - } - $('#hdDir').val(this.fullPath); - $('#pnlLoading').hide(); - if($('#pnlFileList').children('li').length == 0) - $('#pnlEmptyDir').show(); - this.files = $('#pnlFileList').children('li').length; - this.Update(); - this.SetStatusBar(); - filterFiles(); - switchView(); - $('#pnlFileList').show(); - }; - this.LoadFiles = function(refresh){ - if(!RoxyFilemanConf.FILESLIST){ - alert(t('E_ActionDisabled')); - return; - } - var ret = new Array(); - var fileURL = RoxyFilemanConf.FILESLIST; - fileURL = RoxyUtils.AddParam(fileURL, 'd', this.fullPath); - fileURL = RoxyUtils.AddParam(fileURL, 'type', RoxyUtils.GetUrlParam('type')); - var item = this; - if(!this.IsListed() || refresh){ + if (indeterm && currentSelected) { + if (currentSelected.fullPath != li.data('path')) { + $('#pnlDirList').data('indeterm', dir); + $('#pnlDirList .indeterm').removeClass('indeterm'); + dir.addClass('indeterm'); + } + } + else { + dir.addClass('selected'); + $('#pnlDirList li[data-path!="' + this.fullPath + '"] > .dir-item').removeClass('selected'); + this.SetStatusBar(); - $.ajax({ - url: fileURL, - dataType: 'json', - async:true, - success: function(files){ - for(i = 0; i < files.length; i++){ - ret.push(new File(files[i].p, files[i].s, files[i].t, files[i].w, files[i].h)); - } - item.FilesLoaded(ret); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+fileURL); - } - }); - } - else{ - $('#pnlFileList li').each(function(){ - ret.push(new File($(this).attr('data-path'), $(this).attr('data-size'), $(this).attr('data-time'), $(this).attr('data-w'), $(this).attr('data-h'))); - }); - item.FilesLoaded(ret); - } + var p = this.GetParent(); + while (p) { + p.Expand(true); + p = p.GetParent(); + } + this.ListFiles(true, selectedFile); + setLastDir(this.fullPath); + } + }; + this.GetElement = function () { + return $('li[data-path="' + this.fullPath + '"]'); + }; + this.IsExpanded = function () { + var el = this.GetElement().children('ul'); + return (el && el.is(":visible")); + }; + this.IsIndeterm = function () { + var el = this.GetElement().find('> .dir-item'); + return el.is(".indeterm"); + }; + this.IsListed = function () { + if ($('#hdDir').val() == this.fullPath) + return true; + return false; + }; + this.GetExpanded = function (el) { + var ret = new Array(); + if (!el) + el = $('#pnlDirList'); + el.children('li').each(function () { + var path = $(this).attr('data-path'); + var d = new Directory(path); + if (d) { + if (d.IsExpanded() && path) + ret.push(path); + ret = ret.concat(d.GetExpanded(d.GetElement().children('ul'))); + } + }); - return ret; - }; + return ret; + }; + this.RestoreExpanded = function (expandedDirs) { + for (i = 0; i < expandedDirs.length; i++) { + var d = Directory.Parse(expandedDirs[i]); + if (d) + d.Expand(true); + } + }; + this.GetParent = function () { + return Directory.Parse(this.path); + }; + this.SetOpened = function () { + var li = this.GetElement(); + var chevrons = li.children('div').children('.dirPlus'); + if (li.find('li').length < 1) + chevrons.addClass('invisible'); + else if (this.IsExpanded()) + chevrons.removeClass('invisible fa-chevron-right').addClass("fa-chevron-down"); + else + chevrons.removeClass('invisible fa-chevron-down').addClass("fa-chevron-right"); + }; + this.Update = function (newPath) { + var el = this.GetElement(); + if (newPath) { + this.fullPath = newPath; + this.name = RoxyUtils.GetFilename(newPath); + if (!this.name) + this.name = 'My files'; + this.path = RoxyUtils.GetPath(newPath); + } + el.data('path', this.fullPath); + el.data('dirs', this.dirs); + el.data('files', this.files); + el.children('div').children('.name').html(this.name + ' (' + this.files + ')'); + this.SetOpened(); + }; + this.LoadAll = function (selectedDir) { + var expanded = this.GetExpanded(); + var dirListURL = RoxyUtils.GetRootPath(RoxyFilemanConf.DIRLIST); + if (!dirListURL) { + alert(t('E_ActionDisabled')); + return; + } + $('#pnlLoadingDirs').show(); + $('#pnlDirList').hide(); + dirListURL = RoxyUtils.AddParam(dirListURL, 'type', RoxyUtils.GetUrlParam('type')); - this.SortByName = function(files, order){ - files.sort(function(a, b){ - var x = (order == 'desc'?0:2) - a = a.name.toLowerCase(); - b = b.name.toLowerCase(); - if(a > b) - return -1 + x; - else if(a < b) - return 1 - x; - else - return 0; - }); + var dir = this; + $.ajax({ + url: dirListURL, + type: 'POST', + dataType: 'json', + async: false, + cache: false, + success: function (dirs) { + $('#pnlDirList').children('li').remove(); + var d; + for (i = 0; i < dirs.length; i++) { + d = new Directory(dirs[i].p, dirs[i].d, dirs[i].f); + d.Show(); + } + $('#pnlLoadingDirs').hide(); + $('#pnlDirList').show(); + dir.RestoreExpanded(expanded); + d = Directory.Parse(selectedDir); + if (d) d.Select(); + }, + error: function (data) { + $('#pnlLoadingDirs').hide(); + $('#pnlDirList').show(); + alert(t('E_LoadingAjax') + ' ' + RoxyFilemanConf.DIRLIST); + } + }); + }; + this.Expand = function (show) { + var li = this.GetElement(); + var el = li.children('ul'); + if (this.IsExpanded() && !show) + el.hide(); + else + el.show(); - return files; - }; - this.SortBySize = function(files, order){ - files.sort(function(a, b){ - var x = (order == 'desc'?0:2) - a = parseInt(a.size); - b = parseInt(b.size); - if(a > b) - return -1 + x; - else if(a < b) - return 1 - x; - else - return 0; - }); + this.SetOpened(); + }; + this.Create = function (newName) { + if (!newName) + return false; + else if (!RoxyFilemanConf.CREATEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.CREATEDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newName); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newName + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + item.LoadAll(RoxyUtils.MakePath(item.fullPath, newName)); + ret = true; + } else { + alert(data.msg); + } + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.Delete = function () { + if (!RoxyFilemanConf.DELETEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.DELETEDIR), 'd', this.fullPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var parent = item.GetParent(); + parent.dirs--; + parent.Update(); + parent.Select(); + item.GetElement().remove(); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.Rename = function (newName) { + if (!newName) + return false; + else if (!RoxyFilemanConf.RENAMEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.RENAMEDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newName); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newName + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var newPath = RoxyUtils.MakePath(item.path, newName); + item.Update(newPath); + item.Select(); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.Copy = function (newPath) { + if (!RoxyFilemanConf.COPYDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.COPYDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newPath + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var d = Directory.Parse(newPath); + if (d) { + d.LoadAll(d.fullPath); + } + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; + this.Move = function (newPath) { + if (!newPath) + return false; + else if (!RoxyFilemanConf.MOVEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.MOVEDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newPath + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + item.LoadAll(RoxyUtils.MakePath(newPath, item.name)); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.ListFiles = function (refresh, selectedFile) { + $('#pnlLoading').show(); + $('#pnlEmptyDir').hide(); + $('#pnlFileList').hide(); + $('#pnlSearchNoFiles').hide(); + this.LoadFiles(refresh, selectedFile); + }; + this.FilesLoaded = function (filesList, selectedFile) { + var list = $('#pnlFileList'); + filesList = this.SortFiles(filesList); + + var html = []; + for (i = 0; i < filesList.length; i++) { + var f = filesList[i]; + html.push(f.GenerateHtml()); + } - return files; - }; - this.SortByTime = function(files, order){ - files.sort(function(a, b){ - var x = (order == 'desc'?0:2) - a = parseInt(a.time); - b = parseInt(b.time); - if(a > b) - return -1 + x; - else if(a < b) - return 1 - x; - else - return 0; - }); + // Set Html + list.html(html.join("")); - return files; - }; - this.SortFiles = function(files){ - var order = $('#ddlOrder').val(); - if(!order) - order = 'name'; + // Bind events + list.find('.file-item').tooltip({ + show: { + delay: 700, + duration: 100 + }, + hide: 200, + track: true, + content: tooltipContent + }); - switch(order){ - case 'size': - files = this.SortBySize(files, 'asc'); - break; - case 'size_desc': - files = this.SortBySize(files, 'desc'); - break; - case 'time': - files = this.SortByTime(files, 'asc'); - break; - case 'time_desc': - files = this.SortByTime(files, 'desc'); - break; - case 'name_desc': - files = this.SortByName(files, 'desc'); - break; - default: - files = this.SortByName(files, 'asc'); - } + $('#hdDir').val(this.fullPath); + $('#pnlLoading').hide(); + var liLen = list.children('li').length; + if (liLen == 0) + $('#pnlEmptyDir').show(); + this.files = liLen; + this.Update(); + this.SetStatusBar(); + filterFiles(); + switchView(); + list.show(); + this.SetSelectedFile(selectedFile); + }; + this.LoadFiles = function (refresh, selectedFile) { + if (!RoxyFilemanConf.FILESLIST) { + alert(t('E_ActionDisabled')); + return; + } + var ret = new Array(); + var fileURL = RoxyUtils.GetRootPath(RoxyFilemanConf.FILESLIST); + fileURL = RoxyUtils.AddParam(fileURL, 'd', this.fullPath); + fileURL = RoxyUtils.AddParam(fileURL, 'type', RoxyUtils.GetUrlParam('type')); + var item = this; + if (!this.IsListed() || refresh) { + $.ajax({ + url: fileURL, + type: 'POST', + data: { + d: this.fullPath, + type: RoxyUtils.GetUrlParam('type') + }, + dataType: 'json', + async: true, + cache: false, + success: function (files) { + for (i = 0; i < files.length; i++) { + var f = files[i]; + ret.push(new File(f.p, f.s, f.t, f.w, f.h, f.m)); + } + item.FilesLoaded(ret, selectedFile); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + fileURL); + } + }); + } else { + $('#pnlFileList li').each(function () { + ret.push(new File($(this).attr('data-path'), $(this).attr('data-size'), $(this).attr('data-time'), $(this).attr('data-w'), $(this).attr('data-h'))); + }); + item.FilesLoaded(ret, selectedFile); + } - return files; - }; + return ret; + }; + + this.SortByName = function (files, order) { + files.sort(function (a, b) { + var x = (order == 'desc' ? 0 : 2) + a = a.name.toLowerCase(); + b = b.name.toLowerCase(); + if (a > b) + return -1 + x; + else if (a < b) + return 1 - x; + else + return 0; + }); + + return files; + }; + this.SortBySize = function (files, order) { + files.sort(function (a, b) { + var x = (order == 'desc' ? 0 : 2) + a = parseInt(a.size); + b = parseInt(b.size); + if (a > b) + return -1 + x; + else if (a < b) + return 1 - x; + else + return 0; + }); + + return files; + }; + this.SortByTime = function (files, order) { + files.sort(function (a, b) { + var x = (order == 'desc' ? 0 : 2) + a = parseInt(a.time); + b = parseInt(b.time); + if (a > b) + return -1 + x; + else if (a < b) + return 1 - x; + else + return 0; + }); + + return files; + }; + this.SortFiles = function (files) { + var order = $('#ddlOrder').val(); + if (!order) + order = 'name'; + + switch (order) { + case 'size': + files = this.SortBySize(files, 'asc'); + break; + case 'size_desc': + files = this.SortBySize(files, 'desc'); + break; + case 'time': + files = this.SortByTime(files, 'asc'); + break; + case 'time_desc': + files = this.SortByTime(files, 'desc'); + break; + case 'name_desc': + files = this.SortByName(files, 'desc'); + break; + default: + files = this.SortByName(files, 'asc'); + } + + return files; + }; } -Directory.Parse = function(path){ - var ret = false; - var li = $('#pnlDirList').find('li[data-path="'+path+'"]'); - if(li.length > 0) - ret = new Directory(li.attr('data-path'), li.attr('data-dirs'), li.attr('data-files')); +Directory.Parse = function (path) { + var ret = false; + var li = $('#pnlDirList').find('li[data-path="' + path + '"]'); + if (li.length > 0) + ret = new Directory(li.attr('data-path'), li.attr('data-dirs'), li.attr('data-files')); - return ret; -}; + return ret; +}; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js index 16f6435bd7..fa252adf39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js @@ -19,202 +19,266 @@ Contact: Lyubomir Arsov, liubo (at) web-lobby.com */ -function File(filePath, fileSize, modTime, w, h){ - this.fullPath = filePath; - this.type = RoxyUtils.GetFileType(filePath); - this.name = RoxyUtils.GetFilename(filePath); - this.ext = RoxyUtils.GetFileExt(filePath); - this.path = RoxyUtils.GetPath(filePath); - this.icon = RoxyUtils.GetFileIcon(filePath); - this.bigIcon = this.icon.replace('filetypes', 'filetypes/big'); - this.image = filePath; - this.size = (fileSize?fileSize:RoxyUtils.GetFileSize(filePath)); - this.time = modTime; - this.width = (w? w: 0); - this.height = (h? h: 0); - this.Show = function(){ - html = '
      • '; - html += ''; - html += ''+RoxyUtils.FormatDate(new Date(this.time * 1000))+''; - html += ''+this.name+''; - html += ''+RoxyUtils.FormatFileSize(this.size)+''; - html += '
      • '; - $('#pnlFileList').append(html); - var li = $("#pnlFileList li:last"); - li.draggable({helper:makeDragFile,start:startDragFile,cursorAt: { left: 10 ,top:10},delay:200}); - li.click(function(e){ - selectFile(this); - }); - li.dblclick(function(e){ - selectFile(this); - setFile(); - }); - li.tooltip({show:{delay:700},track: true, content:tooltipContent}); - - li.bind('contextmenu', function(e) { - e.stopPropagation(); - e.preventDefault(); - closeMenus('dir'); - selectFile(this); - $(this).tooltip('close'); - var t = e.pageY - $('#menuFile').height(); - if(t < 0) - t = 0; - $('#menuFile').css({ - top: t+'px', - left: e.pageX+'px' - }).show(); - - return false; - }); - }; - this.GetElement = function(){ - return $('li[data-path="'+this.fullPath+'"]'); - }; - this.IsImage = function(){ - var ret = false; - if(this.type == 'image') - ret = true; - return ret; - }; - this.Delete = function(){ - if(!RoxyFilemanConf.DELETEFILE){ - alert(t('E_ActionDisabled')); - return; - } - var deleteUrl = RoxyUtils.AddParam(RoxyFilemanConf.DELETEFILE, 'f', this.fullPath); - var item = this; - $.ajax({ - url: deleteUrl, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - $('li[data-path="'+item.fullPath+'"]').remove(); - var d = Directory.Parse(item.path); - if(d){ - d.files--; - d.Update(); - d.SetStatusBar(); - } - } - else{ - alert(data.msg); - } - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+deleteUrl); - } - }); - }; - this.Rename = function(newName){ - if(!RoxyFilemanConf.RENAMEFILE){ - alert(t('E_ActionDisabled')); - return false; - } - if(!newName) - return false; - var url = RoxyUtils.AddParam(RoxyFilemanConf.RENAMEFILE, 'f', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newName); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var newPath = RoxyUtils.MakePath(this.path, newName); - $('li[data-path="'+item.fullPath+'"] .icon').attr('src', RoxyUtils.GetFileIcon(newName)); - $('li[data-path="'+item.fullPath+'"] .name').text(newName); - $('li[data-path="'+newPath+'"]').attr('data-path', newPath); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; - this.Copy = function(newPath){ - if(!RoxyFilemanConf.COPYFILE){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.COPYFILE, 'f', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var d = Directory.Parse(newPath); - if(d){ - d.files++; - d.Update(); - d.SetStatusBar(); - d.ListFiles(true); - } - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; - this.Move = function(newPath){ - if(!RoxyFilemanConf.MOVEFILE){ - alert(t('E_ActionDisabled')); - return; - } - newFullPath = RoxyUtils.MakePath(newPath, this.name); - var url = RoxyUtils.AddParam(RoxyFilemanConf.MOVEFILE, 'f', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newFullPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - $('li[data-path="'+item.fullPath+'"]').remove(); - var d = Directory.Parse(item.path); - if(d){ - d.files--; - d.Update(); - d.SetStatusBar(); - d = Directory.Parse(newPath); - d.files++; - d.Update(); - } - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; + +$(function () { + $(document).on('dblclick', '.file-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + selectFile(this); + setFile(); + }); + + $('#pnlFileList').on('contextmenu', '.file-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + closeMenus('dir'); + selectFile(this); + $(this).tooltip('close'); + var t = e.pageY; + var menuEnd = t + $('#menuFile').height() + 30; + if (menuEnd > $(window).height()) { + offset = menuEnd - $(window).height() + 30; + t -= offset; + } + $('#menuFile').css({ + top: t + 'px', + left: e.pageX + 'px' + }).show(); + + return false; + }); + + $(document).on('click', '.file-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + + selectFile(this); + }); + + $(document).on('mouseenter', '.file-item', function (e) { + var li = $(this); + + // create draggable + if (!li.data('ui-draggable')) { + li.draggable({ + helper: makeDragFile, + start: startDragFile, + addClasses: false, + appendTo: 'body', + cursorAt: { + left: 10, + top: 10 + }, + delay: 200 + }); + } + }); +}); + +function File(filePath, fileSize, modTime, w, h, mime) { + this.fullPath = filePath; + this.mime = mime; + this.type = RoxyUtils.GetFileType(filePath, mime); + this.icon = RoxyIconHints[this.type]; + this.name = RoxyUtils.GetFilename(filePath); + this.ext = RoxyUtils.GetFileExt(filePath); + this.path = RoxyUtils.GetPath(filePath); + this.image = filePath; + this.size = (fileSize ? fileSize : RoxyUtils.GetFileSize(filePath)); + this.time = modTime; + this.width = (w ? w : 0); + this.height = (h ? h : 0); + this.thumb = this.type === 'image' ? filePath : RoxyUtils.GetAssetPath("images/blank.gif"); + this.GenerateHtml = function () { + var attrs = [ + 'data-mime="' + this.mime + '"', + 'data-path="' + this.fullPath + '"', + 'data-time="' + this.time + '"', + 'data-w="' + this.width + '"', + 'data-h="' + this.height + '"', + 'data-size="' + this.size + '"', + 'title="' + this.name + '"' + ]; + var html = [ + '
      • ', + '
        ', + '' + RoxyUtils.FormatDate(new Date(this.time * 1000)) + '', + '' + this.name + '', + '' + RoxyUtils.FormatFileSize(this.size) + '', + '
      • ' + ].join(""); + + return html; + }; + this.GetElement = function () { + return $('li[data-path="' + this.fullPath + '"]'); + }; + this.IsImage = function () { + return this.type === 'image'; + }; + this.Delete = function () { + if (!RoxyFilemanConf.DELETEFILE) { + alert(t('E_ActionDisabled')); + return; + } + var deleteUrl = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.DELETEFILE), 'f', this.fullPath); + var item = this; + $.ajax({ + url: deleteUrl, + type: 'POST', + data: { + f: this.fullPath + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + $('li[data-path="' + item.fullPath + '"]').remove(); + var d = Directory.Parse(item.path); + if (d) { + d.files--; + d.Update(); + d.SetStatusBar(); + } + } else { + alert(data.msg); + } + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + deleteUrl); + } + }); + }; + this.Rename = function (newName) { + if (!RoxyFilemanConf.RENAMEFILE) { + alert(t('E_ActionDisabled')); + return false; + } + if (!newName) + return false; + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.RENAMEFILE), 'f', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newName); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + f: this.fullPath, + n: newName + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var newPath = RoxyUtils.MakePath(this.path, newName); + var fileType = RoxyUtils.GetFileIcon(newName); + var icon = RoxyIconHints[fileType]; + var li = $('li[data-path="' + item.fullPath + '"]'); + li.toggleClass('file-image', fileType == 'image'); + $('.file-icon', li).attr('class', "").addClass('file-icon fa-fw ' + icon.name).css('color', icon.color); + $('.name', li).text(newName); + $('li[data-path="' + newPath + '"]').attr('data-path', newPath); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; + this.Copy = function (newPath) { + if (!RoxyFilemanConf.COPYFILE) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.COPYFILE), 'f', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + f: this.fullPath, + n: newPath + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var d = Directory.Parse(newPath); + if (d) { + d.files++; + d.Update(); + d.SetStatusBar(); + if (!$("#pnlDirList").data("indeterm")) { + d.ListFiles(true); + } + } + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; + this.Move = function (newPath) { + if (!RoxyFilemanConf.MOVEFILE) { + alert(t('E_ActionDisabled')); + return; + } + newFullPath = RoxyUtils.MakePath(newPath, this.name); + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.MOVEFILE), 'f', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newFullPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + f: this.fullPath, + n: newFullPath + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + $('li[data-path="' + item.fullPath + '"]').remove(); + var d = Directory.Parse(item.path); + if (d) { + d.files--; + d.Update(); + d.SetStatusBar(); + d = Directory.Parse(newPath); + d.files++; + d.Update(); + } + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; } -File.Parse = function(path){ - var ret = false; - var li = $('#pnlFileList').find('li[data-path="'+path+'"]'); - if(li.length > 0) - ret = new File(li.attr('data-path'), li.attr('data-size'), li.attr('data-time'), li.attr('data-w'), li.attr('data-h')); - return ret; +File.Parse = function (path) { + var ret = false; + var li = $('#pnlFileList').find('li[data-path="' + path + '"]'); + if (li.length > 0) + ret = new File(li.data('path'), li.data('size'), li.data('time'), li.data('w'), li.data('h')); + + return ret; }; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip deleted file mode 100644 index a26b9d8a42..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js index febec4041f..48d5a1092b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js @@ -74,6 +74,7 @@ fileTypeIcons['mdb'] = 'file_extension_mdb.png'; fileTypeIcons['mid'] = 'file_extension_mid.png'; fileTypeIcons['mov'] = 'file_extension_mov.png'; fileTypeIcons['mp2'] = 'file_extension_mp2.png'; +fileTypeIcons['mp3'] = 'file_extension_mp3.png'; fileTypeIcons['mp4'] = 'file_extension_mp4.png'; fileTypeIcons['mpeg'] = 'file_extension_mpeg.png'; fileTypeIcons['mpg'] = 'file_extension_mpg.png'; @@ -117,4 +118,4 @@ fileTypeIcons['wmv'] = 'file_extension_wmv.png'; fileTypeIcons['wps'] = 'file_extension_wps.png'; fileTypeIcons['xls'] = 'file_extension_xls.png'; fileTypeIcons['xpi'] = 'file_extension_xpi.png'; -fileTypeIcons['zip'] = 'file_extension_zip.png'; +fileTypeIcons['zip'] = 'file_extension_zip.png'; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js deleted file mode 100644 index da4170647d..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license -//@ sourceMappingURL=jquery-1.10.2.min.map -*/ -(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
        ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
        a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
        t
        ",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
        ",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t -}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
        ","
        "],area:[1,"",""],param:[1,"",""],thead:[1,"","
        "],tr:[2,"","
        "],col:[2,"","
        "],td:[3,"","
        "],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
        ","
        "]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); -u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x(" + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml index f65fccf73a..9bb79b8299 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/_CreateOrUpdate.cshtml @@ -1,14 +1,25 @@ @model MessageTemplateModel -@using SmartStore.Collections; -@using SmartStore.Core.Domain.Catalog; -@using SmartStore.Web.Framework.UI; -@using Telerik.Web.Mvc.UI; +@using SmartStore.Web.Framework.UI @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) -@Html.SmartStore().TabStrip().Name("template-edit").Style(TabsStyle.Tabs).Position(TabsPosition.Top).Items(x => + + +@Html.SmartStore().TabStrip().Name("template-edit").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => { x.Add().Text(T("Admin.Common.Info").Text).Content(TabInfo()).Selected(true); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); @@ -17,64 +28,29 @@ EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "template-edit", this.Html, this.Model)); }) -@helper TokenSelector(TreeNode root) +@helper TabInfo() { - if (root.HasChildren) - { - - } -} + string modelTree = ViewBag.LastModelTreeJson; + string templateName = "MessageTemplate/" + Model.Name; -@helper TokenList(TreeNode node) -{ - -} + if (modelTree.HasValue()) + { + + } -@helper TabInfo() -{ -
        - - - - +
        - @Html.SmartLabelFor(model => model.TokensTree) - - @TokenSelector(Model.TokensTree) -
        @@ -104,16 +80,37 @@ @Html.SmartLabelFor(model => model.Locales[item].EmailAccountId) + + + + + + + + @@ -122,7 +119,7 @@ @Html.SmartLabelFor(model => model.Locales[item].Subject) @@ -153,20 +150,12 @@ @Html.ValidationMessageFor(model => model.Locales[item].Attachment3FileId) - - + - - -
        @Html.SmartLabelFor(model => model.Name) - @Model.Name +
        + @Model.Name +
        @Html.HiddenFor(model => model.Name)
        + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.DropDownListFor(model => Model.Locales[item].EmailAccountId, new SelectList(Model.AvailableEmailAccounts, "Id", "DisplayName", Model.Locales[item].EmailAccountId)) @Html.ValidationMessageFor(model => model.Locales[item].EmailAccountId)
        + @Html.SmartLabelFor(model => model.Locales[item].To) + + @Html.TextBoxFor(model => Model.Locales[item].To) + @Html.ValidationMessageFor(model => model.Locales[item].To) +
        + @Html.SmartLabelFor(model => model.Locales[item].ReplyTo) + + @Html.TextBoxFor(model => Model.Locales[item].ReplyTo) + @Html.ValidationMessageFor(model => model.Locales[item].ReplyTo) +
        @Html.SmartLabelFor(model => model.Locales[item].BccEmailAddresses) - @Html.EditorFor(model => Model.Locales[item].BccEmailAddresses) + @Html.TextBoxFor(model => Model.Locales[item].BccEmailAddresses) @Html.ValidationMessageFor(model => model.Locales[item].BccEmailAddresses)
        - @Html.TextBoxFor(model => Model.Locales[item].Subject, new { @class = "input-large" }) + @Html.TextBoxFor(model => Model.Locales[item].Subject) @Html.ValidationMessageFor(model => model.Locales[item].Subject)
        - @Html.SmartLabelFor(model => model.Locales[item].Body) -
        - @Html.EditorFor(model => Model.Locales[item].Body, "RichEditor") + @Html.EditorFor(model => Model.Locales[item].Body, "Liquid", new { TemplateName = templateName }) @Html.ValidationMessageFor(model => model.Locales[item].Body)
        - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
        , @ @@ -179,12 +168,30 @@ @Html.ValidationMessageFor(model => model.EmailAccountId) + + + + + + + + @@ -193,7 +200,7 @@ @Html.SmartLabelFor(model => model.Subject) @@ -224,12 +231,9 @@ @Html.ValidationMessageFor(model => model.Attachment3FileId) - - + @@ -239,69 +243,5 @@ @helper TabStores() { - -
        + @Html.SmartLabelFor(model => model.To) + + @Html.TextBoxFor(model => model.To) + @Html.ValidationMessageFor(model => model.To) +
        + @Html.SmartLabelFor(model => model.ReplyTo) + + @Html.TextBoxFor(model => model.ReplyTo) + @Html.ValidationMessageFor(model => model.ReplyTo) +
        @Html.SmartLabelFor(model => model.BccEmailAddresses) - @Html.EditorFor(model => model.BccEmailAddresses) + @Html.TextBoxFor(model => model.BccEmailAddresses) @Html.ValidationMessageFor(model => model.BccEmailAddresses)
        - @Html.TextBoxFor(model => model.Subject, new { @class = "input-large" }) + @Html.TextBoxFor(model => model.Subject) @Html.ValidationMessageFor(model => model.Subject)
        - @Html.SmartLabelFor(model => model.Body) -
        - @Html.EditorFor(model => model.Body, "RichEditor") + @Html.EditorFor(model => model.Body, "Liquid", new { TemplateName = templateName }) @Html.ValidationMessageFor(model => model.Body)
        - - - - - - - - -
        - @Html.SmartLabelFor(model => model.LimitedToStores) - - @Html.EditorFor(model => model.LimitedToStores) - @Html.ValidationMessageFor(model => model.LimitedToStores) -
        - @Html.SmartLabelFor(model => model.AvailableStores) - - @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) - { - foreach (var store in Model.AvailableStores) - { - - } - } - else - { -
        @T("Admin.Configuration.Stores.NoStoresDefined")
        - } -
        + @Html.Partial("StoreSelector", Model) } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/Comments.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/Comments.cshtml index 79347bed2a..5edd8cdda4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/Comments.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/Comments.cshtml @@ -3,10 +3,8 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - int? filterByNewsItemId = ViewBag.FilterByNewsItemId; - //page title ViewBag.Title = T("Admin.ContentManagement.News.Comments").Text; }
        @@ -17,55 +15,54 @@
        - - - - -
        - @(Html.Telerik().Grid(Model.Data) - .Name("comments-grid") - .DataKeys(x => { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Id) - .Width(50); - columns.Bound(x => x.NewsItemTitle) - .Width(450) - .Template(x => Html.ActionLink(x.NewsItemTitle, "Edit", "News", new { id = x.NewsItemId }, new { })) - .ClientTemplate("\"><#= NewsItemTitle #>"); - columns.Bound(x => x.CustomerId) - .Width(200) - .Template(x => Html.ActionLink(x.CustomerName, "Edit", "Customer", new { id = x.CustomerId }, new { })) - .ClientTemplate("\"><#= CustomerName #>"); - columns.Bound(x => x.CommentTitle) - .Width(200); - columns.Bound(x => x.CommentText) - .Encoded(false); - columns.Bound(x => x.IpAddress) - .Width(100); - columns.Bound(x => x.CreatedOn) - .Width(150); - columns.Command(commands => - { - commands.Delete().Localize(T); - }).Width(90); - }) - .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => { - var settingBuilder = dataBinding.Ajax(); - if (filterByNewsItemId.HasValue) - { - settingBuilder = settingBuilder.Select("Comments", "News", new { filterByNewsItemId = filterByNewsItemId.Value }); - settingBuilder = settingBuilder.Delete("CommentDelete", "News", new { filterByNewsItemId = filterByNewsItemId.Value }); - } - else - { - settingBuilder = settingBuilder.Select("Comments", "News"); - settingBuilder = settingBuilder.Delete("CommentDelete", "News"); - } - }) - .PreserveGridState() - .EnableCustomBinding(true)) -
        + +
        + @(Html.Telerik().Grid(Model.Data) + .Name("comments-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Id) + .Width(50); + columns.Bound(x => x.NewsItemTitle) + .Width(450) + .Template(x => Html.ActionLink(x.NewsItemTitle, "Edit", "News", new { id = x.NewsItemId }, new { })) + .ClientTemplate("\"><#= NewsItemTitle #>"); + columns.Bound(x => x.CustomerId) + .Width(200) + .Template(x => Html.ActionLink(x.CustomerName, "Edit", "Customer", new { id = x.CustomerId }, new { })) + .ClientTemplate("\"><#= CustomerName #>"); + columns.Bound(x => x.CommentTitle) + .Width(200); + columns.Bound(x => x.CommentText) + .Encoded(false); + columns.Bound(x => x.IpAddress) + .Width(100); + columns.Bound(x => x.CreatedOn) + .Width(150); + columns.Command(commands => + { + commands.Delete().Localize(T); + }).Width(90); + }) + .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => + { + var settingBuilder = dataBinding.Ajax(); + if (filterByNewsItemId.HasValue) + { + settingBuilder = settingBuilder.Select("Comments", "News", new { filterByNewsItemId = filterByNewsItemId.Value }); + settingBuilder = settingBuilder.Delete("CommentDelete", "News", new { filterByNewsItemId = filterByNewsItemId.Value }); + } + else + { + settingBuilder = settingBuilder.Select("Comments", "News"); + settingBuilder = settingBuilder.Delete("CommentDelete", "News"); + } + }) + .PreserveGridState() + .EnableCustomBinding(true)) +
        \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/Create.cshtml index 4f2bb7d996..f3fc345949 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/Create.cshtml @@ -1,6 +1,5 @@ @model NewsItemModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.News.NewsItems.AddNew").Text; } @using (Html.BeginForm()) @@ -10,9 +9,15 @@ @T("Admin.ContentManagement.News.NewsItems.AddNew") @Html.ActionLink("(" + T("Admin.ContentManagement.News.NewsItems.BackToList") + ")", "List")
        - - + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml index aefa777216..f762b5a245 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/Edit.cshtml @@ -9,15 +9,25 @@ @T("Admin.ContentManagement.News.NewsItems.EditNewsItemDetails") - @Model.Title @Html.ActionLink("(" + T("Admin.ContentManagement.News.NewsItems.BackToList") + ")", "List")
        - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } - - - + + + - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } + @Html.DeleteConfirmation("newsitem-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml index aa82033bf7..2a45db4bf7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml @@ -3,7 +3,7 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - + ViewBag.Title = T("Admin.ContentManagement.News.NewsItems").Text; } @@ -13,89 +13,81 @@ @T("Admin.ContentManagement.News.NewsItems")
        - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } + + + + @T("Admin.Common.AddNew") + -  @T("Admin.Common.AddNew") - - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
        @if (Model.AvailableStores.Count > 1) { - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) -
        -   - - -
        +
        +
        + @Html.SmartLabelFor(model => model.SearchStoreId) + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All"), new { @class = "form-control" }) +
        +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        +
        } -

        - - - - - -
        - @(Html.Telerik().Grid() - .Name("newsitem-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Title) - .Width(450) - .Template(x => Html.ActionLink(x.Title, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= Title #>"); - columns.Bound(x => x.Published) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.LanguageName); - columns.Bound(x => x.Comments) - .Template( - @
        - @Html.ActionLink(T("Admin.ContentManagement.News.NewsItems.Fields.Comments").Text + " - " + @item.Comments, "Comments", new { filterByNewsItemId = item.Id }) -
        - ) - .ClientTemplate(""); - columns.Bound(x => x.StartDate); - columns.Bound(x => x.EndDate); - columns.Bound(x => x.LimitedToStores) - .Template(item => @Html.SymbolForBool(item.LimitedToStores)) - .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) - .Hidden(Model.AvailableStores.Count <= 1) - .Centered(); - columns.Bound(x => x.CreatedOn); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "News")) - .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        +
        + @(Html.Telerik().Grid() + .Name("newsitem-grid") + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Title) + .Width(450) + .Template(x => Html.ActionLink(x.Title, "Edit", new { id = x.Id })) + .ClientTemplate("\"><#= Title #>"); + columns.Bound(x => x.Published) + .Template(item => @Html.SymbolForBool(item.Published)) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.LanguageName); + columns.Bound(x => x.Comments) + .Template( + @
        + @Html.ActionLink(T("Admin.ContentManagement.News.NewsItems.Fields.Comments").Text + " - " + @item.Comments, "Comments", new { filterByNewsItemId = item.Id }) +
        + ) + .ClientTemplate(""); + columns.Bound(x => x.StartDate); + columns.Bound(x => x.EndDate); + columns.Bound(x => x.LimitedToStores) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + columns.Bound(x => x.CreatedOn); + }) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "News")) + .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        + + - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.LimitedToStores) - - @Html.EditorFor(model => model.LimitedToStores) - @Html.ValidationMessageFor(model => model.LimitedToStores) -
        - @Html.SmartLabelFor(model => model.AvailableStores) - - @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) - { - foreach (var store in Model.AvailableStores) - { - - } - } - else - { -
        @T("Admin.Configuration.Stores.NoStoresDefined")
        - }
        + @Html.Partial("StoreSelector", Model) } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml index da8b2d4fd5..982e5f05e2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml @@ -5,92 +5,76 @@ }
        - + @T("Admin.Promotions.newsLetterSubscriptions")
        - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
        - - - - - +
        +
        + @Html.SmartLabelFor(model => model.SearchEmail) + @Html.TextBoxFor(model => Model.SearchEmail, new { @class = "form-control" }) +
        @if (Model.AvailableStores.Count > 1) { -
        - - - +
        + @Html.SmartLabelFor(model => model.StoreId) + @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores, T("Admin.Common.All"), new { @class = "form-control" }) +
        } - - - - -
        - @Html.SmartLabelFor(model => model.SearchEmail) - - @Html.EditorFor(model => Model.SearchEmail) -
        - @Html.SmartLabelFor(model => model.StoreId) - - @Html.DropDownList("StoreId", Model.AvailableStores, T("Admin.Common.All")) -
        -   - - -
        - -

        - - - - - -
        - @(Html.Telerik().Grid(Model.NewsLetterSubscriptions.Data) - .Name("newsLetterSubscriptions-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Email) - .Width(500); - columns.Bound(x => x.Active) - .Template(item => @Html.SymbolForBool(item.Active)) - .ClientTemplate(@Html.SymbolForBool("Active")) - .Centered() - .Width(100); - columns.Bound(x => x.StoreName) - .Hidden(Model.AvailableStores.Count <= 1) - .ReadOnly(); - columns.Bound(x => x.CreatedOn) - .ReadOnly() - .Width(150); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(180); +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        + - }) - .Pageable(settings => settings.Total(Model.NewsLetterSubscriptions.Total).PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax() - .Select("SubscriptionList", "NewsLetterSubscription") - .Update("SubscriptionUpdate", "NewsLetterSubscription") - .Delete("SubscriptionDelete", "NewsLetterSubscription")) - .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .ClientEvents(x => x.OnError("grid_onError")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        +
        + @(Html.Telerik().Grid(Model.NewsLetterSubscriptions.Data) + .Name("newsLetterSubscriptions-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Email) + .Width(500); + columns.Bound(x => x.Active) + .Template(item => @Html.SymbolForBool(item.Active)) + .ClientTemplate(@Html.SymbolForBool("Active")) + .Centered() + .Width(100); + columns.Bound(x => x.StoreName) + .Hidden(Model.AvailableStores.Count <= 1) + .ReadOnly(); + columns.Bound(x => x.CreatedOn) + .ReadOnly() + .Width(150); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right", @class = "omega" }) + .Width(220); + }) + .Pageable(settings => settings.Total(Model.NewsLetterSubscriptions.Total).PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("SubscriptionList", "NewsLetterSubscription") + .Update("SubscriptionUpdate", "NewsLetterSubscription") + .Delete("SubscriptionDelete", "NewsLetterSubscription")) + .ClientEvents(events => events.OnDataBinding("onDataBinding")) + .ClientEvents(x => x.OnError("grid_onError")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        @@ -27,12 +26,10 @@
        -
        -
        @if (Model.Warnings.Count > 0) { -
        +
        @foreach (var warning in Model.Warnings) { @warning @@ -41,13 +38,10 @@
        }
        -
        -
        - + if (Model.ProductType == ProductType.SimpleProduct) { -
        - +
        -
        @Html.SmartLabelFor(model => model.UnitPriceInclTax) @@ -96,72 +90,59 @@ @Html.EditorFor(model => model.SubTotalExclTax)
        - -
        - -
        - @Html.Partial("_ProductAddAttributes", Model.ProductVariantAttributes) -
        - -
        - - @{ - var dataDictGiftCard = new ViewDataDictionary(); - dataDictGiftCard.TemplateInfo.HtmlFieldPrefix = "giftcard"; - @Html.Partial("_ProductAddGiftCardInfo", Model.GiftCard, dataDictGiftCard) - } + + +
        + @{ Html.RenderPartial("_ProductAddAttributes", Model); } +
        + + var dataDictGiftCard = new ViewDataDictionary(); + dataDictGiftCard.TemplateInfo.HtmlFieldPrefix = Model.GiftCardFieldPrefix; + @Html.Partial("_ProductAddGiftCardInfo", Model.GiftCard, dataDictGiftCard) -
        -

        - - +
        + + + + + @if(Model.ShowUpdateTotals) + { - - - @if(Model.ShowUpdateTotals) - { - - - - - } - - -
        + @Html.SmartLabelFor(model => model.AdjustInventory) + + @Html.EditorFor(model => model.AdjustInventory) + @Html.ValidationMessageFor(model => model.AdjustInventory) +
        - @Html.SmartLabelFor(model => model.AdjustInventory) - - @Html.EditorFor(model => model.AdjustInventory) - @Html.ValidationMessageFor(model => model.AdjustInventory) + @Html.SmartLabelFor(model => model.UpdateTotals)
        - @Html.SmartLabelFor(model => model.UpdateTotals) - - @Html.EditorFor(model => model.UpdateTotals) - @Html.ValidationMessageFor(model => model.UpdateTotals) -
          - + @Html.EditorFor(model => model.UpdateTotals) + @Html.ValidationMessageFor(model => model.UpdateTotals)
        -
        + } + + + + + + + } else if (Model.ProductType == ProductType.GroupedProduct) {
        - Grouped products are not currently supported for adding to an existing order. @* TODO TBD *@ + Grouped products are not currently supported for adding to an existing order.
        } else if (Model.ProductType == ProductType.BundledProduct) {
        - Bundled products are not currently supported for adding to an existing order. @* TODO TBD *@ -
        - } - else - { -
        - This product type (unknown) is not currently supported for adding to an existing order + Bundled products are not currently supported for adding to an existing order.
        } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml index 0deedb6ebd..2ff3e447b8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml @@ -1,6 +1,4 @@ @model ShipmentModel -@using SmartStore.Core.Domain.Tax; -@using SmartStore.Core.Domain.Catalog; @{ ViewBag.Title = string.Format(T("Admin.Orders.Shipments.AddNew.Title").Text, Model.OrderId); } @@ -11,13 +9,19 @@ @string.Format(T("Admin.Orders.Shipments.AddNew.Title").Text, Model.OrderId) @Html.ActionLink("(" + T("Admin.Orders.Shipments.BackToOrder") + ")", "Edit", new { id = Model.OrderId })
        - - + +
        + @Html.ValidationSummary(false) - +
        @Html.SmartLabelFor(model => model.TrackingNumber) @@ -27,70 +31,57 @@
        - - - - - - - - -
        -

        @T("Admin.Orders.Shipments.Products")

        -
        - - - - - - - - - - - - - @foreach (var item in Model.Items) - { - - - - - - - - } - -
        - @T("Admin.Orders.Shipments.Products.ProductName") - - @T("Admin.Orders.Shipments.Products.SKU") - - @T("Admin.Orders.Shipments.Products.QtyOrdered") - - @T("Admin.Orders.Shipments.Products.QtyShipped") - - @T("Admin.Orders.Shipments.Products.QtyToShip") -
        -
        - @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
        - @Html.Raw(item.AttributeInfo) - } -
        -
        -
        - @item.Sku -
        -
        - @item.QuantityOrdered - - @item.QuantityInAllShipments - - -
        -
        +
        @T("Admin.Orders.Shipments.Products")
        + +
        + + + + + + + + + + + + @foreach (var item in Model.Items) + { + + + + + + + + } + +
        + @T("Admin.Orders.Shipments.Products.ProductName") + + @T("Admin.Orders.Shipments.Products.SKU") + + @T("Admin.Orders.Shipments.Products.QtyOrdered") + + @T("Admin.Orders.Shipments.Products.QtyShipped") + + @T("Admin.Orders.Shipments.Products.QtyToShip") +
        + @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
        + @Html.Raw(item.AttributeInfo) + } +
        + @item.Sku + + @item.QuantityOrdered + + @item.QuantityInAllShipments + + +
        +
        } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddressEdit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddressEdit.cshtml index 17048944ba..5df3e80e05 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddressEdit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddressEdit.cshtml @@ -1,6 +1,5 @@ @model OrderAddressModel @{ - //page title ViewBag.Title = T("Admin.Orders.Address.EditAddress").Text; } @using (Html.BeginForm()) @@ -10,8 +9,12 @@ @T("Admin.Orders.Address.EditAddress") @Html.ActionLink("(" + T("Admin.Orders.Address.BackToOrder") + ")", "Edit", new { id = Model.OrderId })
        - +
        - @Html.Partial("_CreateOrUpdateAddress", Model) + + @Html.EditorFor(model => model.Address, "Address") } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByAmount.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByAmount.cshtml index 704f43ed71..83beff5a8e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByAmount.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByAmount.cshtml @@ -1,15 +1,13 @@ @model IList -@using Telerik.Web.Mvc.UI -
        +
        +
        + + @T("Admin.SalesReport.BestSellers.ByAmount") +
        -
        - -

        @T("Admin.SalesReport.BestSellers.ByAmount")

        -
        - -
        - +
        +
        @@ -31,7 +29,4 @@
        @T("Admin.SalesReport.Bestsellers.Fields.Name")
        - - -
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByQuantity.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByQuantity.cshtml index 96e35a14fb..125233eac8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByQuantity.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersBriefReportByQuantity.cshtml @@ -1,15 +1,13 @@ @model IList -@using Telerik.Web.Mvc.UI -
        - -
        - -

        @T("Admin.SalesReport.BestSellers.ByQuantity")

        -
        - -
        - +
        +
        + + @T("Admin.SalesReport.BestSellers.ByQuantity") +
        + +
        +
        @@ -31,7 +29,4 @@
        @T("Admin.SalesReport.Bestsellers.Fields.Name")
        - - -
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml index e035acfa1e..e7b3698fd4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml @@ -14,74 +14,49 @@
        - - - - - - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.StartDate) - - @Html.EditorFor(model => model.StartDate) -
        - @Html.SmartLabelFor(model => model.EndDate) - - @Html.EditorFor(model => Model.EndDate) -
        - @Html.SmartLabelFor(model => model.OrderStatusId) - - @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All")) -
        - @Html.SmartLabelFor(model => model.PaymentStatusId) - - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All")) -
        -   - - -
        - -

        +
        +
        + @Html.SmartLabelFor(model => model.StartDate) + @Html.EditorFor(model => model.StartDate) +
        +
        + @Html.SmartLabelFor(model => model.EndDate) + @Html.EditorFor(model => model.EndDate) +
        +
        + @Html.SmartLabelFor(model => model.OrderStatusId) + @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All")) +
        +
        + @Html.SmartLabelFor(model => model.PaymentStatusId) + @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All")) +
        + +
        + +
        +
        - - - - -
        - @(Html.Telerik().Grid() - .Name("salesreport-grid") - .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .Columns(columns => - { - columns.Bound(x => x.ProductName) - .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.TotalQuantity) - .Centered() - .Width(100); - columns.Bound(x => x.TotalAmount) - .RightAlign() - .Width(200); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("BestsellersReportList", "Order")) - .EnableCustomBinding(true)) -
        +
        + @(Html.Telerik().Grid() + .Name("salesreport-grid") + .ClientEvents(events => events.OnDataBinding("onDataBinding")) + .Columns(columns => + { + columns.Bound(x => x.ProductName) + .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); + columns.Bound(x => x.TotalQuantity) + .Centered() + .Width(200); + columns.Bound(x => x.TotalAmount) + .RightAlign() + .Width(200); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("BestsellersReportList", "Order")) + .EnableCustomBinding(true)) +
        - - @Html.Widget("order_edit_top") - - - - - - - - - - - - - - - - - - - @if (Model.AffiliateId != 0) - { - - - - - } - - - - - - - - - - - - @if (Model.RecurringPaymentId > 0) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.VatNumber)) - { - - - - - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) - { - - - - - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) - { - - - - - } - @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountInclTax)) - { - - - - - } - @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountExclTax)) - { - - - - - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) - { - - - - - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) - { - - - - - } - @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeInclTax)) - { - - - - - } - @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeExclTax)) - { - - - - - } - @if (Model.DisplayTaxRates) - { - foreach (var tr in Model.TaxRates) - { - - - - - } - } - @if (Model.DisplayTax) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.OrderTotalDiscount)) - { - - - - - } - @foreach (var gc in Model.GiftCards) - { - - - - - } - @if (Model.RedeemedRewardPoints > 0) - { - - - - - } - - - - - @if (!String.IsNullOrEmpty(Model.RefundedAmount)) - { - - - - - } - - - - - - - - - - @if (Model.AllowStoringCreditCardNumber) - { - - - - - } - @if (Model.AllowStoringCreditCardNumber) - { - - - - - } - @if (Model.AllowStoringCreditCardNumber || !String.IsNullOrEmpty(Model.CardNumber)) - { - - - - - } - @if (Model.AllowStoringCreditCardNumber) - { - - - - - } - @if (Model.AllowStoringCreditCardNumber) - { - - - - - } - @if (Model.AllowStoringCreditCardNumber) - { - - - - - } - @if (Model.AllowStoringCreditCardNumber) - { - - - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - } - @if (Model.AllowStoringDirectDebit) - { - - - - - - - } +@Html.DeleteConfirmation("order-delete") - @if (Model.DisplayPurchaseOrderNumber) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.AuthorizationTransactionId)) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.AuthorizationTransactionResult)) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.CaptureTransactionId)) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.CaptureTransactionResult)) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.SubscriptionTransactionId)) - { - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.OrderStatus) - - @Model.OrderStatus -   - @if (Model.CanCancelOrder) - { - - } - - @if (Model.CanCompleteOrder) - { - - } -
        - @Html.SmartLabelFor(model => model.OrderNumber) - - @Model.OrderNumber -
        - @Html.SmartLabelFor(model => model.OrderGuid) - - @Model.OrderGuid -
        - @Html.SmartLabelFor(model => model.StoreName) - - @Model.StoreName -
        - @Html.SmartLabelFor(model => model.AffiliateId) - - @Model.AffiliateFullName -
        -
        -
        - @Html.SmartLabelFor(model => model.CustomerId) - - @Model.CustomerName -
        - @Html.SmartLabelFor(model => model.CustomerIp) - - @Model.CustomerIp -
        - @Html.SmartLabelFor(model => model.RecurringPaymentId) - - @T("Admin.Common.View") -
        - @Html.SmartLabelFor(model => model.VatNumber) - - @Model.VatNumber -
        - @Html.SmartLabelFor(model => model.OrderSubtotalInclTax) - - @Model.OrderSubtotalInclTax -
        - @Html.SmartLabelFor(model => model.OrderSubtotalExclTax) - - @Model.OrderSubtotalExclTax -
        - @Html.SmartLabelFor(model => model.OrderSubTotalDiscountInclTax) - - @Model.OrderSubTotalDiscountInclTax -
        - @Html.SmartLabelFor(model => model.OrderSubTotalDiscountExclTax) - - @Model.OrderSubTotalDiscountExclTax -
        - @Html.SmartLabelFor(model => model.OrderShippingInclTax) - - @Model.OrderShippingInclTax -
        - @Html.SmartLabelFor(model => model.OrderShippingExclTax) - - @Model.OrderShippingExclTax -
        - @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeInclTax) - - @Model.PaymentMethodAdditionalFeeInclTax -
        - @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeExclTax) - - @Model.PaymentMethodAdditionalFeeExclTax -
        - @Html.SmartLabelFor(model => model.Tax) - - @tr.Rate% - @tr.Value -
        - @Html.SmartLabelFor(model => model.Tax) - - @Model.Tax -
        - @Html.SmartLabelFor(model => model.OrderTotalDiscount) - - @Model.OrderTotalDiscount -
        - @Html.SmartLabelFor(model => model.GiftCards[0].CouponCode) (@(gc.CouponCode)) - - @gc.Amount -
        - @Html.SmartLabelFor(model => model.RedeemedRewardPoints) - - @Model.RedeemedRewardPoints @T("Admin.Orders.Fields.RedeemedRewardPoints.Points") - / - @Model.RedeemedRewardPointsAmount -
        - @Html.SmartLabelFor(model => model.OrderTotal) - - @Model.OrderTotal -
        - @Html.SmartLabelFor(model => model.RefundedAmount) - - @Model.RefundedAmount -
        - -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.OrderSubtotalInclTaxValue) - - @Html.EditorFor(model => model.OrderSubtotalInclTaxValue) - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.EditorFor(model => model.OrderSubtotalExclTaxValue) - @T("Admin.Orders.Fields.Edit.ExclTax") -
        - @Html.SmartLabelFor(model => model.OrderSubTotalDiscountInclTaxValue) - - @Html.EditorFor(model => model.OrderSubTotalDiscountInclTaxValue) - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.EditorFor(model => model.OrderSubTotalDiscountExclTaxValue) - @T("Admin.Orders.Fields.Edit.ExclTax") -
        - @Html.SmartLabelFor(model => model.OrderShippingInclTaxValue) - - @Html.EditorFor(model => model.OrderShippingInclTaxValue) - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.EditorFor(model => model.OrderShippingExclTaxValue) - @T("Admin.Orders.Fields.Edit.ExclTax") -
        - @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) - - @Html.EditorFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.EditorFor(model => model.PaymentMethodAdditionalFeeExclTaxValue) - @T("Admin.Orders.Fields.Edit.ExclTax") -
        - @Html.SmartLabelFor(model => model.TaxRatesValue) - - @Html.EditorFor(model => model.TaxRatesValue) -
        - @Html.SmartLabelFor(model => model.TaxValue) - - @Html.EditorFor(model => model.TaxValue) -
        - @Html.SmartLabelFor(model => model.OrderTotalDiscountValue) - - @Html.EditorFor(model => model.OrderTotalDiscountValue) -
        - @Html.SmartLabelFor(model => model.OrderTotalValue) - - @Html.EditorFor(model => model.OrderTotalValue) -
        -
        - -
        - - - -
        -
        -
        - @Html.SmartLabelFor(model => model.CardType) - - @Model.CardType - @Html.EditorFor(model => model.CardType) -
        - @Html.SmartLabelFor(model => model.CardName) - - @Model.CardName - @Html.EditorFor(model => model.CardName) -
        - @Html.SmartLabelFor(model => model.CardNumber) - - @Model.CardNumber - @Html.EditorFor(model => model.CardNumber) -
        - @Html.SmartLabelFor(model => model.CardCvv2) - - @Model.CardCvv2 - @Html.EditorFor(model => model.CardCvv2) -
        - @Html.SmartLabelFor(model => model.CardExpirationMonth) - - @Model.CardExpirationMonth - @Html.EditorFor(model => model.CardExpirationMonth) -
        - @Html.SmartLabelFor(model => model.CardExpirationYear) - - @Model.CardExpirationYear - @Html.EditorFor(model => model.CardExpirationYear) -
        - - - -
        -
        -
        - @Html.SmartLabelFor(model => model.DirectDebitAccountHolder) - - @Model.DirectDebitAccountHolder - @Html.EditorFor(model => model.DirectDebitAccountHolder) -
        - @Html.SmartLabelFor(model => model.DirectDebitAccountNumber) - - @Model.DirectDebitAccountNumber - @Html.EditorFor(model => model.DirectDebitAccountNumber) -
        - @Html.SmartLabelFor(model => model.DirectDebitBankCode) - - @Model.DirectDebitBankCode - @Html.EditorFor(model => model.DirectDebitBankCode) -
        - @Html.SmartLabelFor(model => model.DirectDebitBankName) - - @Model.DirectDebitBankName - @Html.EditorFor(model => model.DirectDebitBankName) -
        - @Html.SmartLabelFor(model => model.DirectDebitBIC) - - @Model.DirectDebitBIC - @Html.EditorFor(model => model.DirectDebitBIC) -
        - @Html.SmartLabelFor(model => model.DirectDebitCountry) - - @Model.DirectDebitCountry - @Html.EditorFor(model => model.DirectDebitCountry) -
        - @Html.SmartLabelFor(model => model.DirectDebitIban) - - @Model.DirectDebitIban - @Html.EditorFor(model => model.DirectDebitIban) -
        - - - -
        -
        -
        - @Html.SmartLabelFor(model => model.PurchaseOrderNumber) - - @Model.PurchaseOrderNumber -
        - @Html.SmartLabelFor(model => model.AuthorizationTransactionId) - - @Model.AuthorizationTransactionId -
        - @Html.SmartLabelFor(model => model.AuthorizationTransactionResult) - - @Model.AuthorizationTransactionResult -
        - @Html.SmartLabelFor(model => model.CaptureTransactionId) - - @Model.CaptureTransactionId -
        - @Html.SmartLabelFor(model => model.CaptureTransactionResult) - - @Model.CaptureTransactionResult -
        - @Html.SmartLabelFor(model => model.SubscriptionTransactionId) - - @Model.SubscriptionTransactionId -
        - @Html.SmartLabelFor(model => model.PaymentMethod) - - @Model.PaymentMethod - @if (Model.PaymentMethodSystemName.HasValue()) - { - (@Model.PaymentMethodSystemName) - } -
        - @Html.SmartLabelFor(model => model.PaymentStatus) - - @Model.PaymentStatus -   - @if (Model.CanMarkOrderAsPaid) - { - -    - } - @if (Model.CanCapture) - { - - } - @if (Model.CanRefund) - { - - } - @if (Model.CanRefundOffline) - { - - } - @if (Model.CanPartiallyRefund) - { - - } - @if (Model.CanPartiallyRefundOffline) - { - - } - @if (Model.CanVoid) - { - - } - @if (Model.CanVoidOffline) - { - - } - -
        - @Html.SmartLabelFor(model => model.CreatedOn) - - @Html.DisplayFor(model => model.CreatedOn) -
        - @Html.SmartLabelFor(model => model.UpdatedOn) - - @Html.DisplayFor(model => model.UpdatedOn) -
        -
        -
        - @Html.SmartLabelFor(model => model.AcceptThirdPartyEmailHandOver) - - @(Model.AcceptThirdPartyEmailHandOver ? T("Common.Yes") : T("Common.No")) -
        - @if (Model.CustomerComment.HasValue()) - { -

        @T("Admin.Order.CustomerComment.Heading")

        -
        - @Model.CustomerComment -
        - } -
        -} -@helper TabBillingInfo() -{ - - - - - -
        - @Html.SmartLabelFor(model => model.BillingAddress) - -
        - - @if (Model.BillingAddress.FirstNameEnabled || Model.BillingAddress.LastNameEnabled) - { - - - - - } - @if (Model.BillingAddress.EmailEnabled) - { - - - - - } - @if (Model.BillingAddress.PhoneEnabled) - { - - - - - } - @if (Model.BillingAddress.FaxEnabled) - { - - - - - } - @if (Model.BillingAddress.CompanyEnabled) - { - - - - - } - @if (Model.BillingAddress.StreetAddressEnabled) - { - - - - - } - @if (Model.BillingAddress.StreetAddress2Enabled) - { - - - - - } - @if (Model.BillingAddress.CityEnabled) - { - - - - - } - @if (Model.BillingAddress.StateProvinceEnabled) - { - - - - - } - @if (Model.BillingAddress.ZipPostalCodeEnabled) - { - - - - - } - @if (Model.BillingAddress.CountryEnabled) - { - - - - - } - - - -
        - @T("Admin.Orders.Address.FullName") - - @Model.BillingAddress.FirstName @Model.BillingAddress.LastName -
        - @T("Admin.Orders.Address.Email") - - @Model.BillingAddress.Email -
        - @T("Admin.Orders.Address.Phone") - - @Model.BillingAddress.PhoneNumber -
        - @T("Admin.Orders.Address.Fax") - - @Model.BillingAddress.FaxNumber -
        - @T("Admin.Orders.Address.Company") - - @Model.BillingAddress.Company -
        - @T("Admin.Orders.Address.Address1") - - @Model.BillingAddress.Address1 -
        - @T("Admin.Orders.Address.Address2") - - @Model.BillingAddress.Address2 -
        - @T("Admin.Orders.Address.City") - - @Model.BillingAddress.City -
        - @T("Admin.Orders.Address.StateProvince") - - @Model.BillingAddress.StateProvinceName -
        - @T("Admin.Orders.Address.ZipPostalCode") - - @Model.BillingAddress.ZipPostalCode -
        - @T("Admin.Orders.Address.Country") - - @Model.BillingAddress.CountryName -
        -  @T("Admin.Common.Edit") -
        -
        -
        -} -@helper TabShippingInfo() +@helper TabInfo() { - if (Model.IsShippable && Model.ShippingAddress != null) - { - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.ShippingAddress) - -
        - - @if (Model.ShippingAddress.FirstNameEnabled || Model.ShippingAddress.LastNameEnabled) - { - - - - - } - @if (Model.ShippingAddress.EmailEnabled) - { - - - - - } - @if (Model.ShippingAddress.PhoneEnabled) - { - - - - - } - @if (Model.ShippingAddress.FaxEnabled) - { - - - - - } - @if (Model.ShippingAddress.CompanyEnabled) - { - - - - - } - @if (Model.ShippingAddress.StreetAddressEnabled) - { - - - - - } - @if (Model.ShippingAddress.StreetAddress2Enabled) - { - - - - - } - @if (Model.ShippingAddress.CityEnabled) - { - - - - - } - @if (Model.ShippingAddress.StateProvinceEnabled) - { - - - - - } - @if (Model.ShippingAddress.ZipPostalCodeEnabled) - { - - - - - } - @if (Model.ShippingAddress.CountryEnabled) - { - - - - - } - - - -
        - @T("Admin.Orders.Address.FullName") - - @Model.ShippingAddress.FirstName @Model.ShippingAddress.LastName -
        - @T("Admin.Orders.Address.Email"): - - @Model.ShippingAddress.Email -
        - @T("Admin.Orders.Address.Phone") - - @Model.ShippingAddress.PhoneNumber -
        - @T("Admin.Orders.Address.Fax") - - @Model.ShippingAddress.FaxNumber -
        - @T("Admin.Orders.Address.Company") - - @Model.ShippingAddress.Company -
        - @T("Admin.Orders.Address.Address1") - - @Model.ShippingAddress.Address1 -
        - @T("Admin.Orders.Address.Address2") - - @Model.ShippingAddress.Address2 -
        - @T("Admin.Orders.Address.City") - - @Model.ShippingAddress.City -
        - @T("Admin.Orders.Address.StateProvince") - - @Model.ShippingAddress.StateProvinceName -
        - @T("Admin.Orders.Address.ZipPostalCode") - - @Model.ShippingAddress.ZipPostalCode -
        - @T("Admin.Orders.Address.Country") - - @Model.ShippingAddress.CountryName -
        -  @T("Admin.Common.Edit") -
        -
        - -
        - @Html.SmartLabelFor(model => model.ShippingMethod) - - @Model.ShippingMethod -
        - @Html.SmartLabelFor(model => model.ShippingStatus) - - @Model.ShippingStatus -
        - -
        -
        - -

        @T("Admin.Orders.Shipments")

        - -
        -
        - - - - - @if (Model.CanAddNewShipments) - { - - - - } -
        - @(Html.Telerik().Grid().Name("shipments-grid") - .DataBinding(binding => - { - binding.Ajax().Select("ShipmentsSelect", "Order", new { orderId = Model.Id }); - }) - .Columns(columns => - { - columns.Bound(x => x.Id) - .Template(x => Html.ActionLink(x.Id.ToString(), "ShipmentDetails", "Order", new { id = x.Id }, new { })) - .ClientTemplate("\"><#= Id #>"); - columns.Bound(x => x.TrackingNumber); - columns.Bound(x => x.TotalWeight); - columns.Bound(x => x.ShippedDate); - columns.Bound(x => x.DeliveryDate); - }) - .EnableCustomBinding(true)) - - @if (Model.CanAddNewShipments) - { -
        - -
        - } -
        - -
        - } - else - { - - - - - -
        - - @T("Admin.Orders.ShippingInfo.NotRequired") -
        - } + @Html.Partial("_Edit.Info") } -@helper BundleItemsInfo(OrderModel.OrderItemModel parentItem) +@helper TabBillingAndShipment() { - if (parentItem.BundleItems != null) - { -
        - @foreach (var item in parentItem.BundleItems.OrderBy(x => x.DisplayOrder)) - { -
        -
        - @Html.ActionLink(item.ProductName, "Edit", "Product", new { id = item.ProductId }, new { }) - @if (item.Quantity > 1) - { - - × @item.Quantity - - } -
        - @if (!String.IsNullOrWhiteSpace(item.PriceWithDiscount)) - { -
        - @Html.Raw(item.PriceWithDiscount) -
        - } - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
        - @Html.Raw(item.AttributeInfo) -
        - } -
        - } -
        - } + @Html.Partial("_Edit.BillingAndShipment") } @helper TabProducts() { - - - if (Model.AutoUpdateOrderItemInfo.HasValue()) - { -
        - - @Html.Raw(Model.AutoUpdateOrderItemInfo) -
        - } - - - - - - @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) - { - - - - } - - - -
        - - - - - - @if (Model.HasDownloadableProducts) - { - - } - - - - - - - - - @foreach (var item in Model.Items) - { - - - @if (Model.HasDownloadableProducts) - { - - } - - - - - - - } - -
        - @T("Admin.Orders.Products.ProductName") - - @T("Admin.Orders.Products.Download") - - @T("Admin.Orders.Products.Price") - - @T("Admin.Orders.Products.Quantity") - - @T("Admin.Orders.Products.Discount") - - @T("Admin.Orders.Products.Total") - - @T("Admin.Common.Edit") -
        -
        - @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) - - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
        - @Html.Raw(item.AttributeInfo) - } - @if (!String.IsNullOrEmpty(item.RecurringInfo)) - { -
        - @Html.Raw(item.RecurringInfo) - } - @if (!String.IsNullOrEmpty(item.Sku)) - { -
        - @T("Admin.Orders.Products.SKU"): - @item.Sku - } - @if (item.ProductType == ProductType.BundledProduct) - { -
        - @BundleItemsInfo(item) - } - @if (item.ReturnRequests.Count > 0) - { -
        -
        - for (int i = 0; i < item.ReturnRequests.Count; ++i) - { - var returnRequest = item.ReturnRequests[i]; - - @returnRequest.StatusString - - - @T("Admin.Orders.Products.ReturnRequest") @returnRequest.Id - , - - @T("Admin.ReturnRequests.Fields.Quantity") @returnRequest.Quantity - if (i != item.ReturnRequests.Count - 1) - { -
        - } - } - } - @if (item.PurchasedGiftCardIds.Count > 0) - { -
        -
        - @T("Admin.Orders.Products.GiftCards"): - for (int i = 0; i < item.PurchasedGiftCardIds.Count; i++) - { - @item.PurchasedGiftCardIds[i] - if (i != item.PurchasedGiftCardIds.Count - 1) - { - , - } - } - } -
        -
        - @if (item.IsDownload) - { -
        - @string.Format(T("Admin.Orders.Products.Download.DownloadCount").Text, item.DownloadCount) - -
        -
        - if (item.DownloadActivationType == DownloadActivationType.Manually) - { -
        - -
        -
        - } -
        - - @T("Admin.Orders.Products.License") - - - @if (item.LicenseDownloadId.HasValue && item.LicenseDownloadId.Value > 0) - { - @T("Admin.Orders.Products.License.DownloadLicense") -
        - } - -
        - } -
        - @if (Model.AllowCustomersToSelectTaxDisplayType) - { - @item.UnitPriceInclTax -
        - @item.UnitPriceExclTax - } - else - { - switch (Model.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - @item.UnitPriceExclTax - } - break; - case TaxDisplayType.IncludingTax: - { - @item.UnitPriceInclTax - } - break; - default: - break; - } - } - -
        - - - - - - - - - - - - - -
        - @T("Admin.Orders.Products.Edit.InclTax") - - -
        - @T("Admin.Orders.Products.Edit.ExclTax") - - -
        - @T("Admin.Orders.Products.AddNew.TaxRate") - - -
        -
        -
        - @item.Quantity - -
        - - - - -
        - -
        -
        -
        - @if (Model.AllowCustomersToSelectTaxDisplayType) - { - @item.DiscountInclTax -
        - @item.DiscountExclTax - } - else - { - switch (Model.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - @item.DiscountExclTax - } - break; - case TaxDisplayType.IncludingTax: - { - @item.DiscountInclTax - } - break; - default: - break; - } - } -
        -
        -
        - - - - - - - - - -
        - @T("Admin.Orders.Products.Edit.InclTax") - - -
        - @T("Admin.Orders.Products.Edit.ExclTax") - - -
        -
        -
        - @if (Model.AllowCustomersToSelectTaxDisplayType) - { - @item.SubTotalInclTax -
        - @item.SubTotalExclTax - } - else - { - switch (Model.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - @item.SubTotalExclTax - } - break; - case TaxDisplayType.IncludingTax: - { - @item.SubTotalInclTax - } - break; - default: - break; - } - } -
        -
        -
        - - - - - - - - - -
        - @T("Admin.Orders.Products.Edit.InclTax") - - -
        - @T("Admin.Orders.Products.Edit.ExclTax") - - -
        -
        -
        - - @if (item.IsReturnRequestPossible) - { - - } - - - - - - - - - - -
        -
        -
        - @Html.Raw(Model.CheckoutAttributeInfo) -
        -
        - -
        + @Html.Partial("_Edit.Products") } + @helper TabOrderNotes() { - - - - -
        - @(Html.Telerik().Grid().Name("ordernotes-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id).RouteKey("orderNoteId"); - keys.Add(x => x.OrderId).RouteKey("orderId"); - }) - .DataBinding(binding => - { - binding.Ajax().Select("OrderNotesSelect", "Order", new { orderId = Model.Id }) - .Delete("OrderNoteDelete", "Order"); - }) - .Columns(columns => - { - columns.Bound(x => x.CreatedOn).Width(140); - columns.Bound(x => x.Note).Encoded(false); - columns.Bound(x => x.DisplayToCustomer) - .Template(item => @Html.SymbolForBool(item.DisplayToCustomer)) - .ClientTemplate(@Html.SymbolForBool("DisplayToCustomer")) - .Centered() - .Width(140); - columns.Command(commands => - { - commands.Delete().Localize(T); - }).Width(140).Title(T("Admin.Common.Delete").Text); - }) - .EnableCustomBinding(true)) -
        - - - - - - - - - - - - - - - - - -
        -
        -
        @T("Admin.Orders.OrderNotes.AddTitle")
        -
        -
        - @Html.SmartLabelFor(model => model.AddOrderNoteMessage) - - @Html.TextAreaFor(model => model.AddOrderNoteMessage, new { @class = "control-xlarge", style = "height: 120px;" }) - @Html.ValidationMessageFor(model => model.AddOrderNoteMessage) -
        - @Html.SmartLabelFor(model => model.AddOrderNoteDisplayToCustomer) - - @Html.EditorFor(model => model.AddOrderNoteDisplayToCustomer) - @Html.ValidationMessageFor(model => model.AddOrderNoteDisplayToCustomer) -
        -   - - -
        - - + @Html.Partial("_Edit.OrderNotes") } @helper TabOrderAttributes() @@ -1831,4 +78,4 @@ @Html.Action("GenericAttributes", "Common", new { entityName = "Order", entityId = Model.Id }) } -@Html.Partial("_AutoUpdateOrderItem", Model.AutoUpdateOrderItem) \ No newline at end of file +@{ Html.RenderPartial("_AutoUpdateOrderItem", Model.AutoUpdateOrderItem); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml index 8cc2132d70..667d9cc779 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml @@ -11,194 +11,172 @@ {
        - + @T("Admin.Orders")
        -
        - @Html.Widget("admin_button_toolbar_before") + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - +
        + @Html.SmartLabelFor(model => model.StoreId) + @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores, allString, new { @class = "form-control" }) +
        } - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.StartDate) - - @Html.EditorFor(model => model.StartDate) -
        - @Html.SmartLabelFor(model => model.EndDate) - - @Html.EditorFor(model => Model.EndDate) -
        - @Html.SmartLabelFor(model => model.CustomerName) - - @Html.EditorFor(model => Model.CustomerName) -
        - @Html.SmartLabelFor(model => model.CustomerEmail) - - @Html.EditorFor(model => Model.CustomerEmail) -
        - @Html.SmartLabelFor(model => model.OrderStatusIds) - - @Html.DropDownList("OrderStatusIds", Model.AvailableOrderStatuses, null, new { multiple = "multiple" }) -
        - @Html.SmartLabelFor(model => model.PaymentStatusIds) - - @Html.DropDownList("PaymentStatusIds", Model.AvailablePaymentStatuses, null, new { multiple = "multiple" }) -
        - @Html.SmartLabelFor(model => model.ShippingStatusIds) - - @Html.DropDownList("ShippingStatusIds", Model.AvailableShippingStatuses, null, new { multiple = "multiple" }) -
        - @Html.SmartLabelFor(model => model.StoreId) - - @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores, allString) -
        - @Html.SmartLabelFor(model => model.OrderGuid) - - @Html.EditorFor(model => Model.OrderGuid) -
        - @Html.SmartLabelFor(model => model.OrderNumber) - - @Html.EditorFor(model => Model.OrderNumber) -
        - @Html.SmartLabelFor(model => model.GoDirectlyToNumber) - - @Html.EditorFor(model => Model.GoDirectlyToNumber) - -
        -   - - -
        - -

        + else + { +
        + } +
        + @Html.SmartLabelFor(model => model.OrderStatusIds) + @Html.DropDownList("OrderStatusIds", Model.AvailableOrderStatuses, null, new { multiple = "multiple", @class = "form-control" }) +
        +
        + @Html.SmartLabelFor(model => model.PaymentStatusIds) + @Html.DropDownList("PaymentStatusIds", Model.AvailablePaymentStatuses, null, new { multiple = "multiple", @class = "form-control" }) +
        +
        + @Html.SmartLabelFor(model => model.ShippingStatusIds) + @Html.DropDownList("ShippingStatusIds", Model.AvailableShippingStatuses, null, new { multiple = "multiple", @class = "form-control" }) +
        - - - - -
        - @(Html.Telerik().Grid() - .Name("orders-grid") - .ClientEvents(events => events - .OnDataBinding("onDataBinding") - .OnDataBound("onDataBound") - .OnComplete("onComplete")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        + - columns.Bound(x => x.OrderNumber) - .ClientTemplate(@Html.LabeledOrderNumber()); - columns.Bound(x => x.OrderStatus); - columns.Bound(x => x.PaymentStatus); - columns.Bound(x => x.ShippingStatus); - columns.Bound(x => x.CustomerName); - columns.Bound(x => x.CustomerEmail); - columns.Bound(x => x.StoreName) - .Hidden(Model.AvailableStores.Count <= 1); - columns.Bound(x => x.CreatedOn); - columns.Bound(x => x.OrderTotal) - .Width(180) - .RightAlign() - .FooterTemplate( - string.Format("
        {0}:
        " + - (hideProfitReport ? "{1}" : "
        {1}:
        ") + - "
        {2}:
        " + - "
        {3}:
        ", - @T("Admin.Orders.Report.Summary").Text, - (hideProfitReport ? "" : @T("Admin.Orders.Report.Profit").Text), - @T("Admin.Orders.Report.Tax").Text, - @T("Admin.Orders.Report.Total").Text)); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("OrderList", "Order")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        - +
        + @(Html.Telerik().Grid() + .Name("orders-grid") + .TableHtmlAttributes(new { @class = "multiline-grid" }) + .ClientEvents(events => events + .OnDataBinding("onDataBinding") + .OnDataBound("onDataBound") + .OnComplete("onComplete")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + @*TODO: number of products, product summary?*@ + columns.Bound(x => x.OrderNumber) + .Width(150) + .Title(T("Admin.Order")) + .ClientTemplate(@Html.LabeledOrderNumber()); + columns.Bound(x => x.CustomerName) + //.Width(300) + .Title(T("Admin.Orders.Fields.Customer")) + .ClientTemplate( + "
        <#= CustomerName #>
        " + + "
        <#= CustomerEmail #>
        "); + columns.Bound(x => x.ShippingStatus) + .Title(T("Admin.Orders.Shipment")) + .ClientTemplate( + "
        " + + "<# if(IsShippable){ #><#= ShippingAddressString #><# } #>" + + "<# if(IsShippable){ #>
        <#= ViaShippingMethod #>
        <# } #>" + + "<# if(!IsShippable){ #><#= ShippingStatus #><# } #>" + + "
        "); + columns.Bound(x => x.CreatedOn) + .Width(200) + .Title(T("Order.OrderDate")) + .ClientTemplate( + "
        <#= CreatedOnString #>
        " + + "
        <#= FromStore #>
        "); + columns.Bound(x => x.OrderStatus) + .Width(100) + .Title(T("Admin.Orders.Fields.OrderStatus")) + .ClientTemplate("<#= OrderStatus #>"); + columns.Bound(x => x.OrderTotal) + .Width(200) + .RightAlign() + .ClientTemplate( + "
        <#= OrderTotal #>
        " + + "<# if(HasPaymentMethod){ #>
        <#= WithPaymentMethod #>
        <# } #>") + .FooterTemplate( + string.Format("
        {0}:
        " + + (hideProfitReport ? "{1}" : "
        {1}:
        ") + + "
        {2}:
        " + + "
        {3}:
        ", + @T("Admin.Orders.Report.Summary").Text, + (hideProfitReport ? "" : @T("Admin.Orders.Report.Profit").Text), + @T("Admin.Orders.Report.Tax").Text, + @T("Admin.Orders.Report.Total").Text)); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("OrderList", "Order")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        + - } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderAverageReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderAverageReport.cshtml index 2f804ca6bc..efb5935d7a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderAverageReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderAverageReport.cshtml @@ -1,53 +1,42 @@ @model IList -@using Telerik.Web.Mvc; -@using Telerik.Web.Mvc.UI -
        -
        +
        +
        +

        @T("Admin.SalesReport.Average.SumTodayOrders")

        +
        -
        - @T("Admin.SalesReport.Average.SumTodayOrders") -
        - - @{ - var list = Model.Reverse().ToList(); - var length = Model.Count; - } - @for (int i = 0; i < list.Count(); ++i ) - { - var x = list[i]; -
        -
        @x.OrderStatus
        -
        @x.SumTodayOrders
        -
        - } - -
        + @for (int i = 0; i < Model.Count; ++i) + { + var x = Model[i]; +
        +
        @x.OrderStatus
        +
        @x.SumTodayOrders
        +
        + }
        -
        - -
        - -

        @T("Admin.SalesReport.Average")

        -
        - -
        - - - - - - - - - - - - @foreach (var x in Model) - { - - + + + + + } + +
        @T("Admin.SalesReport.Average.OrderStatus")@T("Admin.SalesReport.Average.SumThisWeekOrders")@T("Admin.SalesReport.Average.SumThisMonthOrders")@T("Admin.SalesReport.Average.SumThisYearOrders")@T("Admin.SalesReport.Average.SumAllTimeOrders")
        +
        +
        + + @T("Admin.SalesReport.Average") +
        + +
        + + + + + + + + + + + + @foreach (var x in Model) + { + + - - - - - - } - -
        @T("Admin.SalesReport.Average.OrderStatus")@T("Admin.SalesReport.Average.SumThisWeekOrders")@T("Admin.SalesReport.Average.SumThisMonthOrders")@T("Admin.SalesReport.Average.SumThisYearOrders")@T("Admin.SalesReport.Average.SumAllTimeOrders")
        @if (x.Url.HasValue()) { @x.OrderStatus @@ -57,15 +46,14 @@ @x.OrderStatus } @x.SumThisWeekOrders@x.SumThisMonthOrders@x.SumThisYearOrders@x.SumAllTimeOrders
        -
        - +
        @x.SumThisWeekOrders@x.SumThisMonthOrders@x.SumThisYearOrders@x.SumAllTimeOrders
        +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderIncompleteReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderIncompleteReport.cshtml index f248bbb257..a5600493ac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderIncompleteReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/OrderIncompleteReport.cshtml @@ -1,16 +1,13 @@ @model IList -@using Telerik.Web.Mvc; -@using Telerik.Web.Mvc.UI -
        - -
        - -

        @T("Admin.SalesReport.Incomplete")

        -
        - -
        - +
        +
        + + @T("Admin.SalesReport.Incomplete") +
        + +
        +
        @@ -44,8 +41,5 @@
        @T("Admin.SalesReport.Incomplete.Item")
        - - -
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/PartiallyRefundOrderPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/PartiallyRefundOrderPopup.cshtml index 8de2303643..6eb5b973cc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/PartiallyRefundOrderPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/PartiallyRefundOrderPopup.cshtml @@ -1,8 +1,6 @@ -@{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} -@model OrderModel -@{ +@model OrderModel +@{ + Layout = "_AdminPopupLayout"; ViewBag.Title = T("Admin.Orders.Fields.PartialRefund").Text; } @@ -10,45 +8,33 @@ {
        - @T("Admin.Orders.Fields.PartialRefund") + @string.Format(T("Admin.Orders.Fields.PartialRefund.OrderInfo").Text, Model.Id)
        @Html.ValidationSummary(false) - - if (ViewBag.RefreshPage == true) - { - - } - -

        - @string.Format(T("Admin.Orders.Fields.PartialRefund.OrderInfo").Text, Model.Id) -

        - - + +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.Print.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.Print.cshtml index 477c61960a..b9742a9421 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.Print.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.Print.cshtml @@ -1,170 +1,201 @@ -@model IEnumerable -@using SmartStore.Core.Domain.Orders; -@using SmartStore.Core.Domain.Catalog; -@using SmartStore.Admin.Models.Shipping; -@using SmartStore.Services.Localization; -@using SmartStore.Core.Html; +@using SmartStore.Core.Domain.Catalog; +@model IEnumerable @{ - - Layout = "~/Views/Shared/_Print.cshtml"; - - //title - Html.AddTitleParts(T("Admin.Orders.Shipments.ViewDetails").Text); - - var pdfMode = ViewBag.PdfMode == true; - if (!pdfMode) - { - Html.AddBodyCssClass("screen"); - } + Layout = "_Print"; + Html.AddTitleParts(T("Admin.Orders.Shipments.ViewDetails").Text); + + var pdfMode = ViewBag.PdfMode == true; + if (!pdfMode) + { + Html.AddBodyCssClass("screen"); + } } @functions { - private IHtmlString JoinValues(string[] values, string separator = " ") - { - var normalized = values.Where(x => x.HasValue()); - return MvcHtmlString.Create(String.Join(separator, normalized)); - } + private IHtmlString JoinValues(string[] values, string separator = " ") + { + var normalized = values.Where(x => x.HasValue()); + return MvcHtmlString.Create(String.Join(separator, normalized)); + } }
        - @foreach (var shipment in Model) - { - var company = shipment.MerchantCompanyInfo; - var address = shipment.ShippingAddress; - - var colWidths = new int[] { 50, 20, 10 }; - -
        - @Html.Widget("shipmentdetails_top") - -
        -
        -
        - - @{ - var city = JoinValues(new string[] { company.ZipCode, company.City }).ToString(); - var country = JoinValues(new string[] { company.CountryName, company.StateName }, ", ").ToString(); - } - @JoinValues(new string[] { company.CompanyName, company.Street, company.Street2, city, country }, " · ") - -
        -
        - @if (address.Company.HasValue()) + @foreach (var shipment in Model) + { + var company = shipment.MerchantCompanyInfo; + var address = shipment.ShippingAddress; + var showGtin = shipment.Items.Any(x => !String.IsNullOrEmpty(x.Gtin)); + + var colWidths = new int[] { 50, 20, 10 }; + +
        + @{ Html.RenderWidget("shipmentdetails_top"); } + + +
        +
        +
        + + @{ + var city = JoinValues(new string[] { company.ZipCode, company.City }).ToString(); + var country = JoinValues(new string[] { company.CountryName, company.StateName }, ", ").ToString(); + } + @JoinValues(new string[] { company.CompanyName, company.Street, company.Street2, city, country }, " · ") + +
        +
        + @Html.Raw(shipment.FormattedShippingAddress) +
        +
        + + @{ Html.RenderWidget("shipmentdetails_orderinfo_top"); } + +
        +
        @Html.SmartLabelFor(model => model.AmountToRefund) - @Html.EditorFor(model => model.AmountToRefund)     - @string.Format(@T("Admin.Orders.Fields.PartialRefund.AmountToRefund.Max").Text, Model.MaxAmountToRefundFormatted, "") + @Html.EditorFor(model => model.AmountToRefund) +
        + @string.Format(@T("Admin.Orders.Fields.PartialRefund.AmountToRefund.Max").Text, Model.MaxAmountToRefundFormatted, "") +
        -   -
        + + + + + + + + + + + + + @if (!String.IsNullOrEmpty(shipment.TrackingNumber)) { -
        @address.Company
        - } -
        @JoinValues(new string[] { address.Salutation, address.Title, address.FirstName, address.LastName })
        - @if (address.Address1.HasValue()) - { -
        @address.Address1
        - } - @if (address.Address2.HasValue()) - { -
        @address.Address2
        - } - @if (address.ZipPostalCode.HasValue() || address.City.HasValue()) - { -
        @JoinValues(new string[] { address.ZipPostalCode, address.City })
        - } - @if (address.Country != null) - { - if (address.StateProvince != null) + + + + + } + + + + +
        @T("PDFPackagingSlip.Shipment").Text.FormatWith(String.Empty):@shipment.Id
        @T("PDFPackagingSlip.Order").Text.FormatWith(String.Empty):@shipment.OrderId
        @T("PDFPackagingSlip.DeliveryDate"):@shipment.DeliveryDate
        @T("PDFPackagingSlip.TrackingNumber"):@shipment.TrackingNumber
        @T("PDFPackagingSlip.ShippingMethod"):@shipment.ShippingMethod
        + + @{ Html.RenderWidget("shipmentdetails_orderinfotable_after"); } + +
        + @{ Html.RenderWidget("shipmentdetails_orderinfo_bottom"); } +
        + + @{ Html.RenderWidget("shipmentdetails_lines_before"); } + +

        @T("PDFPackagingSlip.ProductListHeadline")

        + +
        + + + + + @if (shipment.ShowSku) { -
        @JoinValues(new string[] { address.Country.Name, address.StateProvince.Name }, ", ")
        + } - else + @if (showGtin) { -
        @JoinValues(new string[] { address.Country.Name }, ", ")
        + } - } - - - - @Html.Widget("shipmentdetails_orderinfo_top") - -
        -
        @T("PDFPackagingSlip.ProductName")@T("PDFPackagingSlip.SKU")@T("PDFPackagingSlip.Gtin")
        - - - - - - - - - - - - - @if (!String.IsNullOrEmpty(shipment.TrackingNumber)) - { - - - - - } - - - - -
        @T("PDFPackagingSlip.Shipment").Text.FormatWith(String.Empty):@shipment.Id
        @T("PDFPackagingSlip.Order").Text.FormatWith(String.Empty):@shipment.OrderId
        @T("PDFPackagingSlip.DeliveryDate"):@shipment.DeliveryDate
        @T("PDFPackagingSlip.TrackingNumber"):@shipment.TrackingNumber
        @T("PDFPackagingSlip.ShippingMethod"):@shipment.ShippingMethod
        - - @Html.Widget("shipmentdetails_orderinfotable_after") -
        - @Html.Widget("shipmentdetails_orderinfo_bottom") -
        - - @Html.Widget("shipmentdetails_lines_before") -

        @T("PDFPackagingSlip.ProductListHeadline")

        - - - - - - - - - - - - @for (int i = 0; i < shipment.Items.Count; i++) - { - var item = shipment.Items[i]; - @ShipmentLine(item) - } - -
        @T("PDFPackagingSlip.ProductName")@T("PDFPackagingSlip.SKU")@T("PDFPackagingSlip.QTY")@T("PDFPackagingSlip.Weight")
        - - - } + @T("PDFPackagingSlip.QTY") + @T("PDFPackagingSlip.Weight") + + + @foreach (var shipmentItem in shipment.Items) + { + + @ShipmentLine(shipmentItem, showGtin, shipment.ShowSku) + @if (shipmentItem.ProductType == ProductType.BundledProduct) + { + @BundleItems(shipmentItem, showGtin, shipment.ShowSku) + } + + } + + + + } -@helper ShipmentLine(ShipmentModel.ShipmentItemModel item) +@helper ShipmentLine(ShipmentModel.ShipmentItemModel item, bool showGtin, bool showSku) +{ + + + @item.ProductName + + @if (item.AttributeInfo.HasValue()) + { +
        + @Html.Raw(item.AttributeInfo) +
        + } + + @if (showSku) + { + + @item.Sku + + } + @if (showGtin) + { + + @item.Gtin + + } + + @item.QuantityInThisShipment + + + @item.ItemWeight + + +} + +@helper BundleItems(ShipmentModel.ShipmentItemModel parentItem, bool showGtin, bool showSku) { - - - @item.ProductName - - @if (item.AttributeInfo.HasValue()) - { -
        - @Html.Raw(item.AttributeInfo) -
        - } - - - - - @item.Sku - - - @item.QuantityInThisShipment - - @* - @item.ItemDimensions - *@ - - @item.ItemWeight - - + if (parentItem.BundleItems == null) + { + return; + } + + foreach (var item in parentItem.BundleItems.OrderBy(x => x.DisplayOrder)) + { + + +
        +
        + @item.ProductName +
        + @if (item.AttributeInfo.HasValue()) + { +
        + @Html.Raw(item.AttributeInfo) +
        + } +
        + + @if (showSku) + { +   + } + @if (showGtin) + { +   + } + + @if (parentItem.BundlePerItemShoppingCart) + { + @item.Quantity + } + else + { +   + } + +   + + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml index 800e47eee5..35af7ea85a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml @@ -1,7 +1,5 @@ @model ShipmentModel @using Telerik.Web.Mvc.UI; -@using SmartStore.Core.Domain.Tax; -@using SmartStore.Core.Domain.Catalog; @{ ViewBag.Title = T("Admin.Orders.Shipments.ViewDetails").Text; } @@ -14,12 +12,15 @@
        @if (Model.DisplayPdfPackagingSlip) { - + - @T("Admin.Orders.Shipments.PrintPackagingSlip") + @T("Admin.Orders.Shipments.PrintPackagingSlip") } - +
        @@ -31,11 +32,15 @@ @Html.SmartLabelFor(model => model.TrackingNumber) - @Html.EditorFor(model => model.TrackingNumber) - - +
        + @Html.EditorFor(model => model.TrackingNumber) + + + +
        @@ -43,7 +48,9 @@ @Html.SmartLabelFor(model => model.TotalWeight) - @Model.TotalWeight +
        + @Model.TotalWeight +
        @@ -51,14 +58,16 @@ @Html.SmartLabelFor(model => model.ShippedDate) - @Model.ShippedDate - @if (Model.CanShip) - { -   - - } +
        + @Model.ShippedDate + @if (Model.CanShip) + { + + } +
        @@ -66,68 +75,64 @@ @Html.SmartLabelFor(model => model.DeliveryDate) - @Model.DeliveryDate - @if (Model.CanDeliver) - { -   - - } +
        + @Model.DeliveryDate + @if (Model.CanDeliver) + { + + } +
        -

        @T("Admin.Orders.Shipments.Products")

        +
        @T("Admin.Orders.Shipments.Products")
        - - - + + + + } + +
        +
        + + + + + + + + + + @foreach (var item in Model.Items) + { + + - -
        + @T("Admin.Orders.Shipments.Products.ProductName") + + @T("Admin.Orders.Shipments.Products.SKU") + + @T("Admin.Orders.Shipments.Products.QtyShipped") +
        +
        + @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) - - - - - - - - - - @foreach (var item in Model.Items) - { - - - - - - } - -
        - @T("Admin.Orders.Shipments.Products.ProductName") - - @T("Admin.Orders.Shipments.Products.SKU") - - @T("Admin.Orders.Shipments.Products.QtyShipped") -
        -
        - @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) - - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
        - @Html.Raw(item.AttributeInfo) - } -
        -
        -
        - @item.Sku -
        -
        - @item.QuantityInThisShipment -
        - -
        + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
        + @Html.Raw(item.AttributeInfo) + } +
        +
        +
        + @item.Sku +
        +
        + @item.QuantityInThisShipment +
        + } @Html.DeleteConfirmation("DeleteShipment", "shipment-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml index 967a96ac86..a340893b3c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentList.cshtml @@ -1,11 +1,7 @@ @model ShipmentListModel - @using Telerik.Web.Mvc.UI - @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title ViewBag.Title = T("Admin.Orders.Shipments.List").Text; } @@ -19,92 +15,79 @@
        @if (Model.DisplayPdfPackagingSlip) { - -  @T("Admin.Orders.Shipments.PrintPackagingSlip.All") + + + @T("Admin.Orders.Shipments.PrintPackagingSlip.All") - }
        - - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.StartDate) - - @Html.EditorFor(model => model.StartDate) -
        - @Html.SmartLabelFor(model => model.EndDate) - - @Html.EditorFor(model => Model.EndDate) -
        - @Html.SmartLabelFor(model => model.TrackingNumber) - - @Html.EditorFor(model => Model.TrackingNumber) -
        -   - - -
        - -

        - - - - - -
        - @(Html.Telerik().Grid() - .Name("shipments-grid") - .ClientEvents(events => events - .OnDataBinding("onDataBinding") - .OnDataBound("onDataBound")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id); - columns.Bound(x => x.OrderId) - .Width(80) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) - .ClientTemplate("\"><#= OrderId #>"); - columns.Bound(x => x.TrackingNumber); - columns.Bound(x => x.TotalWeight); - columns.Bound(x => x.ShippedDate); - columns.Bound(x => x.DeliveryDate); - columns.Bound(x => x.Id) - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "ShipmentDetails", "Order", new { id = x.Id }, new { })) - .ClientTemplate("\">" + T("Admin.Common.View").Text + "") - .Width(50) - .Centered() - .HeaderTemplate("{0}".FormatWith(T("Admin.Common.View").Text)); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("ShipmentListSelect", "Order")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        + +
        +
        + @Html.SmartLabelFor(model => model.StartDate) + @Html.EditorFor(model => model.StartDate) +
        +
        + @Html.SmartLabelFor(model => model.EndDate) + @Html.EditorFor(model => model.EndDate) +
        + +
        + @Html.SmartLabelFor(model => model.TrackingNumber) + @Html.EditorFor(model => model.TrackingNumber) +
        + +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        +
        + + +
        + @(Html.Telerik().Grid() + .Name("shipments-grid") + .ClientEvents(events => events + .OnDataBinding("onDataBinding") + .OnDataBound("onDataBound")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id); + columns.Bound(x => x.OrderId) + .Width(80) + .Centered() + .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) + .ClientTemplate("\"><#= OrderId #>"); + columns.Bound(x => x.TrackingNumber); + columns.Bound(x => x.TotalWeight); + columns.Bound(x => x.ShippedDate); + columns.Bound(x => x.DeliveryDate); + columns.Bound(x => x.Id) + .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "ShipmentDetails", "Order", new { id = x.Id }, new { @class = "t-button" })) + .ClientTemplate("\" class=\"t-button\">" + T("Admin.Common.View").Text + "") + .Width(50) + .Centered() + .Title(String.Empty); + }) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("ShipmentListSelect", "Order")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        + - } @Html.HiddenFor(model => model.OrderId) @Html.HiddenFor(model => model.OrderItemId) @Html.EditorFor(model => model.LicenseDownloadId) - - if (Model.LicenseDownloadId > 0) - { - + + + + if (Model.LicenseDownloadId > 0) + { + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_AutoUpdateOrderItem.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_AutoUpdateOrderItem.cshtml index 77dab4ffd5..e502aa3d42 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/_AutoUpdateOrderItem.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_AutoUpdateOrderItem.cshtml @@ -1,65 +1,68 @@ @model AutoUpdateOrderItemModel - + @if (Model.ShowUpdateRewardPoints) + { + + + @Html.SmartLabelFor(model => model.UpdateRewardPoints) + + + @Html.EditorFor(model => model.UpdateRewardPoints) + @Html.ValidationMessageFor(model => model.UpdateRewardPoints) + + + } + @if (Model.ShowUpdateTotals) + { + + + @Html.SmartLabelFor(model => model.UpdateTotals) + + + @Html.EditorFor(model => model.UpdateTotals) + @Html.ValidationMessageFor(model => model.UpdateTotals) + + + } + + + + ) + .FooterContent( + @ + + + ) + .Render(); + } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_CreateOrUpdateAddress.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_CreateOrUpdateAddress.cshtml deleted file mode 100644 index 02051f2805..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/_CreateOrUpdateAddress.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@model OrderAddressModel - -@Html.EditorFor(model => model.Address, "Address") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml new file mode 100644 index 0000000000..2e47d7e3d5 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.BillingAndShipment.cshtml @@ -0,0 +1,116 @@ +@model OrderModel + +
        +
        + +
        +
        +
        @T("Order.BillingAddress")
        + @RenderAddress(Model.BillingAddress) +
        + +
        + +
        + @if (Model.IsShippable && Model.ShippingAddress != null) + { +
        +
        @T("Order.ShippingAddress")
        + @RenderAddress(Model.ShippingAddress) +
        + + } + else + { +
        + @T("Admin.Orders.ShippingInfo.NotRequired") +
        + } +
        + +
        + @if (Model.IsShippable) + { +
        @T("Admin.Orders.Fields.ShippingMethod")
        +

        @Model.ShippingMethod

        + +
        @T("Admin.Orders.Fields.ShippingStatus")
        +

        @Model.ShippingStatus

        + } +
        +
        +
        + +@if (Model.IsShippable && Model.ShippingAddress != null) +{ +
        @T("Admin.Orders.Shipments")
        + +
        + @(Html.Telerik().Grid().Name("shipments-grid") + .DataBinding(binding => + { + binding.Ajax().Select("ShipmentsSelect", "Order", new { orderId = Model.Id }); + }) + .Columns(columns => + { + columns.Bound(x => x.Id) + .Template(x => Html.ActionLink(x.Id.ToString(), "ShipmentDetails", "Order", new { id = x.Id }, new { })) + .ClientTemplate("\"><#= Id #>"); + columns.Bound(x => x.TrackingNumber); + columns.Bound(x => x.TotalWeight); + columns.Bound(x => x.ShippedDate); + columns.Bound(x => x.DeliveryDate); + }) + .ToolBar(commands => commands.Template(GridCommands)) + .EnableCustomBinding(true)) +
        +} + +@helper GridCommands(Grid grid) +{ + if (Model.CanAddNewShipments) + { + + } +} + +@helper RenderAddress(AddressModel address) +{ +
        + @Html.Raw(address.FormattedAddress) +
        + + + if (address.PhoneEnabled && address.PhoneNumber.HasValue()) + { +
        + @T("Order.Phone"): @address.PhoneNumber +
        + } + if (address.FaxEnabled && address.FaxNumber.HasValue()) + { +
        + @T("Order.Fax"): @address.FaxNumber +
        + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml new file mode 100644 index 0000000000..28aee8af41 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Info.cshtml @@ -0,0 +1,940 @@ +@model OrderModel +@using SmartStore.Core.Domain.Tax; + + + +@Html.Widget("order_edit_top") + + + + + + + + + + + + + + + + + + + @if (Model.AffiliateId != 0) + { + + + + + } + + + + + + + + + + + + @if (Model.RecurringPaymentId > 0) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.VatNumber)) + { + + + + + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { + + + + + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { + + + + + } + @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountInclTax)) + { + + + + + } + @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountExclTax)) + { + + + + + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { + + + + + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { + + + + + } + @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeInclTax)) + { + + + + + } + @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeExclTax)) + { + + + + + } + @if (Model.DisplayTaxRates) + { + foreach (var tr in Model.TaxRates) + { + + + + + } + } + @if (Model.DisplayTax) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.OrderTotalDiscount)) + { + + + + + } + @foreach (var gc in Model.GiftCards) + { + + + + + } + @if (Model.RedeemedRewardPoints > 0) + { + + + + + } + @if (Model.CreditBalance.HasValue()) + { + + + + + } + @if (Model.OrderTotalRounding.HasValue()) + { + + + + + } + + + + + @if (Model.RefundedAmount.HasValue()) + { + + + + + } + + + + + + + + + + + + + @if (Model.AllowStoringCreditCardNumber) + { + + + + + + + + + } + @if (Model.AllowStoringCreditCardNumber || !String.IsNullOrEmpty(Model.CardNumber)) + { + + + + + } + @if (Model.AllowStoringCreditCardNumber) + { + + + + + + + + + + + + + + + + + + + } + + @if (Model.AllowStoringDirectDebit) + { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + + @if (Model.DisplayCompletePaymentNote) + { + + + + } + @if (Model.DisplayPurchaseOrderNumber) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.AuthorizationTransactionId)) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.AuthorizationTransactionResult)) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.CaptureTransactionId)) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.CaptureTransactionResult)) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.SubscriptionTransactionId)) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.OrderStatus) + +
        + @Model.OrderStatus +
        + @if (Model.CanCancelOrder || Model.CanCompleteOrder) + { +
        + @if (Model.CanCancelOrder) + { + + } + + @if (Model.CanCompleteOrder) + { + + } +
        + } +
        + @Html.SmartLabelFor(model => model.OrderNumber) + +
        + @Model.OrderNumber +
        +
        + @Html.SmartLabelFor(model => model.OrderGuid) + +
        + @Model.OrderGuid +
        +
        + @Html.SmartLabelFor(model => model.StoreName) + +
        + @Model.StoreName +
        +
        + @Html.SmartLabelFor(model => model.AffiliateId) + + +
        +
        +
        + @Html.SmartLabelFor(model => model.CustomerId) + + +
        + @Html.SmartLabelFor(model => model.CustomerIp) + +
        + @Model.CustomerIp +
        +
        + @Html.SmartLabelFor(model => model.RecurringPaymentId) + + +
        + @Html.SmartLabelFor(model => model.VatNumber) + +
        + @Model.VatNumber +
        +
        + @Html.SmartLabelFor(model => model.OrderSubtotalInclTax) + +
        + @Model.OrderSubtotalInclTax +
        +
        + @Html.SmartLabelFor(model => model.OrderSubtotalExclTax) + +
        + @Model.OrderSubtotalExclTax +
        +
        + @Html.SmartLabelFor(model => model.OrderSubTotalDiscountInclTax) + +
        + @Model.OrderSubTotalDiscountInclTax +
        +
        + @Html.SmartLabelFor(model => model.OrderSubTotalDiscountExclTax) + +
        + @Model.OrderSubTotalDiscountExclTax +
        +
        + @Html.SmartLabelFor(model => model.OrderShippingInclTax) + +
        + @Model.OrderShippingInclTax +
        +
        + @Html.SmartLabelFor(model => model.OrderShippingExclTax) + +
        + @Model.OrderShippingExclTax +
        +
        + @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeInclTax) + +
        + @Model.PaymentMethodAdditionalFeeInclTax +
        +
        + @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeExclTax) + +
        + @Model.PaymentMethodAdditionalFeeExclTax +
        +
        + @Html.SmartLabelFor(model => model.Tax) + +
        + @tr.Rate% - @tr.Value +
        +
        + @Html.SmartLabelFor(model => model.Tax) + +
        + @Model.Tax +
        +
        + @Html.SmartLabelFor(model => model.OrderTotalDiscount) + +
        + @Model.OrderTotalDiscount +
        +
        + @Html.SmartLabelFor(model => model.GiftCards[0].CouponCode) (@(gc.CouponCode)) + +
        + @gc.Amount +
        +
        + @Html.SmartLabelFor(model => model.RedeemedRewardPoints) + +
        + @Model.RedeemedRewardPoints @T("Admin.Orders.Fields.RedeemedRewardPoints.Points") + / + @Model.RedeemedRewardPointsAmount +
        +
        + @Html.SmartLabelFor(model => model.CreditBalance) + +
        + @Model.CreditBalance +
        +
        + @Html.SmartLabelFor(model => model.OrderTotalRounding) + +
        + @Model.OrderTotalRounding +
        +
        + @Html.SmartLabelFor(model => model.OrderTotal) + +
        + @Model.OrderTotal +
        +
        + @Html.SmartLabelFor(model => model.RefundedAmount) + +
        + @Model.RefundedAmount +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.OrderSubtotalInclTaxValue) + + @Html.EditorFor(model => model.OrderSubtotalInclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.InclTax").Text }) +
        + @Html.EditorFor(model => model.OrderSubtotalExclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.ExclTax").Text }) +
        +
        + @Html.SmartLabelFor(model => model.OrderSubTotalDiscountInclTaxValue) + + @Html.EditorFor(model => model.OrderSubTotalDiscountInclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.InclTax").Text }) +
        + @Html.EditorFor(model => model.OrderSubTotalDiscountExclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.ExclTax").Text }) +
        +
        + @Html.SmartLabelFor(model => model.OrderShippingInclTaxValue) + + @Html.EditorFor(model => model.OrderShippingInclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.InclTax").Text }) +
        + @Html.EditorFor(model => model.OrderShippingExclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.ExclTax").Text }) +
        +
        + @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) + + @Html.EditorFor(model => model.PaymentMethodAdditionalFeeInclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.InclTax").Text }) +
        + @Html.EditorFor(model => model.PaymentMethodAdditionalFeeExclTaxValue, new { postfix = T("Admin.Orders.Fields.Edit.ExclTax").Text }) +
        +
        + @Html.SmartLabelFor(model => model.TaxRatesValue) + + @Html.EditorFor(model => model.TaxRatesValue) +
        + @Html.SmartLabelFor(model => model.TaxValue) + + @Html.EditorFor(model => model.TaxValue) +
        + @Html.SmartLabelFor(model => model.OrderTotalDiscountValue) + + @Html.EditorFor(model => model.OrderTotalDiscountValue) +
        + @Html.SmartLabelFor(model => model.CreditBalanceValue) + + @Html.EditorFor(model => model.CreditBalanceValue) +
        + @Html.SmartLabelFor(model => model.OrderTotalRoundingValue) + + @Html.EditorFor(model => model.OrderTotalRoundingValue) +
        + @Html.SmartLabelFor(model => model.OrderTotalValue) + + @Html.EditorFor(model => model.OrderTotalValue) +
        +
        +
        + + + + + @{ Html.RenderWidget("admin_order_total_edit_buttons_after"); } +
        +
        +
        + @Html.SmartLabelFor(model => model.CardType) + +
        + @Model.CardType +
        + @Html.EditorFor(model => model.CardType) +
        + @Html.SmartLabelFor(model => model.CardName) + +
        + @Model.CardName +
        + @Html.EditorFor(model => model.CardName) +
        + @Html.SmartLabelFor(model => model.CardNumber) + +
        + @Model.CardNumber +
        + @Html.EditorFor(model => model.CardNumber) +
        + @Html.SmartLabelFor(model => model.CardCvv2) + +
        + @Model.CardCvv2 +
        + @Html.EditorFor(model => model.CardCvv2) +
        + @Html.SmartLabelFor(model => model.CardExpirationMonth) + +
        + @Model.CardExpirationMonth +
        + @Html.EditorFor(model => model.CardExpirationMonth) +
        + @Html.SmartLabelFor(model => model.CardExpirationYear) + +
        + @Model.CardExpirationYear +
        + @Html.EditorFor(model => model.CardExpirationYear) +
        + + + +
        +
        +
        + @Html.SmartLabelFor(model => model.DirectDebitAccountHolder) + +
        + @Model.DirectDebitAccountHolder +
        + @Html.EditorFor(model => model.DirectDebitAccountHolder) +
        + @Html.SmartLabelFor(model => model.DirectDebitAccountNumber) + +
        + @Model.DirectDebitAccountNumber +
        + @Html.EditorFor(model => model.DirectDebitAccountNumber) +
        + @Html.SmartLabelFor(model => model.DirectDebitBankCode) + +
        + @Model.DirectDebitBankCode +
        + @Html.EditorFor(model => model.DirectDebitBankCode) +
        + @Html.SmartLabelFor(model => model.DirectDebitBankName) + +
        + @Model.DirectDebitBankName +
        + @Html.EditorFor(model => model.DirectDebitBankName) +
        + @Html.SmartLabelFor(model => model.DirectDebitBIC) + +
        + @Model.DirectDebitBIC +
        + @Html.EditorFor(model => model.DirectDebitBIC) +
        + @Html.SmartLabelFor(model => model.DirectDebitCountry) + +
        + @Model.DirectDebitCountry +
        + @Html.EditorFor(model => model.DirectDebitCountry) +
        + @Html.SmartLabelFor(model => model.DirectDebitIban) + +
        + @Model.DirectDebitIban +
        + @Html.EditorFor(model => model.DirectDebitIban) +
        + + + +
        +
        +
        +
        + @Html.Raw(T("Order.CompletePayment.AdminNote", Url.Action("Details", "Order", new { id = Model.Id, area = "" }))) +
        +
        + @Html.SmartLabelFor(model => model.PurchaseOrderNumber) + +
        + @Model.PurchaseOrderNumber +
        +
        + @Html.SmartLabelFor(model => model.AuthorizationTransactionId) + +
        + @Model.AuthorizationTransactionId +
        +
        + @Html.SmartLabelFor(model => model.AuthorizationTransactionResult) + +
        + @Model.AuthorizationTransactionResult +
        +
        + @Html.SmartLabelFor(model => model.CaptureTransactionId) + +
        + @Model.CaptureTransactionId +
        +
        + @Html.SmartLabelFor(model => model.CaptureTransactionResult) + +
        + @Model.CaptureTransactionResult +
        +
        + @Html.SmartLabelFor(model => model.SubscriptionTransactionId) + +
        + @Model.SubscriptionTransactionId +
        +
        + @Html.SmartLabelFor(model => model.PaymentMethod) + +
        + @Model.PaymentMethod + @if (Model.PaymentMethodSystemName.HasValue()) + { + (@Model.PaymentMethodSystemName) + } +
        +
        + @Html.SmartLabelFor(model => model.PaymentStatus) + + @Model.PaymentStatus + + @if (Model.CanMarkOrderAsPaid) + { + + } + @if (Model.CanCapture) + { + + } + @if (Model.CanRefund) + { + + } + @if (Model.CanRefundOffline) + { + + } + @if (Model.CanPartiallyRefund) + { + + } + @if (Model.CanPartiallyRefundOffline) + { + + } + @if (Model.CanVoid) + { + + } + @if (Model.CanVoidOffline) + { + + } + + @{ Html.RenderWidget("admin_order_edit_payment_buttons_after"); } +
        + @Html.SmartLabelFor(model => model.CreatedOn) + +
        + @Html.DisplayFor(model => model.CreatedOn) +
        +
        + @Html.SmartLabelFor(model => model.UpdatedOn) + +
        + @Html.DisplayFor(model => model.UpdatedOn) +
        +
        +
        +
        + @Html.SmartLabelFor(model => model.AcceptThirdPartyEmailHandOver) + +
        + @(Model.AcceptThirdPartyEmailHandOver ? T("Common.Yes") : T("Common.No")) +
        +
        + @if (Model.CustomerComment.HasValue()) + { +

        @T("Admin.Order.CustomerComment.Heading")

        +
        + @Model.CustomerComment +
        + } +
        \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.OrderNotes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.OrderNotes.cshtml new file mode 100644 index 0000000000..8dfb77aa17 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.OrderNotes.cshtml @@ -0,0 +1,107 @@ +@model OrderModel + +
        + @(Html.Telerik().Grid().Name("ordernotes-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id).RouteKey("orderNoteId"); + keys.Add(x => x.OrderId).RouteKey("orderId"); + }) + .DataBinding(binding => + { + binding.Ajax().Select("OrderNotesSelect", "Order", new { orderId = Model.Id }) + .Delete("OrderNoteDelete", "Order"); + }) + .Columns(columns => + { + columns.Bound(x => x.CreatedOn).Width(140); + columns.Bound(x => x.Note).Encoded(false); + columns.Bound(x => x.DisplayToCustomer) + .Template(item => @Html.SymbolForBool(item.DisplayToCustomer)) + .ClientTemplate(@Html.SymbolForBool("DisplayToCustomer")) + .Centered() + .Width(140); + columns.Command(commands => + { + commands.Delete().Localize(T); + }).Width(140).HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Template(OrderNotesGridCommands)) + .EnableCustomBinding(true)) +
        + +@{Html.SmartStore().Window() + .Name("addrecord-window") + .Size(WindowSize.Large) + .Title(T("Admin.Orders.OrderNotes.AddButton")) + .Content( + @ +
        + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.AddOrderNoteMessage) + + @Html.TextAreaFor(model => model.AddOrderNoteMessage, new { style = "height: 120px;" }) + @Html.ValidationMessageFor(model => model.AddOrderNoteMessage) +
        + @Html.SmartLabelFor(model => model.AddOrderNoteDisplayToCustomer) + + @Html.EditorFor(model => model.AddOrderNoteDisplayToCustomer) + @Html.ValidationMessageFor(model => model.AddOrderNoteDisplayToCustomer) +
        +
        +
        ) + .FooterContent( + @ + + + ) + .Render(); +} + +@helper OrderNotesGridCommands(Grid grid) +{ + + + @T("Admin.Orders.OrderNotes.AddButton") + +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Products.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Products.cshtml new file mode 100644 index 0000000000..a4a06be61b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_Edit.Products.cshtml @@ -0,0 +1,429 @@ +@model OrderModel +@using SmartStore.Core.Domain.Tax; +@using SmartStore.Core.Domain.Catalog; + + + + + +@if (Model.AutoUpdateOrderItemInfo.HasValue()) +{ +
        + + @Html.Raw(Model.AutoUpdateOrderItemInfo) +
        +} + +
        +
        + +
        +
        + + + + + @if (Model.HasDownloadableProducts) + { + + } + + + + + + + + + @foreach (var item in Model.Items) + { + + + + @if (Model.HasDownloadableProducts) + { + + } + + + + + + + + + } + +
        + @T("Admin.Orders.Products.ProductName") + + @T("Admin.Orders.Products.Download") + + @T("Admin.Orders.Products.Price") + + @T("Admin.Orders.Products.Quantity") + + @T("Admin.Orders.Products.Discount") + + @T("Admin.Orders.Products.Total") +
        +
        +
        + @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) +
        + + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { + @Html.Raw(item.AttributeInfo) + } + @if (!String.IsNullOrEmpty(item.RecurringInfo)) + { +
        + @Html.Raw(item.RecurringInfo) + } + @if (!String.IsNullOrEmpty(item.Sku)) + { +
        + @T("Admin.Orders.Products.SKU"): + @item.Sku + } + @if (item.ProductType == ProductType.BundledProduct) + { +
        + @BundleItemsInfo(item) + } + @if (item.ReturnRequests.Count > 0) + { +
        +
        + for (int i = 0; i < item.ReturnRequests.Count; ++i) + { + var returnRequest = item.ReturnRequests[i]; + + @returnRequest.StatusString + + + @T("Admin.Orders.Products.ReturnRequest") @returnRequest.Id + + + , + + @T("Admin.ReturnRequests.Fields.Quantity") @returnRequest.Quantity + if (i != item.ReturnRequests.Count - 1) + { +
        + } + } + } + @if (item.PurchasedGiftCardIds.Count > 0) + { +
        +
        + @T("Admin.Orders.Products.GiftCards"): + + for (int i = 0; i < item.PurchasedGiftCardIds.Count; i++) + { + @item.PurchasedGiftCardIds[i] + if (i != item.PurchasedGiftCardIds.Count - 1) + { + , + } + } + } +
        +
        + @if (item.IsDownload) + { +
        + @string.Format(T("Admin.Orders.Products.Download.DownloadCount").Text, item.DownloadCount) + +
        + + if (item.DownloadActivationType == DownloadActivationType.Manually) + { +
        + +
        + } + +
        + @T("Admin.Orders.Products.License") + + @if (item.LicenseDownloadId.HasValue && item.LicenseDownloadId.Value > 0) + { + + @T("Admin.Orders.Products.License.DownloadLicense") + + } + + +
        + } +
        + +
        + @if (Model.AllowCustomersToSelectTaxDisplayType) + { + @item.UnitPriceInclTax +
        + @item.UnitPriceExclTax + } + else + { + @(Model.TaxDisplayType == TaxDisplayType.ExcludingTax ? item.UnitPriceExclTax : item.UnitPriceInclTax) + } +
        + +
        +
        +
        +
        + @T("Admin.Orders.Products.Edit.InclTax") +
        +
        + +
        +
        +
        +
        +
        +
        + @T("Admin.Orders.Products.Edit.ExclTax") +
        +
        + +
        +
        +
        +
        +
        +
        + @T("Admin.Orders.Products.AddNew.TaxRate") +
        +
        + +
        +
        +
        +
        +
        +
        + @item.Quantity +
        + +
        +
        + +
        +
        +
        + +
        + @if (Model.AllowCustomersToSelectTaxDisplayType) + { + @item.DiscountInclTax +
        + @item.DiscountExclTax + } + else + { + @(Model.TaxDisplayType == TaxDisplayType.ExcludingTax ? item.DiscountExclTax : item.DiscountInclTax) + } +
        + +
        +
        +
        +
        + @T("Admin.Orders.Products.Edit.InclTax") +
        +
        + +
        +
        +
        +
        +
        +
        + @T("Admin.Orders.Products.Edit.ExclTax") +
        +
        + +
        +
        +
        +
        +
        + +
        + @if (Model.AllowCustomersToSelectTaxDisplayType) + { + @item.SubTotalInclTax +
        + @item.SubTotalExclTax + } + else + { + @(Model.TaxDisplayType == TaxDisplayType.ExcludingTax ? item.SubTotalExclTax : item.SubTotalInclTax) + } +
        + +
        + +
        +
        +
        + @T("Admin.Orders.Products.Edit.InclTax") +
        +
        + +
        +
        +
        + +
        +
        +
        + @T("Admin.Orders.Products.Edit.ExclTax") +
        +
        + +
        +
        +
        +
        +
        + + @if (item.IsReturnRequestPossible) + { + + } + + + + + + + + +
        +
        +
        + +@if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) +{ +
        + @Html.Raw(Model.CheckoutAttributeInfo) +
        +} + +@helper BundleItemsInfo(OrderModel.OrderItemModel parentItem) +{ + if (parentItem.BundleItems != null) + { +
        + @foreach (var item in parentItem.BundleItems.OrderBy(x => x.DisplayOrder)) + { +
        +
        + @Html.ActionLink(item.ProductName, "Edit", "Product", new { id = item.ProductId }, new { }) + @if (item.Quantity > 1) + { + + × @item.Quantity + + } +
        + @if (!String.IsNullOrWhiteSpace(item.PriceWithDiscount)) + { +
        + @Html.Raw(item.PriceWithDiscount) +
        + } + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
        + @Html.Raw(item.AttributeInfo) +
        + } +
        + } +
        + } +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddAttributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddAttributes.cshtml index 4fb125214c..77602f54cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddAttributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddAttributes.cshtml @@ -1,92 +1,76 @@ -@model IList -@using SmartStore.Web.Framework; +@using SmartStore.Web.Framework; @using SmartStore.Core.Domain.Catalog; -@if (Model.Count > 0) +@model OrderModel.AddOrderProductModel.ProductDetailsModel + +@if (Model.ProductVariantAttributes.Count > 0) { -
        - @foreach (var attribute in Model) - { - string controlId = string.Format("product_attribute_{0}_{1}", attribute.ProductAttributeId, attribute.Id); -
        - @if (attribute.IsRequired) - { - * - } - @if (!string.IsNullOrEmpty(attribute.TextPrompt)) - { - @attribute.TextPrompt - } - else - { - @attribute.Name - } - @if (attribute.AttributeControlType == AttributeControlType.TextBox || - attribute.AttributeControlType == AttributeControlType.FileUpload) - { -     - } - else - { -
        - } - @switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - { - - } - break; - case AttributeControlType.RadioList: - case AttributeControlType.ColorSquares: - { - foreach (var pvaValue in attribute.Values) - { - - - } - } - break; - case AttributeControlType.Checkboxes: - { - foreach (var pvaValue in attribute.Values) - { - - - } - } - break; - case AttributeControlType.TextBox: - { - - } - break; - case AttributeControlType.MultilineTextbox: - { - - } - break; - case AttributeControlType.Datepicker: - { - @Html.DatePickerDropDowns(controlId + "_day", controlId + "_month", controlId + "_year", DateTime.Now.Year, DateTime.Now.Year + 1) - } - break; - case AttributeControlType.FileUpload: - { - - } - break; - } -
        - } -
        +
        + @foreach (var attribute in Model.ProductVariantAttributes) + { + var controlId = attribute.GetControlId(Model.ProductId, 0); +
        +
        +
        + +
        +
        +
        + @switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + { + + } + break; + case AttributeControlType.RadioList: + case AttributeControlType.Boxes: + case AttributeControlType.Checkboxes: + { + foreach (var pvaValue in attribute.Values) + { +
        + + +
        + } + } + break; + case AttributeControlType.TextBox: + { + + } + break; + case AttributeControlType.MultilineTextbox: + { + + } + break; + case AttributeControlType.Datepicker: + { + @Html.DatePickerDropDowns(controlId + "-day", controlId + "-month", controlId + "-year", DateTime.Now.Year, DateTime.Now.Year + 1) + } + break; + case AttributeControlType.FileUpload: + { + + } + break; + } +
        +
        + } +
        } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddGiftCardInfo.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddGiftCardInfo.cshtml index 0a5d1e6d40..a4efcd840f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddGiftCardInfo.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/_ProductAddGiftCardInfo.cshtml @@ -1,79 +1,63 @@ -@model OrderModel.AddOrderProductModel.GiftCardModel +@using SmartStore.Core.Domain.Catalog; +@model OrderModel.AddOrderProductModel.GiftCardModel + @if (Model.IsGiftCard) { - //Actually just a copy of \Presentation\SmartStore.Web\Views\Catalog\_GiftCardInfo.cshtml -

         

        -
        +
        +
        +
        + @Html.SmartLabelFor(model => model.RecipientName) +
        +
        + @Html.EditorFor(model => model.RecipientName) + @Html.ValidationMessageFor(model => model.RecipientName) +
        +
        + + @if (Model.GiftCardType == GiftCardType.Virtual) + { +
        +
        + @Html.SmartLabelFor(model => model.RecipientEmail) +
        +
        + @Html.EditorFor(model => model.RecipientEmail) + @Html.ValidationMessageFor(model => model.RecipientEmail) +
        +
        + } + +
        +
        + @Html.SmartLabelFor(model => model.SenderName) +
        +
        + @Html.EditorFor(model => model.SenderName) + @Html.ValidationMessageFor(model => model.SenderName) +
        +
        + + @if (Model.GiftCardType == GiftCardType.Virtual) + { +
        +
        + @Html.SmartLabelFor(model => model.SenderEmail) +
        +
        + @Html.EditorFor(model => model.SenderEmail) + @Html.ValidationMessageFor(model => model.SenderEmail) +
        +
        + } -
        - @Html.LabelFor(model => model.RecipientName, new { @class = "control-label required", @for = "RecipientName" }) -
        - @Html.EditorFor(model => model.RecipientName, "BlockInput") -
        -
        - - @if (Model.GiftCardType == SmartStore.Core.Domain.Catalog.GiftCardType.Virtual) - { -
        - @Html.LabelFor(model => model.RecipientEmail, new { @class = "control-label required", @for = "RecipientEmail" }) -
        - @Html.EditorFor(model => model.RecipientEmail, "BlockInput") -
        -
        - } - -
        - @Html.LabelFor(model => model.SenderName, new { @class = "control-label required", @for = "SenderName" }) -
        - @Html.EditorFor(model => model.SenderName, "BlockInput") -
        -
        - - @if (Model.GiftCardType == SmartStore.Core.Domain.Catalog.GiftCardType.Virtual) - { -
        - @Html.LabelFor(model => model.SenderEmail, new { @class = "control-label required", @for = "SenderEmail" }) -
        - @Html.EditorFor(model => model.SenderEmail, "BlockInput") -
        -
        - } - -
        - @Html.LabelFor(model => model.Message, new { @class = "control-label", @for = "Message" }) -
        - @Html.TextAreaFor(model => model.Message, new { @class = "message input-block-level" }) -
        -
        - - @*
        -
        @Html.LabelFor(model => model.RecipientName)
        -
        - @Html.TextBoxFor(model => model.RecipientName) -
        - @if (Model.GiftCardType == SmartStore.Core.Domain.Catalog.GiftCardType.Virtual) - { -
        @Html.LabelFor(model => model.RecipientEmail)
        -
        - @Html.TextBoxFor(model => model.RecipientEmail) -
        - } -
        - @Html.LabelFor(model => model.SenderName)
        -
        - @Html.TextBoxFor(model => model.SenderName) -
        - @if (Model.GiftCardType == SmartStore.Core.Domain.Catalog.GiftCardType.Virtual) - { -
        @Html.LabelFor(model => model.SenderEmail)
        -
        - @Html.TextBoxFor(model => model.SenderEmail)
        - } -
        - @Html.LabelFor(model => model.Message)
        -
        - @Html.TextAreaFor(model => model.Message, new { style = "Width: 300px; Height: 100px;" }) -
        -
        *@ -
        -} \ No newline at end of file +
        +
        + @Html.SmartLabelFor(model => model.Message) +
        +
        + @Html.TextAreaFor(model => model.Message) + @Html.ValidationMessageFor(model => model.Message) +
        +
        +
        +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Packaging/UploadPackage.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Packaging/UploadPackage.cshtml index ecb6937ec0..10962d4f98 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Packaging/UploadPackage.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Packaging/UploadPackage.cshtml @@ -1,34 +1,42 @@ - +@model dynamic + @{ string dialogTitle = (string)Model.Title; string dialogInfo = (string)Model.Info; } @{Html.SmartStore().Window() - .Name("uploadpackage-window") - .Title(dialogTitle) - .Content(@ - @using (Html.BeginForm("UploadPackage", "Packaging", FormMethod.Post, new { enctype = "multipart/form-data", @class = "form-horizontal" })) - { + .Name("uploadpackage-window") + .Title(dialogTitle) + .Size(WindowSize.Large) + .Content(@ +
        @dialogInfo
        -
        - -
        - +
        +
        +
        +
        + +
        +
        +
        + +
        - } - - ) - .FooterContent(@ - - @T("Common.Cancel") - ) - .Modal(true) - .Visible(false) - .Render(); + + ) + .FooterContent(@ + + @T("Common.Cancel") + + + ) + .Render(); } +
        -
        +
        @Model.FriendlyName - @ViewBag.Title (@(Model.SystemName)) + @ViewBag.Title (@T("Admin.Common.BackToList"))
        - -@if (Model.ConfigurationRoute != null && Model.ConfigurationRoute.Action.HasValue()) -{ - try +
        + @if (Model.ConfigurationRoute != null && Model.ConfigurationRoute.Action.HasValue()) { - @Html.Action(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + try + { + Html.RenderAction(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + } + catch (Exception exception) + { +
        + @T("Admin.Configuration.Plugins.UnknownError") +
        + +
        @exception.ToString()
        + } } - catch (Exception exception) + else { -
        - @T("Admin.Configuration.Plugins.UnknownError") -
        - -
        @exception.ToString()
        + @T("Admin.Common.ConfigurationNotRequired") } -} -else -{ - @T("Admin.Common.ConfigurationNotRequired") -} +
        + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml index 74edd98572..ae16bd7e9e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/ConfigureProvider.cshtml @@ -4,39 +4,47 @@ ViewBag.Title = T("Admin.Common.Configure").Text + " - " + Model.FriendlyName; } + +
        -
        +
        @Model.FriendlyName - @ViewBag.Title (@(Model.SystemName)) + @ViewBag.Title (@T("Admin.Common.BackToList"))
        @if (Model.PluginDescriptor != null) { }
        -@if (Model.ConfigurationRoute != null && Model.ConfigurationRoute.Action.HasValue()) -{ - try +
        + @if (Model.ConfigurationRoute != null && Model.ConfigurationRoute.Action.HasValue()) { - @Html.Action(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + try + { + Html.RenderAction(Model.ConfigurationRoute.Action, Model.ConfigurationRoute.Controller, Model.ConfigurationRoute.RouteValues); + } + catch (Exception exception) + { +
        + @T("Admin.Configuration.Plugins.UnknownError") +
        + +
        @exception.ToString()
        + } } - catch (Exception exception) + else { -
        - @T("Admin.Configuration.Plugins.UnknownError") -
        - -
        @exception.ToString()
        + @T("Admin.Common.ConfigurationNotRequired") } -} -else -{ - @T("Admin.Common.ConfigurationNotRequired") -} \ No newline at end of file +
        \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml index c6aae8a842..3d0d2ad478 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/EditProviderPopup.cshtml @@ -2,35 +2,26 @@ @model SmartStore.Web.Framework.Plugins.ProviderModel @{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} - -@{ - ViewBag.Title = T("Common.Edit") + ": " + Model.FriendlyName.Truncate(30, "..."); -} - -@if (ViewBag.RefreshPage == true) -{ - - - return; + Layout = "_AdminPopupLayout"; + ViewBag.Title = string.Concat(T("Common.Edit"), " - ", ((ViewBag.Title as string) ?? Model.SystemName).Truncate(30, "…")); } @using (Html.BeginForm()) {
        - @Model.FriendlyName + + @if (Model.IconUrl.HasValue()) + { + @Model.FriendlyName + } + @ViewBag.Title
        -
        @@ -45,6 +36,9 @@ @Html.SmartLabelFor(model => model.Locales[item].FriendlyName) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].FriendlyName) @Html.ValidationMessageFor(model => model.Locales[item].FriendlyName) @@ -58,11 +52,6 @@ @Html.ValidationMessageFor(model => model.Locales[item].Description) - - - @Html.HiddenFor(model => model.Locales[item].LanguageId) - - , @ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml index 5897e38319..5241909eb9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/LicensePlugin.cshtml @@ -1,38 +1,59 @@ -@model LicensePluginModel - +@using SmartStore.Licensing +@model LicensePluginModel @{ Layout = null; } - - @if (Model.InvalidDataStoreId == 0) {
        - @for (int i = 0; i < Model.Licenses.Count; ++i) + @for (int i = 0; i < Model.StoreLicenses.Count; ++i) { + var storeLicense = Model.StoreLicenses[i]; + var keyAttributes = new RouteValueDictionary(new { autocomplete = "off" }); + if (storeLicense.LicenseLabel.LicenseState == LicensingState.Licensed && string.IsNullOrWhiteSpace(storeLicense.LicenseKey) && !storeLicense.LicenseLabel.HideLabel) + { + keyAttributes.Add("disabled", "disabled"); + } + + + + } @@ -44,7 +65,7 @@

        @T("Admin.Configuration.Plugins.LicensingInvalidStoreUrl")

        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml index e8c60696f4..e8a34cc7e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml @@ -1,149 +1,127 @@ -@model LocalPluginsModel -@using SmartStore.Web.Framework.UI; +@using SmartStore.Web.Framework.UI; @using Newtonsoft.Json; -@using SmartStore.Licensing; +@model LocalPluginsModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Plugins").Text; - var allPluginsSelectList = Model.AllPlugins.Select(x => new SelectListItem { Value = x.SystemName, Text = x.FriendlyName }); - var availableStores = JsonConvert.SerializeObject(Model.AvailableStores.Select(x => new { value = x.Id, text = x.Name }).ToArray()); + var allPluginsSelectList = Model.AllPlugins.Select(x => new SelectListItem { Value = x.SystemName, Text = x.FriendlyName }); + var availableStores = JsonConvert.SerializeObject(Model.AvailableStores.Select(x => new { value = x.Id, text = x.Name }).ToArray()); - Html.AddCssFileParts(true, "~/Content/x-editable/bootstrap-editable.css"); - Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); + Html.AddCssFileParts(true, "~/Content/vendors/x-editable/bootstrap-editable.css"); + Html.AppendScriptParts(true, "~/Content/vendors/x-editable/bootstrap-editable.js"); } @helper PluginList(IEnumerable plugins) { foreach (var plugin in plugins) { -
        +
        -
        +
        -
        -
        - - @plugin.FriendlyName - - @if (plugin.Installed) - { - @T("Admin.Configuration.Plugins.Fields.Installed") - } - @if (plugin.IsLicensable) - { - if (plugin.LicenseState == LicensingState.Licensed) - { - @T("Admin.Common.Licensed") - } - else if (plugin.LicenseState == LicensingState.Unlicensed && plugin.TruncatedLicenseKey.HasValue()) - { - @T("Admin.Common.Unlicensed") - } - else - { - - @if (!plugin.RemainingDemoUsageDays.HasValue) +
        +
        +
        +
        +
        @plugin.FriendlyName
        +
        + @if (plugin.Installed) { - @T("Admin.Plugins.LicensingDemoNotStarted") + @T("Admin.Configuration.Plugins.Fields.Installed") } - else if (plugin.RemainingDemoUsageDays <= 0) + @Html.Partial("LicenseLabel", plugin.LicenseLabel) +
        +
        + @if (plugin.Description.HasValue()) + { +
        + @plugin.Description +
        + } +
        + + @if (plugin.Installed) + { + }
        - @if (plugin.Url.HasValue()) + + @T("Admin.Configuration.Plugins.Fields.Version"): + @plugin.Version + + @if (plugin.Author.HasValue()) { - - @T("Common.Website") + @T("Admin.Configuration.Plugins.Fields.Author"): + @plugin.Author } - @if (plugin.Author.HasValue()) + @if (plugin.Url.HasValue()) { - @T("Admin.Configuration.Plugins.Fields.Author"): - @plugin.Author + + @T("Common.Website") } - - @T("Admin.Configuration.Plugins.Fields.Version"): - @plugin.Version - @T("Admin.Configuration.Plugins.Fields.SystemName"): @plugin.SystemName
        - @if (plugin.Description.HasValue()) - { -
        - @plugin.Description -
        - } @if (@Model.AvailableStores.Count > 1) { -
        - @T("Admin.Common.Store.LimitedTo"): +
        + @T("Admin.Common.Store.LimitedTo"):
        } - @if (plugin.Installed) - { -
        -
        - - - @T("Admin.Configuration.Plugins.Resources.Update") - - @if (plugin.ConfigurationUrl.HasValue()) - { - - - @T("Admin.Configuration.Plugins.Fields.Configure") - - } - @if (plugin.LicenseUrl.HasValue()) - { - - } -
        -
        - }
        - @@ -153,51 +131,46 @@ @using (Html.BeginForm("ExecuteTasks", null, FormMethod.Post, new { id = "plugins-form" })) { - @Html.DropDownList("pluginsToInstall", allPluginsSelectList, new { @class = "hide", multiple = "multiple" }) - @Html.DropDownList("pluginsToUninstall", allPluginsSelectList, new { @class = "hide", multiple = "multiple" }) - + @Html.DropDownList("pluginsToInstall", allPluginsSelectList, new { @class = "d-none noskin", multiple = "multiple" }) + @Html.DropDownList("pluginsToUninstall", allPluginsSelectList, new { @class = "d-none noskin", multiple = "multiple" }) +
        @T("Admin.Configuration.Plugins")
        -
        - @Html.TextBox("filter-query", "", new { style = "margin: 0", placeholder = T("Common.Search").Text }) - - + - @T("Admin.Packaging.UploadPlugin") + @T("Admin.Packaging.UploadPlugin") -
        - -
        - + +
        +

        @T("Admin.Configuration.Plugins.Description")

        1. @Html.Raw(T("Admin.Configuration.Plugins.Description.Step1", "uploadpackage", "#uploadpackage-window"))
        2. @@ -206,7 +179,7 @@
        3. @Html.Raw(T("Admin.Configuration.Plugins.Description.Step4"))
        - +
        -
        -
        - @Model.Licenses[i].StoreUrl (@Model.Licenses[i].StoreName) - - - @T("Admin.Common.Change") - +
        +
        + + @storeLicense.StoreName + + + @Html.Partial("Partials/LicenseLabel", storeLicense.LicenseLabel) +
        - @Html.SmartLabelFor(model => model.Licenses[i].LicenseKey) + @Html.SmartLabelFor(model => model.StoreLicenses[i].LicenseKey) - @Html.HiddenFor(model => model.Licenses[i].StoreId) - @Html.HiddenFor(model => model.Licenses[i].StoreUrl) - - @Html.TextBoxFor(model => model.Licenses[i].LicenseKey, new { @class = "input-large", autocomplete = "off" }) + @Html.HiddenFor(model => model.StoreLicenses[i].StoreId) + @Html.HiddenFor(model => model.StoreLicenses[i].StoreUrl) + @Html.TextBoxFor(model => model.StoreLicenses[i].LicenseKey, keyAttributes) +
        +
        + +
        +
        +
        + @storeLicense.StoreUrl + + @T("Admin.Common.Change") + +
        @@ -219,37 +192,29 @@ } - +
        } -
        - -
        - +@{Html.SmartStore().Window() + .Name("plugin-licence-modal") + .Size(WindowSize.Large) + .Title(T("Admin.Common.License")) + .FooterContent(@ + + + + ) + .Render(); +} -@Html.Action("UploadPackage", "Packaging", new { isTheme = false }) \ No newline at end of file +@{ Html.RenderAction("UploadPackage", "Packaging", new { isTheme = false }); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/Partials/LicenseLabel.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/Partials/LicenseLabel.cshtml new file mode 100644 index 0000000000..c8f1c80292 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/Partials/LicenseLabel.cshtml @@ -0,0 +1,35 @@ +@using SmartStore.Licensing +@model LicenseLabelModel + +@{ + Layout = null; +} + +@if (Model.IsLicensable && !Model.HideLabel) +{ + if (Model.LicenseState == LicensingState.Licensed) + { + @T("Admin.Common.Licensed") + } + else if (Model.LicenseState == LicensingState.Unlicensed && Model.TruncatedLicenseKey.HasValue()) + { + @T("Admin.Common.Unlicensed") + } + else + { + + @if (!Model.RemainingDemoUsageDays.HasValue) + { + @T("Admin.Plugins.LicensingDemoNotStarted") + } + else if (Model.RemainingDemoUsageDays <= 0) + { + @T("Admin.Plugins.LicensingDemoExpired") + } + else + { + @T("Admin.Plugins.LicensingDemoRemainingDays", Model.RemainingDemoUsageDays) + } + + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Poll/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Poll/Create.cshtml index 000368badd..39bbadf493 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Poll/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Poll/Create.cshtml @@ -1,6 +1,5 @@ @model PollModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Polls.AddNew").Text; } @using (Html.BeginForm()) @@ -10,9 +9,14 @@ @T("Admin.ContentManagement.Polls.AddNew") @Html.ActionLink("(" + T("Admin.ContentManagement.Polls.BackToList") + ")", "List")
        - - + +
        - @Html.Partial("_CreateOrUpdate", Model) + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Poll/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Poll/Edit.cshtml index a4f8a02318..167fc04ca9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Poll/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Poll/Edit.cshtml @@ -1,6 +1,5 @@ @model PollModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Polls.EditPollDetails").Text; } @using (Html.BeginForm()) @@ -10,11 +9,20 @@ @T("Admin.ContentManagement.Polls.EditPollDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.ContentManagement.Polls.BackToList") + ")", "List")
        - - - + + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("poll-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Poll/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Poll/List.cshtml index 16324fc040..989298a007 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Poll/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Poll/List.cshtml @@ -13,45 +13,45 @@ @T("Admin.ContentManagement.Polls")
        - - - - -
        - @(Html.Telerik().Grid() - .Name("polls-grid") - .BindTo(Model.Data) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Width(400) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.LanguageName) - .Width(150); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - columns.Bound(x => x.Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.ShowOnHomePage) - .Width(100) - .Template(item => @Html.SymbolForBool(item.ShowOnHomePage)) - .ClientTemplate(@Html.SymbolForBool("ShowOnHomePage")) - .Centered(); - columns.Bound(x => x.StartDate) - .Width(180); - columns.Bound(x => x.EndDate) - .Width(180); - }) - .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Poll")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        \ No newline at end of file + +
        + @(Html.Telerik().Grid() + .Name("polls-grid") + .BindTo(Model.Data) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Width(400) + .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.LanguageName) + .Width(150); + columns.Bound(x => x.DisplayOrder) + .Width(100) + .Centered(); + columns.Bound(x => x.Published) + .Width(100) + .Template(item => @Html.SymbolForBool(item.Published)) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.ShowOnHomePage) + .Width(100) + .Template(item => @Html.SymbolForBool(item.ShowOnHomePage)) + .ClientTemplate(@Html.SymbolForBool("ShowOnHomePage")) + .Centered(); + columns.Bound(x => x.StartDate) + .Width(180); + columns.Bound(x => x.EndDate) + .Width(180); + }) + .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Poll")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml index 007983104e..b6889cc50f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Poll/_CreateOrUpdate.cshtml @@ -1,26 +1,27 @@ -@model PollModel -@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI; +@model PollModel + @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) -@Html.SmartStore().TabStrip().Name("poll-edit").Items(x => +@Html.SmartStore().TabStrip().Name("poll-edit").Style(TabsStyle.Material).Items(x => { x.Add().Text(T("Admin.ContentManagement.Polls.Info").Text).Content(TabInfo()).Selected(true); x.Add().Text(T("Admin.ContentManagement.Polls.Answers").Text).Content(TabAnswers()); + x.Add().Text(T("Common.Voting").Text).Content(TabVoting()); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); - //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "poll-edit", this.Html, this.Model)); }) + @helper TabInfo() - { - +{ @@ -97,106 +98,93 @@
        @Html.SmartLabelFor(model => model.LanguageId) - @Html.DropDownListFor(model => model.LanguageId, new SelectList(ViewBag.AllLanguages, "Id", "Name")) + @Html.DropDownListFor(model => model.LanguageId, Model.AvailableLanguages)
        } + @helper TabAnswers() +{ + if (Model.Id > 0) { - if (Model.Id > 0) - { - - - - -
        - @(Html.Telerik().Grid() - .Name("answers-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - x.Add(y => y.PollId).RouteKey("pollId"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Width(250); - columns.Bound(x => x.NumberOfVotes) - .ReadOnly() - .Width(100); - columns.Bound(x => x.DisplayOrder1).Width(100); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(180); - - }) - .ToolBar(x => x.Insert()) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("PollAnswers", "Poll", new { pollId = Model.Id }) - .Update("PollAnswerUpdate", "Poll") - .Delete("PollAnswerDelete", "Poll") - .Insert("PollAnswerAdd", "Poll", new { pollId = Model.Id }); - }) - .ClientEvents(x => x.OnError("grid_onError")) - .EnableCustomBinding(true)) - -
        - } - else - { - @T("Admin.ContentManagement.Polls.Answers.SaveBeforeEdit") - } +
        + @(Html.Telerik().Grid() + .Name("answers-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + x.Add(y => y.PollId).RouteKey("pollId"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Width("60%"); + columns.Bound(x => x.NumberOfVotes) + .ReadOnly() + .Width("10%") + .Centered(); + columns.Bound(x => x.DisplayOrder1) + .Width("10%") + .Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .Width("20%") + .HtmlAttributes(new { align = "right" }); ; + }) + .ToolBar(x => x.Insert()) + .Editable(x => + { + x.Mode(GridEditMode.InLine); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("PollAnswers", "Poll", new { pollId = Model.Id }) + .Update("PollAnswerUpdate", "Poll") + .Delete("PollAnswerDelete", "Poll") + .Insert("PollAnswerAdd", "Poll", new { pollId = Model.Id }); + }) + .EnableCustomBinding(true)) +
        + } + else + { + @T("Admin.ContentManagement.Polls.Answers.SaveBeforeEdit") + } } -@helper TabStores() +@helper TabVoting() { - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.LimitedToStores) - - @Html.EditorFor(model => model.LimitedToStores) - @Html.ValidationMessageFor(model => model.LimitedToStores) -
        - @Html.SmartLabelFor(model => model.AvailableStores) - - @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) + if (Model.Id > 0) + { +
        + @(Html.Telerik().Grid() + .Name("voting-records-grid") + .DataKeys(x => { - foreach (var store in Model.AvailableStores) - { - - } - } - else + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => { -
        @T("Admin.Configuration.Stores.NoStoresDefined")
        - }
        + columns.Bound(x => x.Email) + .Template(x => Html.ActionLink(x.Email, "Edit", "Customer", new { id = x.CustomerId }, new { })) + .ClientTemplate("\"><#= Email #>"); + columns.Bound(x => x.Username) + .Visible(Model.UsernamesEnabled); + columns.Bound(x => x.FullName); + columns.Bound(x => x.AnswerName); + columns.Bound(x => x.CreatedOn); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("VotingRecords", "Poll", new { pollId = Model.Id })) + .EnableCustomBinding(true)) + + } + else + { + @T("Admin.ContentManagement.Polls.Answers.SaveBeforeEdit") + } +} + +@helper TabStores() +{ + @Html.Partial("StoreSelector", Model) } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationCreatePopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationCreatePopup.cshtml index 28b33bd8e2..389109912d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationCreatePopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationCreatePopup.cshtml @@ -1,5 +1,5 @@ @{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; + Layout = "_AdminPopupLayout"; } @model ProductVariantAttributeCombinationModel @{ @@ -24,5 +24,6 @@ - @Html.Partial("_CreateOrUpdateAttributeCombinationPopup", Model) + + Html.RenderPartial("_CreateOrUpdateAttributeCombinationPopup", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationEditPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationEditPopup.cshtml index 9dc0bb43fb..3e76fe00d6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationEditPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/AttributeCombinationEditPopup.cshtml @@ -1,5 +1,5 @@ @{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; + Layout = "_AdminPopupLayout"; } @model ProductVariantAttributeCombinationModel @{ @@ -18,11 +18,12 @@ @T("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.EditTitle")
        -
        - @Html.Partial("_CreateOrUpdateAttributeCombinationPopup", Model) + + Html.RenderPartial("_CreateOrUpdateAttributeCombinationPopup", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml index ebdea889fa..6884fb03d0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml @@ -5,6 +5,7 @@ ViewBag.Title = T("Admin.Catalog.BulkEdit").Text; } + @using (Html.BeginForm()) {
        @@ -20,186 +21,160 @@ @T("Admin.Catalog.BulkEdit.Info")
        - - - - - - - - - +
        +
        + @Html.SmartLabelFor(model => model.SearchProductName) + @Html.TextBoxFor(model => Model.SearchProductName, new { @class = "form-control" }) +
        +
        + @Html.SmartLabelFor(model => model.SearchProductTypeId) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, allString) +
        @if (Model.AvailableStores.Count > 1) { -
        - - - +
        + @Html.SmartLabelFor(model => model.SearchStoreId) + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All"), new { @class = "form-control" }) +
        } - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
        - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, allString) -
        - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores, allString) -
        - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, allString) -
        - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, allString) -
        -   - - -
        - -

        - - - - - -
        - @(Html.Telerik().Grid() - .Name("products-grid") - .DataKeys(keys => - { - keys.Add(p => p.Id); - }) - .ToolBar(commands => - { - commands.SubmitChanges(); - }) - .DataBinding(dataBinding => - dataBinding.Ajax() - .Select("BulkEditSelect", "Product") - .Update("BulkEditSave", "Product") - ) - .Columns(columns => - { - columns.Bound(p => p.Name) - .ReadOnly() - .Template(x => @Html.LabeledProductName(x.Id, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("Id", "Name")); - columns.Bound(p => p.Sku).Width(150); - columns.Bound(p => p.Price) - .Format("{0:0.00}") - .RightAlign() - .Width(120); - columns.Bound(p => p.OldPrice) - .Format("{0:0.00}") - .RightAlign() - .Width(120); - columns.Bound(p => p.ManageInventoryMethod) - .ReadOnly(); - columns.Bound(p => p.StockQuantity) - .Centered() - .Width(80); - columns.Bound(p => p.Published) - .Width(80) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.Id) - .ReadOnly() - .Width(90) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); - columns.Command( - commands => commands.Delete().Localize(T) - ).Width(90); - }) - .ClientEvents(events => events.OnDataBinding("Grid_onDataBinding").OnError("Grid_onError").OnSubmitChanges("Grid_onSubmitChanges")) - .Editable(editing => editing.Mode(GridEditMode.InCell)) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - ) +
        + @Html.SmartLabelFor(model => model.SearchCategoryId) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, allString) +
        +
        + @Html.SmartLabelFor(model => model.SearchManufacturerId) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, allString) +
        +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        + - - function Grid_onSubmitChanges(e) { - //Pass search parameters - e.values["SearchProductName"] = $('#@Html.FieldIdFor(model => model.SearchProductName)').val(); - e.values["SearchStoreId"] = $('#SearchStoreId').val(); - e.values["SearchCategoryId"] = $('#SearchCategoryId').val(); - e.values["SearchManufacturerId"] = $('#SearchManufacturerId').val(); - e.values["SearchProductTypeId"] = $('#SearchProductTypeId').val(); - } - -
        } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemEditPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemEditPopup.cshtml index 8c277b9f8e..f760a7b5ca 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemEditPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemEditPopup.cshtml @@ -3,7 +3,7 @@ @model ProductBundleItemModel @{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; + Layout = "_AdminPopupLayout"; } @using (Html.BeginForm(new { btnId = ViewBag.BtnId, formId = ViewBag.FormId })) @@ -16,11 +16,12 @@
        @ViewBag.Title
        - -
        @@ -34,30 +35,10 @@ $('#attribute_@attribute.Id').selectWrapper();
        } - - $('#@Html.FieldIdFor(model => model.FilterAttributes)').change(function () { - $('#AttributeFilters').toggle( - $('#@Html.FieldIdFor(model => model.FilterAttributes)').is(':checked') - ); - }).trigger('change'); }); - - @if (ViewBag.RefreshPage) - { - - try {window.opener.document.forms['@(ViewBag.FormId)'].@(ViewBag.BtnId).click();} - catch (e){} - - } - @if (ViewBag.RefreshPage && ViewBag.CloseWindow) - { - - window.close(); - - } - @Html.SmartStore().TabStrip().Name("bundleitem-edit").Items(x => + @Html.SmartStore().TabStrip().Name("bundleitem-edit").Style(TabsStyle.Material).Items(x => { x.Add().Text(T("Common.General").Text).Content(TabInfo()).Selected(true); x.Add().Text(T("Admin.Catalog.Products.BundleItems.Attributes").Text).Content(TabAttributeFilters()); @@ -75,6 +56,9 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name) @@ -88,11 +72,6 @@ @Html.ValidationMessageFor(model => model.Locales[item].ShortDescription) - - - @Html.HiddenFor(model => model.Locales[item].LanguageId) - - , @ @@ -190,7 +169,9 @@ @Html.SmartLabelFor(model => model.CreatedOn) @@ -198,7 +179,9 @@ @Html.SmartLabelFor(model => model.UpdatedOn)
        - @Html.DisplayFor(model => model.CreatedOn) +
        + @Html.DisplayFor(model => model.CreatedOn) +
        - @Html.DisplayFor(model => model.UpdatedOn) +
        + @Html.DisplayFor(model => model.UpdatedOn) +
        @@ -206,64 +189,47 @@ @helper TabAttributeFilters() { -
        - - - - - -
        - @Html.SmartLabelFor(model => model.FilterAttributes) - - @Html.EditorFor(model => model.FilterAttributes) - @Html.ValidationMessageFor(model => model.FilterAttributes) -
        -
        - -
        - + if (Model.Attributes.Count == 0) + { +
        + @T("Admin.Catalog.Products.BundleItems.Fields.FilterAttributes.NoneNote") +
        + return; + } +
        + + + + + @foreach (var attribute in Model.Attributes) { - if(Model.Attributes.IndexOf(attribute) != 0) - { - - - - } if (attribute.PreSelect.Count > 0) { } } -
        + @Html.SmartLabelFor(model => model.FilterAttributes) + + @Html.CheckBoxFor(model => model.FilterAttributes, new { data_toggler_for = "#AttributeFilters" }) + @Html.ValidationMessageFor(model => model.FilterAttributes) +
        -
        -
        -
        - -
        + @Html.SmartLabel(attribute.AttributeControlId, attribute.Name)
        - @Html.DropDownList(attribute.AttributeControlId, attribute.Values, null, new { multiple = "multiple" }) + @Html.DropDownList(attribute.AttributeControlId, attribute.Values, null, new { multiple = "multiple", @class = "form-control" })
        -
        - - @Html.Hint(T("Admin.Catalog.Products.BundleItems.Fields.FilterPreSelect.Hint")) -
        + @Html.SmartLabel(attribute.PreSelectControlId, T("Admin.Catalog.Products.BundleItems.Fields.FilterPreSelect"), T("Admin.Catalog.Products.BundleItems.Fields.FilterPreSelect.Hint"))
        - @Html.DropDownList(attribute.PreSelectControlId, attribute.PreSelect) + @Html.DropDownList(attribute.PreSelectControlId, attribute.PreSelect, new { @class = "form-control" })
        - @if (Model.Attributes.Count == 0) - { -
        - @T("Admin.Catalog.Products.BundleItems.Fields.FilterAttributes.NoneNote") -
        - } -
        + + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/Create.cshtml index be97e1f213..fae1eea678 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/Create.cshtml @@ -1,6 +1,5 @@ @model ProductModel @{ - //page title ViewBag.Title = T("Admin.Catalog.Products.AddNew").Text; } @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "product-form" })) @@ -13,12 +12,13 @@
        -
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml index 19284787da..d5ec3dcfa6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml @@ -1,5 +1,6 @@ @model ProductModel @using Telerik.Web.Mvc.UI; +@using SmartStore.Core.Domain.Catalog; @{ ViewBag.Title = T("Admin.Catalog.Products.EditProductDetails").Text; } @@ -13,89 +14,194 @@ (@T("Admin.Catalog.Products.BackToList"))
        - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } - - - - -  @T("Admin.Catalog.Products.Copy") + + + @T("Admin.Catalog.Products.Copy") - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("product-delete") -@*copy product form*@ @{Html.SmartStore().Window() .Name("copyproduct-window") - .Title(T("Admin.Catalog.Products.Copy").Text) + .Title(T("Admin.Catalog.Products.Copy")) + .Size(WindowSize.Large) .Content(@ - @using (Html.BeginForm("CopyProduct", "Product", FormMethod.Post, new { style = "margin:0;" })) { - @Html.HiddenFor(model => Model.CopyProductModel.Id) - -
        - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.CopyProductModel.Name) - - @Html.EditorFor(model => Model.CopyProductModel.Name) -
        - @Html.SmartLabelFor(model => model.CopyProductModel.Published) - - @Html.EditorFor(model => Model.CopyProductModel.Published) -
        - @Html.SmartLabelFor(model => model.CopyProductModel.CopyImages) - - @Html.EditorFor(model => Model.CopyProductModel.CopyImages) -
        -
        - } -
        ) - .FooterContent(@ - - ) - .Width(500) - .Height(500) - .Modal(true) - .Visible(false) - .Render(); + + @Html.HiddenFor(model => Model.CopyProductModel.Id) + + + + + + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.CopyProductModel.NumberOfCopies) + + @Html.EditorFor(model => Model.CopyProductModel.NumberOfCopies) +
        + @Html.SmartLabelFor(model => model.CopyProductModel.Name) + + @Html.EditorFor(model => Model.CopyProductModel.Name) +
        + @Html.SmartLabelFor(model => model.CopyProductModel.Published) + + @Html.EditorFor(model => Model.CopyProductModel.Published) +
        + @Html.SmartLabelFor(model => model.CopyProductModel.CopyImages) + + @Html.EditorFor(model => Model.CopyProductModel.CopyImages) +
        + +
        ) + .FooterContent(@ + + ) + .Render(); } + + +@if (Model.Id != 0 && Model.NumberOfAvailableProductAttributes > 0) +{ + Html.SmartStore().Window() + .Name("CopyAttributeOptionsDialog") + .Title(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.CopyOptions")) + .Size(WindowSize.Large) + .Content(@ +
        + + + +
        @T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.CopyOptionsHint")
        +

        + +
        +

        @T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.AskExistingValues")

        +
        + + +
        +
        + + +
        +
        +
        +
        ) + .FooterContent(@ + + + ) + .Render(); +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditAttributeValues.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditAttributeValues.cshtml index c341f0c4b7..6537c83374 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditAttributeValues.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditAttributeValues.cshtml @@ -1,100 +1,99 @@ @model ProductModel.ProductVariantAttributeValueListModel - @using Telerik.Web.Mvc.UI; @using SmartStore.Web.Framework.UI; @using SmartStore.Admin.Extensions; - @{ - //page title ViewBag.Title = string.Format(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.EditAttributeDetails").Text, Model.ProductVariantAttributeName, Model.ProductName); } + @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "productvariantattribute-form" })) { -
        -
        - @string.Format(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.EditAttributeDetails").Text, Model.ProductVariantAttributeName, Model.ProductName) - @Html.ActionLink("(" + T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.BackToProduct") + ")", "Edit", "Product", new { Id = Model.ProductId }, null) -
        -
        -
        -
        - - - - - - - -
        - @(Html.Telerik().Grid() - .Name("pvav-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("pvavId"); - x.Add(y => y.ProductVariantAttributeId).RouteKey("productVariantAttributeId"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name) - .ReadOnly() - .Template(x => @Html.VariantAttributeValueName(x)) - .ClientTemplate(@Html.VariantAttributeValueName()); - columns.Bound(x => x.Alias); - columns.Bound(x => x.LinkedProductName) - .ReadOnly() - .Template(x => @Html.LabeledProductName(x.LinkedProductId, x.LinkedProductName, x.LinkedProductTypeName, x.LinkedProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("LinkedProductId", "LinkedProductName", "LinkedProductTypeName", "LinkedProductTypeLabelHint")); - columns.Bound(x => x.PriceAdjustmentString) - .Width(80) - .Centered(); - columns.Bound(x => x.WeightAdjustmentString) - .Width(80) - .Centered(); - columns.Bound(x => x.IsPreSelected) - .Width(80) - .Template(item => @Html.SymbolForBool(item.IsPreSelected)) - .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(80) - .Centered(); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=productvariantattribute-form', 1000, 600, true); return false;\" class='t-button' />") - .Title(T("Admin.Common.Edit").Text); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) - .Width(50) - .Title(T("Admin.Common.Delete").Text); - }) - .DataBinding(dataBinding => - dataBinding.Ajax() - .Select("ProductAttributeValueList", "Product", new { productVariantAttributeId = Model.ProductVariantAttributeId }) - .Delete("ProductAttributeValueDelete", "Product")) - .EnableCustomBinding(true)) -
        - +
        +
        + @string.Format(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.EditAttributeDetails").Text, Model.ProductVariantAttributeName, Model.ProductName) + @Html.ActionLink("(" + T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.BackToProduct") + ")", "Edit", "Product", new { Id = Model.ProductId }, null) +
        +
        +
        +
        + +
        + @(Html.Telerik().Grid() + .Name("pvav-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("pvavId"); + x.Add(y => y.ProductVariantAttributeId).RouteKey("productVariantAttributeId"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .ReadOnly() + .ClientTemplate(@Html.VariantAttributeValueName()); + columns.Bound(x => x.Alias); + columns.Bound(x => x.LinkedProductName) + .ReadOnly() + .ClientTemplate(@Html.LabeledProductName("LinkedProductId", "LinkedProductName", "LinkedProductTypeName", "LinkedProductTypeLabelHint")); + columns.Bound(x => x.PriceAdjustmentString) + .Width(80) + .Centered(); + columns.Bound(x => x.WeightAdjustmentString) + .Width(80) + .Centered(); + columns.Bound(x => x.IsPreSelected) + .Width(80) + .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(80) + .Centered(); + columns.Command(commands => + { + commands.Custom("edit-pvav").Text(T("Common.Edit")); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right" }) + .Width(150); + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("ProductAttributeValueList", "Product", new { productVariantAttributeId = Model.ProductVariantAttributeId }) + .Delete("ProductAttributeValueDelete", "Product")) + .ToolBar(commands => commands.Template(GridCommands)) + .EnableCustomBinding(true)) +
        +} + +@helper GridCommands(Grid grid) +{ + + +
        -} \ No newline at end of file + return false; + }); + }); + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditProductTag.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditProductTag.cshtml index 640d4c1f80..1cb887125a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditProductTag.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditProductTag.cshtml @@ -1,9 +1,6 @@ -@{ - Layout = "~/Administration/Views/Shared/_AdminPopupLayout.cshtml"; -} -@model ProductTagModel +@model ProductTagModel @{ - //page title + Layout = "_AdminPopupLayout"; ViewBag.Title = T("Admin.Catalog.ProductTags.EditTagDetails").Text; } @using (Html.BeginForm()) @@ -13,8 +10,12 @@ @T("Admin.Catalog.ProductTags.EditTagDetails")- @Model.Name
        - +
        - @Html.Partial("_CreateOrUpdateProductTag", Model) + + Html.RenderPartial("_CreateOrUpdateProductTag", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/AttributeControlType.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/AttributeControlType.cshtml index a2494c300f..4149706492 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/AttributeControlType.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/AttributeControlType.cshtml @@ -3,7 +3,7 @@ var controlTypeList = AttributeControlType.DropdownList.ToSelectList(); } - @foreach (var item in controlTypeList) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductAttribute.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductAttribute.cshtml index 4e3d7d0def..5f43c5e07d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductAttribute.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductAttribute.cshtml @@ -1,8 +1,7 @@ - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductCategory.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductCategory.cshtml index 6c61bf7d44..df97acf556 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductCategory.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/ProductCategory.cshtml @@ -1,5 +1,8 @@ - - + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceCustomer.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceCustomer.cshtml index 4b6c4391f6..b17a055582 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceCustomer.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceCustomer.cshtml @@ -1,8 +1,7 @@ - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceStore.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceStore.cshtml index 28cf2f3f95..cb49c262b6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceStore.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/EditorTemplates/TierPriceStore.cshtml @@ -1,7 +1,6 @@ - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml index 85c43950dc..69a81b30c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml @@ -12,197 +12,158 @@ @T("Admin.Catalog.Products.Manage") -
        - @Html.Widget("admin_button_toolbar_before") +
        + @{ Html.RenderWidget("admin_button_toolbar_before"); } + +
        + @Html.TextBoxFor(model => Model.GoDirectlyToSku, new { @class = "form-control", placeholder = T("Admin.Catalog.Products.List.GoDirectlyToSku") }) +
        + + +
        +
        -  @T("Admin.Common.AddNew") + + @T("Admin.Common.AddNew") - - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
        - - - - - - - - - + - - - +
        + @Html.SmartLabelFor(model => model.SearchStoreId) + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, allString, new { @class = "form-control" }) +
        } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.SearchProductName) - - @Html.EditorFor(model => Model.SearchProductName) -
        - @Html.SmartLabelFor(model => model.SearchProductTypeId) - - @Html.DropDownListFor(model => model.SearchProductTypeId, Model.AvailableProductTypes, allString) -
        - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, allString) -
        - @Html.SmartLabelFor(model => model.SearchWithoutCategories) - - @Html.EditorFor(model => model.SearchWithoutCategories) -
        - @Html.SmartLabelFor(model => model.SearchCategoryId) - - @Html.DropDownListFor(model => model.SearchCategoryId, Model.AvailableCategories, allString) -
        - @Html.SmartLabelFor(model => model.SearchWithoutManufacturers) - - @Html.EditorFor(model => model.SearchWithoutManufacturers) -
        - @Html.SmartLabelFor(model => model.SearchManufacturerId) - - @Html.DropDownListFor(model => model.SearchManufacturerId, Model.AvailableManufacturers, allString) -
        - @Html.SmartLabelFor(model => model.SearchIsPublished) - - @Html.EditorFor(model => model.SearchIsPublished) -
        - @Html.SmartLabelFor(model => model.SearchHomePageProducts) - - @Html.EditorFor(model => model.SearchHomePageProducts) -
        - @Html.SmartLabelFor(model => model.GoDirectlyToSku) - - @Html.EditorFor(model => Model.GoDirectlyToSku) - -
        -   - - -
        - -

        +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        + - - - - -
        - @(Html.Telerik().Grid() - .Name("products-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .Filterable(false) - .Sortable(false) - .Template(x => string.Format("", x.Id)) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); +
        + @(Html.Telerik().Grid() + .Name("products-grid") + .Columns(columns => + { + columns.Bound(x => x.Id) + .Filterable(false) + .Sortable(false) + .Template(x => string.Format("", x.Id)) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); - if (Model.DisplayProductPictures) - { - columns.Bound(x => x.PictureThumbnailUrl) - .Sortable(false) - .Width(50) - .Template( - @ - @item.Id - - ) - .ClientTemplate("<#= Id #> class='zoomable-thumb' <# } #> />"); - } - columns.Bound(x => x.Name) - .Template(x => @Html.LabeledProductName(x.Id, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("Id", "Name")); - columns.Bound(x => x.Sku) - .Sortable(false); - columns.Bound(x => x.Price) - .Format("{0:0.00}") - .RightAlign(); - columns.Bound(x => x.StockQuantity) - .Sortable(false) - .Centered(); - columns.Bound(x => x.LimitedToStores) - .Sortable(false) - .Template(item => @Html.SymbolForBool(item.LimitedToStores)) - .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) - .Hidden(Model.AvailableStores.Count <= 1) - .Centered(); - columns.Bound(x => x.CreatedOn); - columns.Bound(x => x.UpdatedOn) - .Sortable(false); - columns.Bound(x => x.Published) - .Sortable(false) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - }) - .Sortable(x => - { - x.AllowUnsort(true); - x.SortMode(GridSortMode.SingleColumn); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("ProductList", "Product")) - .ClientEvents(events => events.OnDataBinding("productsGrid_onDataBinding").OnDataBound("productsGrid_onDataBound")) - .EnableCustomBinding(true) - .PreserveGridState()) -
        + if (Model.DisplayProductPictures) + { + columns.Bound(x => x.PictureThumbnailUrl) + .Sortable(false) + .Width(50) + .Template( + @ + @item.Id + + ) + .ClientTemplate("
        <#= Id #> class='zoomable-thumb' <# } #> />
        "); + } + columns.Bound(x => x.Name) + .Template(x => @Html.LabeledProductName(x.Id, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) + .ClientTemplate(@Html.LabeledProductName("Id", "Name")); + columns.Bound(x => x.Sku); + columns.Bound(x => x.Price) + .Format("{0:0.00}") + .RightAlign(); + columns.Bound(x => x.StockQuantity) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.UpdatedOn); + columns.Bound(x => x.Published) + .Template(item => @Html.SymbolForBool(item.Published)) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + }) + .Sortable(x => + { + x.AllowUnsort(true); + x.SortMode(GridSortMode.SingleColumn); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("ProductList", "Product")) + .ClientEvents(events => events.OnDataBinding("productsGrid_onDataBinding").OnDataBound("productsGrid_onDataBound")) + .EnableCustomBinding(true) + .PreserveGridState()) + - - - -} \ No newline at end of file +
        + @(Html.Telerik().Grid() + .Name("product-tags-grid") + .DataKeys(x => { x.Add(y => y.Id).RouteKey("Id"); }) + .Columns(columns => + { + columns.Bound(x => x.Name); + columns.Bound(x => x.ProductCount) + .Centered() + .Width(150); + columns.Command(commands => + { + commands.Custom("edit-product-tag").Text(T("Common.Edit")); + commands.Delete().Localize(T); + }) + .Width(90) + .HtmlAttributes(new { align = "right" }); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductTags", "Product") + .Delete("ProductTagDelete", "Product"); + }) + .Filterable() + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true)) + + +
        +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml index 6b11b4ac3f..fcac81fbca 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_Create.SaveFirst.cshtml @@ -2,6 +2,6 @@

        @T("Admin.SaveBeforeEdit")

        - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Acl.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Acl.cshtml index 873a7becf4..8c8c81a5ae 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Acl.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Acl.cshtml @@ -1,50 +1,3 @@ @model ProductModel - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.SubjectToAcl) - - @Html.EditorFor(model => model.SubjectToAcl) - @Html.ValidationMessageFor(model => model.SubjectToAcl) -
        - @Html.SmartLabelFor(model => model.AvailableCustomerRoles) - - @if (Model.AvailableCustomerRoles != null && Model.AvailableCustomerRoles.Count > 0) - { - foreach (var customerRole in Model.AvailableCustomerRoles) - { -
        - -
        - } - } - else - { -
        No customer roles defined
        - } -
        +@Html.Partial("AclSelector", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml index 0827ea946f..7db058511c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml @@ -5,94 +5,84 @@ @if (Model.Id > 0) {
        -
        - @T("Admin.Catalog.Products.AssociatedProducts.Note1") - @T("Admin.Catalog.Products.AssociatedProducts.Note2") -
        +
          +
        • @T("Admin.Catalog.Products.AssociatedProducts.Note1")
        • +
        • @T("Admin.Catalog.Products.AssociatedProducts.Note2")
        • +
        -

        +
        + @(Html.Telerik().Grid() + .Name("associatedproducts-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("AssociatedProductList", "Product", new { productId = Model.Id }) + .Update("AssociatedProductUpdate", "Product") + .Delete("AssociatedProductDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.ProductName) + .ReadOnly() + .Width("40%") + .ClientTemplate(@Html.LabeledProductName("Id", "ProductName")); + columns.Bound(x => x.Sku) + .Width("20%") + .ReadOnly(); + columns.Bound(x => x.Published) + .ReadOnly() + .Width("10%") + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width("10%") + .Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right" }) + .Width("20%"); + }) + .ToolBar(commands => commands.Template(GridCommands)) + .EnableCustomBinding(true)) +
        - - - - - - - -
        - @(Html.Telerik().Grid() - .Name("associatedproducts-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("AssociatedProductList", "Product", new { productId = Model.Id }) - .Update("AssociatedProductUpdate", "Product") - .Delete("AssociatedProductDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.ProductName) - .ReadOnly() - .Width(520) - .ClientTemplate(@Html.LabeledProductName("Id", "ProductName")); - columns.Bound(x => x.Sku) - .ReadOnly(); - columns.Bound(x => x.Published) - .ReadOnly() - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Centered(); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(220); - }) - .EnableCustomBinding(true)) -
        - - - -
        } else { - @T("Admin.Catalog.Products.AssociatedProducts.SaveBeforeEdit") -} \ No newline at end of file +

        + @T("Admin.Catalog.Products.AssociatedProducts.SaveBeforeEdit") +

        +} + +@helper GridCommands(Grid grid) +{ + @(Html.SmartStore().EntityPicker() + .IconCssClass("fa fa-plus") + .DisableGroupedProducts(true) + .HtmlAttribute("class", "t-button t-button-primary") + .DialogTitle(T("Admin.Catalog.Products.AssociatedProducts.AddNew").Text.EncodeJsString('\'', false)) + .Caption(T("Admin.Catalog.Products.AssociatedProducts.AddNew").Text.EncodeJsString('\'', false)) + .OnSelectionCompleted("AssociatedProducts_Selected")) +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml index fc675e560f..b94502e231 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml @@ -3,70 +3,104 @@ @using Telerik.Web.Mvc.UI;
        -

        @T("Admin.Catalog.Products.ProductVariantAttributes.Attributes")

        +

        @T("Admin.Catalog.Products.ProductVariantAttributes.Attributes")

        @if (Model.Id > 0) - { - if (Model.NumberOfAvailableProductAttributes > 0) - { - - + { + if (Model.NumberOfAvailableProductAttributes > 0) + { + + @(Html.Telerik().Grid() - .Name("productvariantattributes-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("ProductVariantAttributeList", "Product", new { productId = Model.Id }) - .Insert("ProductVariantAttributeInsert", "Product", new { productId = Model.Id }) - .Update("ProductVariantAttributeUpdate", "Product") - .Delete("ProductVariantAttributeDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.ProductAttribute) - .Width(150); - columns.Bound(x => x.TextPrompt) - .Width(150) - .Centered(); - columns.Bound(x => x.IsRequired) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsRequired)) - .ClientTemplate(@Html.SymbolForBool("IsRequired")) - .Centered(); - columns.Bound(x => x.AttributeControlType) - .Width(150) - .Centered(); - columns.Bound(x => x.DisplayOrder1) - .Width(100) - .Centered(); - columns.Bound(x => x.ViewEditUrl) - .Width(200) - .ClientTemplate("\"><#= ViewEditText #>") - .ReadOnly() - .Title(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values").Text); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }) - .Width(180); - }) - .ToolBar(commands => commands.Insert()) - .ClientEvents(events => events.OnEdit("onProductVariantAttributeEdit")) - .EnableCustomBinding(true)) + .Name("productvariantattributes-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductVariantAttributeList", "Product", new { productId = Model.Id }) + .Insert("ProductVariantAttributeInsert", "Product", new { productId = Model.Id }) + .Update("ProductVariantAttributeUpdate", "Product") + .Delete("ProductVariantAttributeDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.ProductAttribute) + .Width("16%"); + columns.Bound(x => x.AttributeControlType) + .Width("16%"); + columns.Bound(x => x.TextPrompt) + .Width("16%"); + columns.Bound(x => x.IsRequired) + .Width("10%") + .ClientTemplate(@Html.SymbolForBool("IsRequired")) + .Centered(); + columns.Bound(x => x.DisplayOrder1) + .Width("10%") + .Centered(); + columns.Bound(x => x.ViewEditUrl) + .Width("16%") + .ClientTemplate( + "<#= ViewEditText #>" + + "" + ) + .ReadOnly() + .Title(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values").Text); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .Width("20%") + .HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Insert() ) + .ClientEvents(events => + { + events.OnEdit("onProductVariantAttributeEdit"); + events.OnRowDataBound("onProductVariantAttributeRowDataBound"); + }) + .EnableCustomBinding(true)) } else { @@ -83,8 +117,8 @@ }
        -
        -

        @T("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations")

        +
        +

        @T("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations")

        @if (Model.Id > 0) { @@ -94,34 +128,13 @@ @T("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.Description")
        -
        - - -
        - - - -
        - - - @@ -162,7 +175,7 @@ .Columns(columns => { columns.Bound(x => x.AttributesXml) - .ClientTemplate("?btnId=btnRefreshCombinations&formId=product-form', 1000, 800, true); return false;\"><#= AttributesXml #>
        <#= Warnings #>") + .ClientTemplate("?btnId=btnRefreshCombinations&formId=product-form'); return false;\"><#= AttributesXml #>
        <#= Warnings #>") .Width(460) .Encoded(false) .ReadOnly(); @@ -184,19 +197,20 @@ .Centered(); columns.Bound(x => x.AllowOutOfStockOrders) .Width(60) - .HeaderTemplate("") + .HeaderTemplate("") .Template(item => @Html.SymbolForBool(item.AllowOutOfStockOrders)) .ClientTemplate(@Html.SymbolForBool("AllowOutOfStockOrders")) .Centered(); //columns.Bound(x => x.Id) // .Width(90) // .Centered() - // .ClientTemplate("") + // .ClientTemplate("") // .Title(""); columns.Command(commands => { commands.Delete().Localize(T); }) + .HtmlAttributes(new { align = "right" }) .Width(90); }) .Pageable(settings => settings.PageSize(100).Position(GridPagerPosition.Both)) @@ -207,15 +221,9 @@ .Delete("ProductVariantAttributeCombinationDelete", "Product"); }) .ClientEvents(events => events.OnRowDataBound("onRowDataBound_Combinations")) + .ToolBar(commands => commands.Template(AttributeCombinationsGridCommands)) .EnableCustomBinding(true))
        - -
        - -
        } else { @@ -230,4 +238,23 @@ @T("Admin.Catalog.Products.ProductVariantAttributes.SaveBeforeEdit")
        } - \ No newline at end of file + + +@helper AttributeCombinationsGridCommands(Grid grid) +{ + + + + + +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.BundleItems.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.BundleItems.cshtml index 1d3bbb0a9e..7b742117d3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.BundleItems.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.BundleItems.cshtml @@ -4,19 +4,10 @@ @if (Model.Id > 0) { - - -
        - +
        @T("Admin.Catalog.Products.BundleItems.NotesOnProductBundles"): - @T("Common.Show") + @T("Common.Show")
        @Html.Raw(T("Admin.Catalog.Products.BundleItems.AdminNoteGeneral")) @@ -28,6 +19,7 @@ @Html.Raw(T("Admin.Catalog.Products.BundleItems.AdminNotePerItemShipping")) }
        +
        @@ -38,6 +30,9 @@ @Html.SmartLabelFor(model => model.Locales[item].BundleTitleText) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => model.Locales[item].BundleTitleText) @Html.ValidationMessageFor(model => model.Locales[item].BundleTitleText) @@ -88,121 +83,111 @@
        -

        - - - - - - - - -
        - @(Html.Telerik().Grid() - .Name("bundleitems-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("BundleItemList", "Product", new { productId = Model.Id }) - .Delete("BundleItemDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.ProductName) - .ReadOnly() - .Width(520) - .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.Sku) - .ReadOnly(); - columns.Bound(x => x.Quantity) - .Centered(); - columns.Bound(x => x.Discount) - .Centered(); - columns.Bound(x => x.Visible) - .ClientTemplate(@Html.SymbolForBool("Visible")) - .Centered(); - columns.Bound(x => x.Published) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Centered(); - columns.Bound(x => x.Id) - .Centered() - .ClientTemplate("") - .Title(""); - columns.Command(commands => - { - commands.Delete().Localize(T); - }); - }) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_bundleItems")) - .EnableCustomBinding(true)) -
        - - - -
        - - } else { - @T("Admin.Catalog.Products.BundleItems.SaveBeforeEdit") +

        + @T("Admin.Catalog.Products.BundleItems.SaveBeforeEdit") +

        +} + +@helper GridCommands(Grid grid) +{ + @(Html.SmartStore().EntityPicker() + .HtmlAttribute("class", "t-button t-button-primary") + .DialogTitle(T("Admin.Catalog.Products.BundleItems.AddNew").Text.EncodeJsString('\'', false)) + .Caption(T("Common.AddNew").Text.EncodeJsString('\'', false)) + .DisableBundleProducts(true) + .DisableGroupedProducts(true) + .IconCssClass("fa fa-plus") + .OnSelectionCompleted("EntPicker_Completed")) + + } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Categories.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Categories.cshtml index 5e758d0e85..0b386ebd9d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Categories.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Categories.cshtml @@ -8,7 +8,13 @@ { @(Html.Telerik().Grid() - .Name("productcategories-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("ProductCategoryList", "Product", new { productId = Model.Id }) - .Insert("ProductCategoryInsert", "Product", new { productId = Model.Id }) - .Update("ProductCategoryUpdate", "Product") - .Delete("ProductCategoryDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.Category) + .Name("productcategories-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductCategoryList", "Product", new { productId = Model.Id }) + .Insert("ProductCategoryInsert", "Product", new { productId = Model.Id }) + .Update("ProductCategoryUpdate", "Product") + .Delete("ProductCategoryDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.Category) + .Width("60%") .Template(x => Html.ActionLink(x.Category, "Edit", "Category", new { id = x.CategoryId })) .ClientTemplate("\"><#= Category #>"); - columns.Bound(x => x.IsFeaturedProduct) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsFeaturedProduct)) - .ClientTemplate(@Html.SymbolForBool("IsFeaturedProduct")) - .Centered(); + columns.Bound(x => x.IsFeaturedProduct) + .Width(100) + .Template(item => @Html.SymbolForBool(item.IsFeaturedProduct)) + .ClientTemplate(@Html.SymbolForBool("IsFeaturedProduct")) + .Centered(); columns.Bound(x => x.DisplayOrder) .Centered() - .Width(100); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }) - .Width(180); - }) - .ToolBar(commands => commands.Insert()) - .ClientEvents(events => { + .Width(100); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Insert()) + .ClientEvents(events => { events.OnEdit("onProductCategoryEdit"); events.OnDataBound("onProductCategoryDataBound"); }) - .EnableCustomBinding(true)) + .EnableCustomBinding(true)) } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml index 694263f00a..4ca1174d22 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Discounts.cshtml @@ -2,18 +2,21 @@ @if (Model.AvailableDiscounts != null && Model.AvailableDiscounts.Count > 0) { - foreach (var discount in Model.AvailableDiscounts) - { - - } + foreach (var discount in Model.AvailableDiscounts) + { +
        + + +
        + } } else { -
        - @T("Admin.Catalog.Products.Discounts.NoDiscounts") +
        + @T("Admin.Promotions.Discounts.NoDiscountsAvailable")
        } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml new file mode 100644 index 0000000000..101f7681e8 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Downloads.cshtml @@ -0,0 +1,291 @@ +@model ProductModel + +@using SmartStore.Core.Domain.Catalog; + + + + + + + + + + + + + + + + + + + + @if (Model.DownloadId != null) + { + + + + + + + + + + + + + if(Model.DownloadVersions.Count > 1) + { + + + + + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        +
        +
        @T("Common.Download")
        +
        +
        + @Html.SmartLabelFor(model => model.IsDownload) + + @Html.CheckBoxFor(model => model.IsDownload, new { data_toggler_for = "#pnlIsDownload" }) + @Html.ValidationMessageFor(model => model.IsDownload) +
        + @Html.SmartLabelFor(model => model.DownloadId) + + @Html.EditorFor(model => model.DownloadId, new { entityId = Model.Id, entityName = "Product" }) + @Html.ValidationMessageFor(model => model.DownloadId) +
        + @Html.SmartLabelFor(model => model.DownloadFileVersion) + + @Html.EditorFor(model => model.DownloadFileVersion) + @Html.ValidationMessageFor(model => model.DownloadFileVersion) +
        +
        +
        @T("Common.Download.Versions")
        +
        +
        + @Html.SmartLabelFor(model => model.NewVersionDownloadId) + + @Html.EditorFor(model => model.NewVersionDownloadId, new { entityId = Model.Id, entityName = "Product" }) + @Html.ValidationMessageFor(model => model.NewVersionDownloadId) +
        + @Html.SmartLabelFor(model => model.NewVersion) + + @Html.EditorFor(model => model.NewVersion) + @Html.ValidationMessageFor(model => model.NewVersion) +
        + + + + + + + + + + @foreach (var download in Model.DownloadVersions) + { + + + + + + } + +
        @T("Common.Download.Version")@T("Common.Download")
        +
        + @download.FileVersion +
        +
        + + + + + + + + +
        +
        +
        +
        @T("Common.Options")
        +
        +
        + @Html.SmartLabelFor(model => model.UnlimitedDownloads) + + @Html.CheckBoxFor(model => model.UnlimitedDownloads, new { data_toggler_for = "#pnlMaxNumberOfDownloads", data_toggler_reverse = true }) + @Html.ValidationMessageFor(model => model.UnlimitedDownloads) +
        + @Html.SmartLabelFor(model => model.MaxNumberOfDownloads) + + @Html.EditorFor(model => model.MaxNumberOfDownloads) + @Html.ValidationMessageFor(model => model.MaxNumberOfDownloads) +
        + @Html.SmartLabelFor(model => model.DownloadExpirationDays) + + @Html.EditorFor(model => model.DownloadExpirationDays) + @Html.ValidationMessageFor(model => model.DownloadExpirationDays) +
        + @Html.SmartLabelFor(model => model.DownloadActivationTypeId) + + @Html.DropDownListFor(model => model.DownloadActivationTypeId, ((DownloadActivationType)Model.DownloadActivationTypeId).ToSelectList()) + @Html.ValidationMessageFor(model => model.DownloadActivationTypeId) +
        + @Html.SmartLabelFor(model => model.HasUserAgreement) + + @Html.CheckBoxFor(model => model.HasUserAgreement, new { data_toggler_for = "#pnlUserAgreementText" }) + @Html.ValidationMessageFor(model => model.HasUserAgreement) +
        + @Html.SmartLabelFor(model => model.UserAgreementText) + + @Html.EditorFor(model => model.UserAgreementText, "Html") + @Html.ValidationMessageFor(model => model.UserAgreementText) +
        + @Html.SmartLabelFor(model => model.HasSampleDownload) + + @Html.CheckBoxFor(model => model.HasSampleDownload, new { data_toggler_for = "#pnlSampleDownloadFile" }) + @Html.ValidationMessageFor(model => model.HasSampleDownload) +
        + @Html.SmartLabelFor(model => model.SampleDownloadId) + + @Html.EditorFor(model => model.SampleDownloadId) + @Html.ValidationMessageFor(model => model.SampleDownloadId) +
        + + + +@{Html.SmartStore().Window() + .Name("add-changelog-window") + .Size(WindowSize.Large) + .Title(T("Admin.Catalog.Products.Download.AddChangelog")) + .Content( + @ +
        + +
        + @Html.EditorFor(model => model.AddChangelog) + @Html.ValidationMessageFor(model => model.AddChangelog) +
        +
        +
        ) + .FooterContent(@ + + + ) + .Render(); +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml index 8d295edfb2..81994c52ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml @@ -2,58 +2,17 @@ @using SmartStore.Core.Domain.Catalog; -@{ - var availableTags = Model.AvailableProductTags.Select(x => x.Value).ToArray(); - for (int i = 0; i < availableTags.Length; i++) - { - availableTags[i] = availableTags[i].EncodeJsString(); - } - ViewBag.SerializedTags = String.Join(",", availableTags); -} - - -
        +
        @if (Model.Id != 0) { @@ -213,7 +51,7 @@ @Html.SmartLabelFor(model => model.Id) @@ -222,7 +60,9 @@ @Html.SmartLabelFor(model => model.ProductUrl) } @@ -251,11 +91,13 @@ @Html.SmartLabelFor(model => model.AssociatedToProductId) } - + @@ -275,7 +117,10 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) @@ -284,7 +129,7 @@ @Html.SmartLabelFor(model => model.Locales[item].ShortDescription) @@ -292,16 +137,11 @@ - - - -
        - @Html.DisplayFor(model => model.Id) + @Html.TextBoxFor(model => model.Id, new { @readonly = "readonly", @class = "form-control-plaintext" }) @Html.ValidationMessageFor(model => model.Id)
        - @Model.ProductUrl +
        - @Html.ActionLink(Model.AssociatedToProductName, "Edit", "Product", new { id = Model.AssociatedToProductId }, new { }) +
        + @Html.ActionLink(Model.AssociatedToProductName, "Edit", "Product", new { id = Model.AssociatedToProductId }, new { }) +
        @Html.SmartLabelFor(model => model.ProductTemplateId) - @Html.TextBoxFor(model => Model.Locales[item].Name, new { @class = "input-large" }) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextBoxFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name)
        - @Html.TextAreaFor(model => model.Locales[item].ShortDescription, new { @class = "input-large" }) + @Html.TextAreaFor(model => model.Locales[item].ShortDescription) @Html.ValidationMessageFor(model => model.Locales[item].ShortDescription)
        @Html.SmartLabelFor(model => model.Locales[item].FullDescription) - @Html.EditorFor(model => model.Locales[item].FullDescription, Html.RichEditorFlavor()) + + @Html.EditorFor(model => model.Locales[item].FullDescription, "Html") @Html.ValidationMessageFor(model => model.Locales[item].FullDescription)
        - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
        , @ @@ -310,7 +150,7 @@ @Html.SmartLabelFor(model => model.Name) @@ -319,7 +159,7 @@ @Html.SmartLabelFor(model => model.ShortDescription) @@ -327,13 +167,13 @@ -
        - @Html.TextBoxFor(model => model.Name, new { @class = "input-large" }) + @Html.TextBoxFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name)
        - @Html.TextAreaFor(x => x.ShortDescription, new { @class = "input-large" }) + @Html.TextAreaFor(x => x.ShortDescription) @Html.ValidationMessageFor(model => model.ShortDescription)
        @Html.SmartLabelFor(model => model.FullDescription) - @Html.EditorFor(x => x.FullDescription, Html.RichEditorFlavor()) + + @Html.EditorFor(x => x.FullDescription, "Html") @Html.ValidationMessageFor(model => model.FullDescription)
        - )) + )) @@ -363,6 +203,24 @@ @Html.ValidationMessageFor(model => model.Gtin) + + + + + + + + @@ -395,7 +253,7 @@ @Html.SmartLabelFor(model => model.AvailableEndDateTimeUtc) @@ -413,7 +271,7 @@ @Html.SmartLabelFor(model => model.ShowOnHomePage) @@ -442,7 +300,9 @@ @Html.SmartLabelFor(model => model.CreatedOn) } @@ -453,7 +313,9 @@ @Html.SmartLabelFor(model => model.UpdatedOn) } @@ -470,33 +332,43 @@ @Html.SmartLabelFor(model => model.RequireOtherProducts) - - - - - - - - + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.CustomsTariffNumber) + + @Html.EditorFor(model => model.CustomsTariffNumber) + @Html.ValidationMessageFor(model => model.CustomsTariffNumber) +
        + @Html.SmartLabelFor(model => model.CountryOfOriginId) + + @Html.DropDownListFor(model => model.CountryOfOriginId, Model.AvailableCountries, T("Common.Unspecified")) + @Html.ValidationMessageFor(model => model.CountryOfOriginId) +
        @Html.SmartLabelFor(model => model.AdminComment) @@ -377,7 +235,7 @@ @Html.SmartLabelFor(model => model.ProductTags) - @Html.HiddenFor(model => model.ProductTags, new { @class = "tag-chooser" }) + @Html.ListBoxFor(model => model.ProductTags, Model.AvailableProductTags, new { multiple = "multiple", data_tags = "true" }) @Html.ValidationMessageFor(model => model.ProductTags)
        - @Html.EditorFor(model => model.AvailableEndDateTimeUtc) + @Html.EditorFor(model => model.AvailableEndDateTimeUtc, new { pickTime = true }) @Html.ValidationMessageFor(model => model.AvailableEndDateTimeUtc)
        - @Html.EditorFor(model => model.ShowOnHomePage) + @Html.CheckBoxFor(model => model.ShowOnHomePage, new { data_toggler_for = "#pnlHomePageDisplayOrder" }) @Html.ValidationMessageFor(model => model.ShowOnHomePage)
        - @Html.DisplayFor(model => model.CreatedOn) +
        + @Html.DisplayFor(model => model.CreatedOn) +
        - @Html.DisplayFor(model => model.UpdatedOn) +
        + @Html.DisplayFor(model => model.UpdatedOn) +
        - @Html.EditorFor(model => model.RequireOtherProducts) + @Html.CheckBoxFor(model => model.RequireOtherProducts, new { data_toggler_for = "#pnlRequireOtherProducts" }) @Html.ValidationMessageFor(model => model.RequireOtherProducts)
        - @Html.SmartLabelFor(model => model.RequiredProductIds) - - @Html.EditorFor(model => model.RequiredProductIds) - - - - @Html.ValidationMessageFor(model => model.RequiredProductIds) -
        - @Html.SmartLabelFor(model => model.AutomaticallyAddRequiredProducts) - - @Html.EditorFor(model => model.AutomaticallyAddRequiredProducts) - @Html.ValidationMessageFor(model => model.AutomaticallyAddRequiredProducts) -
        + @Html.SmartLabelFor(model => model.RequiredProductIds) + +
        + @Html.TextBoxFor(model => model.RequiredProductIds, new { @readonly = "readonly" }) + + + + + + + @(Html.SmartStore().EntityPicker() + .For(x => x.RequiredProductIds) + .DialogTitle(T("Admin.Catalog.Products.Fields.RequireOtherProducts").Text.EncodeJsString('\'', false)) + .Caption(T("Admin.Common.Search").Text.EncodeJsString('\'', false))) + +
        + @Html.ValidationMessageFor(model => model.RequiredProductIds) +
        + @Html.SmartLabelFor(model => model.AutomaticallyAddRequiredProducts) + + @Html.EditorFor(model => model.AutomaticallyAddRequiredProducts) + @Html.ValidationMessageFor(model => model.AutomaticallyAddRequiredProducts) +
        @@ -510,7 +382,7 @@ @Html.SmartLabelFor(model => model.IsGiftCard) @@ -525,104 +397,6 @@
        - @Html.EditorFor(model => model.IsGiftCard) + @Html.CheckBoxFor(model => model.IsGiftCard, new { data_toggler_for = "#pnlGiftCardType" }) @Html.ValidationMessageFor(model => model.IsGiftCard)
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        - @Html.SmartLabelFor(model => model.IsDownload) - - @Html.EditorFor(model => model.IsDownload) - @Html.ValidationMessageFor(model => model.IsDownload) -
        - @Html.SmartLabelFor(model => model.DownloadId) - - @Html.EditorFor(model => model.DownloadId) - @Html.ValidationMessageFor(model => model.DownloadId) -
        - @Html.SmartLabelFor(model => model.UnlimitedDownloads) - - @Html.EditorFor(model => model.UnlimitedDownloads) - @Html.ValidationMessageFor(model => model.UnlimitedDownloads) -
        - @Html.SmartLabelFor(model => model.MaxNumberOfDownloads) - - @Html.EditorFor(model => model.MaxNumberOfDownloads) - @Html.ValidationMessageFor(model => model.MaxNumberOfDownloads) -
        - @Html.SmartLabelFor(model => model.DownloadExpirationDays) - - @Html.TextBoxFor(model => model.DownloadExpirationDays) - @Html.ValidationMessageFor(model => model.DownloadExpirationDays) -
        - @Html.SmartLabelFor(model => model.DownloadActivationTypeId) - - @Html.DropDownListFor(model => model.DownloadActivationTypeId, ((DownloadActivationType)Model.DownloadActivationTypeId).ToSelectList()) - @Html.ValidationMessageFor(model => model.DownloadActivationTypeId) -
        - @Html.SmartLabelFor(model => model.HasUserAgreement) - - @Html.EditorFor(model => model.HasUserAgreement) - @Html.ValidationMessageFor(model => model.HasUserAgreement) -
        - @Html.SmartLabelFor(model => model.UserAgreementText) - - @Html.EditorFor(model => model.UserAgreementText, Html.RichEditorFlavor()) - @Html.ValidationMessageFor(model => model.UserAgreementText) -
        - @Html.SmartLabelFor(model => model.HasSampleDownload) - - @Html.EditorFor(model => model.HasSampleDownload) - @Html.ValidationMessageFor(model => model.HasSampleDownload) -
        - @Html.SmartLabelFor(model => model.SampleDownloadId) - - @Html.EditorFor(model => model.SampleDownloadId) - @Html.ValidationMessageFor(model => model.SampleDownloadId) -
        - - - - - - - - - - - - - + + + + + + + + + + + + + +
        @@ -634,37 +408,39 @@ @Html.SmartLabelFor(model => model.IsRecurring) - @Html.EditorFor(model => model.IsRecurring) + @Html.CheckBoxFor(model => model.IsRecurring, new { data_toggler_for = "#pnlIsRecurring" }) @Html.ValidationMessageFor(model => model.IsRecurring)
        - @Html.SmartLabelFor(model => model.RecurringCycleLength) - - @Html.EditorFor(model => model.RecurringCycleLength) - @Html.ValidationMessageFor(model => model.RecurringCycleLength) -
        - @Html.SmartLabelFor(model => model.RecurringCyclePeriodId) - - @Html.DropDownListFor(model => model.RecurringCyclePeriodId, ((RecurringProductCyclePeriod)Model.RecurringCyclePeriodId).ToSelectList()) - @Html.ValidationMessageFor(model => model.RecurringCyclePeriodId) -
        - @Html.SmartLabelFor(model => model.RecurringTotalCycles) - - @Html.EditorFor(model => model.RecurringTotalCycles) - @Html.ValidationMessageFor(model => model.RecurringTotalCycles) -
        + @Html.SmartLabelFor(model => model.RecurringCycleLength) + + @Html.EditorFor(model => model.RecurringCycleLength) + @Html.ValidationMessageFor(model => model.RecurringCycleLength) +
        + @Html.SmartLabelFor(model => model.RecurringCyclePeriodId) + + @Html.DropDownListFor(model => model.RecurringCyclePeriodId, ((RecurringProductCyclePeriod)Model.RecurringCyclePeriodId).ToSelectList()) + @Html.ValidationMessageFor(model => model.RecurringCyclePeriodId) +
        + @Html.SmartLabelFor(model => model.RecurringTotalCycles) + + @Html.EditorFor(model => model.RecurringTotalCycles) + @Html.ValidationMessageFor(model => model.RecurringTotalCycles) +
        @@ -678,82 +454,84 @@ @Html.SmartLabelFor(model => model.IsShipEnabled) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        - @Html.EditorFor(model => model.IsShipEnabled) + @Html.CheckBoxFor(model => model.IsShipEnabled, new { data_toggler_for = "#pnlIsShipEnabled" }) @Html.ValidationMessageFor(model => model.IsShipEnabled)
        - @Html.SmartLabelFor(model => model.DeliveryTimeId) - - @Html.DropDownListFor(model => model.DeliveryTimeId, Model.AvailableDeliveryTimes, T("Common.Unspecified")) - @Html.ValidationMessageFor(model => model.DeliveryTimeId) -
        - @Html.SmartLabelFor(model => model.IsFreeShipping) - - @Html.EditorFor(model => model.IsFreeShipping) - @Html.ValidationMessageFor(model => model.IsFreeShipping) -
        - @Html.SmartLabelFor(model => model.AdditionalShippingCharge) - - @Html.EditorFor(model => model.AdditionalShippingCharge) @Model.PrimaryStoreCurrencyCode - @Html.ValidationMessageFor(model => model.AdditionalShippingCharge) -
        - @Html.SmartLabelFor(model => model.QuantityUnitId) - - @Html.DropDownListFor(model => model.QuantityUnitId, Model.AvailableQuantityUnits, T("Common.Unspecified")) - @Html.ValidationMessageFor(model => model.QuantityUnitId) -
        - @Html.SmartLabelFor(model => model.Weight) - - @Html.EditorFor(model => model.Weight) @Model.BaseWeightIn - @Html.ValidationMessageFor(model => model.Weight) -
        - @Html.SmartLabelFor(model => model.Length) - - @Html.EditorFor(model => model.Length) @Model.BaseDimensionIn - @Html.ValidationMessageFor(model => model.Length) -
        - @Html.SmartLabelFor(model => model.Width) - - @Html.EditorFor(model => model.Width) @Model.BaseDimensionIn - @Html.ValidationMessageFor(model => model.Width) -
        - @Html.SmartLabelFor(model => model.Height) - - @Html.EditorFor(model => model.Height) @Model.BaseDimensionIn - @Html.ValidationMessageFor(model => model.Height) -
        + @Html.SmartLabelFor(model => model.DeliveryTimeId) + + @Html.DropDownListFor(model => model.DeliveryTimeId, Model.AvailableDeliveryTimes, T("Common.Unspecified")) + @Html.ValidationMessageFor(model => model.DeliveryTimeId) +
        + @Html.SmartLabelFor(model => model.IsFreeShipping) + + @Html.EditorFor(model => model.IsFreeShipping) + @Html.ValidationMessageFor(model => model.IsFreeShipping) +
        + @Html.SmartLabelFor(model => model.AdditionalShippingCharge) + + @Html.EditorFor(model => model.AdditionalShippingCharge, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.AdditionalShippingCharge) +
        + @Html.SmartLabelFor(model => model.QuantityUnitId) + + @Html.DropDownListFor(model => model.QuantityUnitId, Model.AvailableQuantityUnits, T("Common.Unspecified")) + @Html.ValidationMessageFor(model => model.QuantityUnitId) +
        + @Html.SmartLabelFor(model => model.Weight) + + @Html.EditorFor(model => model.Weight, new { postfix = Model.BaseWeightIn }) + @Html.ValidationMessageFor(model => model.Weight) +
        + @Html.SmartLabelFor(model => model.Length) + + @Html.EditorFor(model => model.Length, new { postfix = Model.BaseDimensionIn }) + @Html.ValidationMessageFor(model => model.Length) +
        + @Html.SmartLabelFor(model => model.Width) + + @Html.EditorFor(model => model.Width, new { postfix = Model.BaseDimensionIn }) + @Html.ValidationMessageFor(model => model.Width) +
        + @Html.SmartLabelFor(model => model.Height) + + @Html.EditorFor(model => model.Height, new { postfix = Model.BaseDimensionIn }) + @Html.ValidationMessageFor(model => model.Height) +
        @@ -767,34 +545,29 @@ @Html.SmartLabelFor(model => model.IsTaxExempt) - - - - - - - - + + + + + + + + + +
        - @Html.EditorFor(model => model.IsTaxExempt) + @Html.CheckBoxFor(model => model.IsTaxExempt, new { data_toggler_for = "#pnlIsTaxExempt", data_toggler_reverse = "true" }) @Html.ValidationMessageFor(model => model.IsTaxExempt)
        - @Html.SmartLabelFor(model => model.TaxCategoryId) - - @Html.DropDownListFor(model => model.TaxCategoryId, Model.AvailableTaxCategories, new { placeholder = T("Common.PleaseSelect").Text }) - @Html.ValidationMessageFor(model => model.TaxCategoryId) -
        - @Html.SmartLabelFor(model => model.IsEsd) - - @Html.EditorFor(model => model.IsEsd) - @Html.ValidationMessageFor(model => model.IsEsd) -
        + @Html.SmartLabelFor(model => model.TaxCategoryId) + + @Html.DropDownListFor(model => model.TaxCategoryId, Model.AvailableTaxCategories) + @Html.ValidationMessageFor(model => model.TaxCategoryId) +
        + @Html.SmartLabelFor(model => model.IsEsd) + + @Html.EditorFor(model => model.IsEsd) + @Html.ValidationMessageFor(model => model.IsEsd) +
        - -
        \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Inventory.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Inventory.cshtml index 8b85adfb67..f564238252 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Inventory.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Inventory.cshtml @@ -7,7 +7,6 @@ $("#@Html.FieldIdFor(model => model.ManageInventoryMethodId)").change(toggleManageStock); $("#@Html.FieldIdFor(model => model.BackorderModeId)").change(toggleManageStock); - $("#@Html.FieldIdFor(model => model.DisplayStockAvailability)").click(toggleManageStock); toggleManageStock(); @@ -103,7 +102,7 @@ @Html.SmartLabelFor(model => model.DisplayStockAvailability) - @Html.EditorFor(model => model.DisplayStockAvailability) + @Html.CheckBoxFor(model => model.DisplayStockAvailability, new { data_toggler_for = "#pnlDisplayStockQuantity" }) @Html.ValidationMessageFor(model => model.DisplayStockAvailability) @@ -179,4 +178,35 @@ @Html.ValidationMessageFor(model => model.OrderMaximumQuantity) + + + + @Html.SmartLabelFor(model => model.QuantityStep) + + + @Html.EditorFor(model => model.QuantityStep) + @Html.ValidationMessageFor(model => model.QuantityStep) + + + + + @Html.SmartLabelFor(model => model.HideQuantityControl) + + + @Html.EditorFor(model => model.HideQuantityControl) + @Html.ValidationMessageFor(model => model.HideQuantityControl) + + + + @* TODO: display when dropdown was implemented *@ + @* Can be hidden if HideQuantityControl == true *@ + + + @Html.SmartLabelFor(model => model.QuantiyControlType) + + + @Html.EnumSettingEditorFor(model => model.QuantiyControlType) + @Html.ValidationMessageFor(model => model.QuantiyControlType) + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Manufacturers.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Manufacturers.cshtml index e06230c752..6f7c88cd84 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Manufacturers.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Manufacturers.cshtml @@ -8,7 +8,13 @@ { @(Html.Telerik().Grid() - .Name("productmanufacturers-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("ProductManufacturerList", "Product", new { productId = Model.Id }) - .Insert("ProductManufacturerInsert", "Product", new { productId = Model.Id }) - .Update("ProductManufacturerUpdate", "Product") - .Delete("ProductManufacturerDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.Manufacturer) + .Name("productmanufacturers-grid") + .DataKeys(keys => { keys.Add(x => x.Id); }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductManufacturerList", "Product", new { productId = Model.Id }) + .Insert("ProductManufacturerInsert", "Product", new { productId = Model.Id }) + .Update("ProductManufacturerUpdate", "Product") + .Delete("ProductManufacturerDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.Manufacturer) + .Width("60%") .Template(x => Html.ActionLink(x.Manufacturer, "Edit", "Manufacturer", new { id = x.ManufacturerId })) .ClientTemplate("\"><#= Manufacturer #>"); - columns.Bound(x => x.IsFeaturedProduct) - .Width(100) - .Template(item => Html.SymbolForBool(item.IsFeaturedProduct)) - .ClientTemplate(Html.SymbolForBool("IsFeaturedProduct")) - .Centered(); - columns.Bound(x => x.DisplayOrder) + columns.Bound(x => x.IsFeaturedProduct) + .Width(100) + .Template(item => Html.SymbolForBool(item.IsFeaturedProduct)) + .ClientTemplate(Html.SymbolForBool("IsFeaturedProduct")) + .Centered(); + columns.Bound(x => x.DisplayOrder) .Centered() - .Width(100); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }) - .Width(180); - }) - .ToolBar(commands => commands.Insert()) - .ClientEvents(events => + .Width(100); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Insert()) + .ClientEvents(events => { events.OnEdit("onProductManufacturerEdit"); events.OnDataBound("onProductManufacturerDataBound"); }) - .EnableCustomBinding(true)) + .EnableCustomBinding(true)) } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml index a8e66f1e74..5e47dfab18 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml @@ -1,57 +1,19 @@ @model ProductModel - @using Telerik.Web.Mvc.UI; @if (Model.Id > 0) { - - - + - +
        - - - - - - - - - + + + + + + + + + + + + + + + + - -
        -
        @T("Admin.Catalog.Products.Pictures.AddNew")
        +
        @T("Common.Options")
        - @Html.SmartLabelFor(model => model.AddPictureModel.PictureId) - - @Html.EditorFor(model => model.AddPictureModel.PictureId, new { transientUpload = true }) - @Html.ValidationMessageFor(model => model.AddPictureModel.PictureId) -
        - @Html.SmartLabelFor(model => model.AddPictureModel.DisplayOrder) - - @Html.EditorFor(model => model.AddPictureModel.DisplayOrder) - @Html.ValidationMessageFor(model => model.AddPictureModel.DisplayOrder) -
        + @Html.SmartLabelFor(model => model.HasPreviewPicture) + + @Html.EditorFor(model => model.HasPreviewPicture) + @Html.ValidationMessageFor(model => model.HasPreviewPicture) +
        +
        +
        @T("Admin.Catalog.Products.Pictures.AddNew")
        +
        +
        + @Html.SmartLabelFor(model => model.AddPictureModel.PictureId) + + @Html.EditorFor(model => model.AddPictureModel.PictureId, new { transientUpload = true }) + @Html.ValidationMessageFor(model => model.AddPictureModel.PictureId) +
        + @Html.SmartLabelFor(model => model.AddPictureModel.DisplayOrder) + + @Html.EditorFor(model => model.AddPictureModel.DisplayOrder) + @Html.ValidationMessageFor(model => model.AddPictureModel.DisplayOrder) +
          -
        + + + +
        + @(Html.Telerik().Grid() + .Name("productpictures-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.PictureUrl) + .Width(400) + .Centered() + .ClientTemplate("<#= PictureId #>") + .ReadOnly(); + columns.Bound(x => x.DisplayOrder).Width(100).Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }).Width(180).HtmlAttributes(new { align = "right" }); + + }) + .Editable(x => + { + x.Mode(GridEditMode.InLine); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("ProductPictureList", "Product", new { productId = Model.Id }) + .Update("ProductPictureUpdate", "Product", new { productId = Model.Id }) + .Delete("ProductPictureDelete", "Product", new { productId = Model.Id }); + }) + .EnableCustomBinding(true)) +
        } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml index bebc6fa0c7..d1b5f910b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Price.cshtml @@ -8,9 +8,6 @@ var fieldBasePriceBaseAmount; $(function () { - $("#@Html.FieldIdFor(model => model.CustomerEntersPrice)").click(toggleCustomerEntersPrice); - $("#@Html.FieldIdFor(model => model.BasePriceEnabled)").click(toggleBasePrice); - fieldBasePriceMeasureUnit = $("#@Html.FieldIdFor(model => model.BasePriceMeasureUnit)"); fieldBasePriceAmount = $("#@Html.FieldIdFor(model => model.BasePriceAmount)"); fieldBasePriceBaseAmount = $("#@Html.FieldIdFor(model => model.BasePriceBaseAmount)"); @@ -19,13 +16,11 @@ fieldBasePriceAmount.on("blur", getCurrentBasePrice); fieldBasePriceBaseAmount.on("blur", getCurrentBasePrice); - toggleCustomerEntersPrice(); getCurrentBasePrice(); - toggleBasePrice(); function getCurrentBasePrice() { - var basePriceAmount = Globalize.parseFloat(fieldBasePriceAmount.val()); - var basePriceBaseAmount = Globalize.parseInt(fieldBasePriceBaseAmount.val()); + var basePriceAmount = SmartStore.globalization.parseFloat(fieldBasePriceAmount.val()); + var basePriceBaseAmount = SmartStore.globalization.parseInt(fieldBasePriceBaseAmount.val()); if (basePriceAmount > 0 && basePriceBaseAmount > 0) { $.ajax({ cache:false, @@ -47,253 +42,247 @@ } return false; } - - function toggleBasePrice() { - if ($('#@Html.FieldIdFor(model => model.BasePriceEnabled)').is(':checked')) { - $('#pnlBasePriceBaseAmount').show(); - $('#pnlBasePriceAmount').show(); - $('#pnlBasePriceAmountHint').show(); - } - else { - $('#pnlBasePriceBaseAmount').hide(); - $('#pnlBasePriceAmount').hide(); - $('#pnlBasePriceAmountHint').hide(); - } - } - - function toggleCustomerEntersPrice() { - var src = $('#@Html.FieldIdFor(model => model.CustomerEntersPrice)'); - if (src.is(':checked')) { - $('#pnlMinimumCustomerEnteredPrice').show(); - $('#pnlMaximumCustomerEnteredPrice').show(); - } - else { - $('#pnlMinimumCustomerEnteredPrice').hide(); - $('#pnlMaximumCustomerEnteredPrice').hide(); - } - } })
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +
        +
        +
        + @Html.SmartLabelFor(model => model.MinimumCustomerEnteredPrice) +
        +
        + @Html.EditorFor(model => model.MinimumCustomerEnteredPrice, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.MinimumCustomerEnteredPrice) +
        +
        +
        +
        + @Html.SmartLabelFor(model => model.MaximumCustomerEnteredPrice) +
        +
        + @Html.EditorFor(model => model.MaximumCustomerEnteredPrice, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.MaximumCustomerEnteredPrice) +
        +
        +
        +
        +
        +
        - - - - - - - - - - - - - - - - - -
        +
        +
        +
        @Html.SmartLabelFor(model => model.Price) -
        - @Html.EditorFor(model => model.Price) @Model.PrimaryStoreCurrencyCode + +
        + @Html.EditorFor(model => model.Price, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.Price) -
        + + +
        +
        @Html.SmartLabelFor(model => model.OldPrice) -
        - @Html.EditorFor(model => model.OldPrice) @Model.PrimaryStoreCurrencyCode + +
        + @Html.EditorFor(model => model.OldPrice, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.OldPrice) -
        + + +
        +
        @Html.SmartLabelFor(model => model.ProductCost) -
        - @Html.EditorFor(model => model.ProductCost) @Model.PrimaryStoreCurrencyCode + +
        + @Html.EditorFor(model => model.ProductCost, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.ProductCost) -
        + + +
        +
        @Html.SmartLabelFor(model => model.SpecialPrice) -
        - @Html.EditorFor(model => model.SpecialPrice) @Model.PrimaryStoreCurrencyCode + +
        + @Html.EditorFor(model => model.SpecialPrice, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.SpecialPrice) -
        + + +
        +
        @Html.SmartLabelFor(model => model.SpecialPriceStartDateTimeUtc) -
        + +
        @Html.EditorFor(model => model.SpecialPriceStartDateTimeUtc, new { pickTime = true }) @Html.ValidationMessageFor(model => model.SpecialPriceStartDateTimeUtc) -
        + + +
        +
        @Html.SmartLabelFor(model => model.SpecialPriceEndDateTimeUtc) -
        + +
        @Html.EditorFor(model => model.SpecialPriceEndDateTimeUtc, new { pickTime = true }) @Html.ValidationMessageFor(model => model.SpecialPriceEndDateTimeUtc) -
        + + +
        +
        @Html.SmartLabelFor(model => model.DisableBuyButton) -
        + +
        @Html.EditorFor(model => model.DisableBuyButton) @Html.ValidationMessageFor(model => model.DisableBuyButton) -
        + + +
        +
        @Html.SmartLabelFor(model => model.DisableWishlistButton) -
        + +
        @Html.EditorFor(model => model.DisableWishlistButton) @Html.ValidationMessageFor(model => model.DisableWishlistButton) -
        + + +
        +
        @Html.SmartLabelFor(model => model.AvailableForPreOrder) -
        + +
        @Html.EditorFor(model => model.AvailableForPreOrder) @Html.ValidationMessageFor(model => model.AvailableForPreOrder) -
        + + +
        +
        @Html.SmartLabelFor(model => model.CallForPrice) -
        + +
        @Html.EditorFor(model => model.CallForPrice) @Html.ValidationMessageFor(model => model.CallForPrice) -
        + + +
        +
        @Html.SmartLabelFor(model => model.CustomerEntersPrice) -
        - @Html.EditorFor(model => model.CustomerEntersPrice) + +
        + @Html.CheckBoxFor(model => model.CustomerEntersPrice, new { data_toggler_for = "#pnlCustomerEntersPrice" }) @Html.ValidationMessageFor(model => model.CustomerEntersPrice) -
        - @Html.SmartLabelFor(model => model.MinimumCustomerEnteredPrice) - - @Html.EditorFor(model => model.MinimumCustomerEnteredPrice) @Model.PrimaryStoreCurrencyCode - @Html.ValidationMessageFor(model => model.MinimumCustomerEnteredPrice) -
        - @Html.SmartLabelFor(model => model.MaximumCustomerEnteredPrice) - - @Html.EditorFor(model => model.MaximumCustomerEnteredPrice) @Model.PrimaryStoreCurrencyCode - @Html.ValidationMessageFor(model => model.MaximumCustomerEnteredPrice) -
        -
        -
        +
        +
        @Html.SmartLabelFor(model => model.BasePriceEnabled) -
        - @Html.EditorFor(model => model.BasePriceEnabled) + +
        + @Html.CheckBoxFor(model => model.BasePriceEnabled, new { data_toggler_for = "#pnlBasePriceEnabled" }) @Html.ValidationMessageFor(model => model.BasePriceEnabled) -
        - @Html.SmartLabelFor(model => model.BasePriceBaseAmount) - - @Html.EditorFor(model => model.BasePriceBaseAmount, new { small = true }) - @Html.DropDownListFor(model => model.BasePriceMeasureUnit, Model.AvailableMeasureUnits, new { @class = "autowidth", style = "width:100px" }) -
        - @Html.SmartLabelFor(model => model.BasePriceAmount) - - @Html.EditorFor(model => model.BasePriceAmount, new { small = true }) - @*@T("Admin.Catalog.Products.Fields.BasePriceInfo")*@ -
        -   - -
        - @T("Admin.Products.BasePrice.Hint") -
        -
        +
        +
        +
        +
        +
        + @Html.SmartLabelFor(model => model.BasePriceBaseAmount) +
        +
        +
        +
        + @Html.EditorFor(model => model.BasePriceBaseAmount) +
        +
        + @Html.DropDownListFor(model => model.BasePriceMeasureUnit, Model.AvailableMeasureUnits) +
        +
        +
        +
        +
        +
        + @Html.SmartLabelFor(model => model.BasePriceAmount) +
        +
        + @Html.EditorFor(model => model.BasePriceAmount) + @*@T("Admin.Catalog.Products.Fields.BasePriceInfo")*@ +
        +
        +
        +
        +   +
        +
        +
        + @T("Admin.Products.BasePrice.Hint") +
        +
        +
        +
        +
        -
        -

        @T("Admin.Catalog.Products.TierPrices")

        +
        +

        @T("Admin.Catalog.Products.TierPrices")

        @if (Model.Id > 0) { @(Html.Telerik().Grid() - .Name("tierprices-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("TierPriceList", "Product", new { productId = Model.Id }) - .Insert("TierPriceInsert", "Product", new { productId = Model.Id }) - .Update("TierPriceUpdate", "Product") - .Delete("TierPriceDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.Quantity) - .Centered() - .Width(120); - columns.Bound(x => x.Price1) - .Format("{0:0.00}") - .Width(120) - .RightAlign(); - columns.Bound(x => x.Store); - columns.Bound(x => x.CustomerRole); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }) - .Width(180); - }) - .ToolBar(commands => commands.Insert()) - .ClientEvents(events => events.OnEdit("onTierPriceEdit")) - .EnableCustomBinding(true)) - - + .Name("tierprices-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("TierPriceList", "Product", new { productId = Model.Id }) + .Insert("TierPriceInsert", "Product", new { productId = Model.Id }) + .Update("TierPriceUpdate", "Product") + .Delete("TierPriceDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.Quantity) + .Centered() + .Width(80); + columns.Bound(x => x.Price1) + .Format("{0:0.00}") + .Width(120) + .RightAlign(); + columns.Bound(x => x.CalculationMethod).Width("20%"); + columns.Bound(x => x.Store).Width("25%"); + columns.Bound(x => x.CustomerRole).Width("20%"); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + //.Width(180) + .HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Insert()) + .ClientEvents(events => events.OnEdit("onTierPriceEdit")) + .EnableCustomBinding(true)) } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Promotion.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Promotion.cshtml index cdeda617b9..99771f4d29 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Promotion.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Promotion.cshtml @@ -2,65 +2,50 @@ @using Telerik.Web.Mvc.UI;
        -

        @T("Admin.Catalog.Products.RelatedProducts")

        +

        @T("Admin.Catalog.Products.RelatedProducts")

        @if (Model.Id > 0) { - - - - - - - -
        - @(Html.Telerik().Grid() - .Name("relatedproducts-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("RelatedProductList", "Product", new { productId = Model.Id }) - .Update("RelatedProductUpdate", "Product") - .Delete("RelatedProductDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.Product2Name) - .ReadOnly() - .Width(520) - .ClientTemplate(@Html.LabeledProductName("ProductId2", "Product2Name")); - columns.Bound(x => x.Product2Sku) - .ReadOnly(); - columns.Bound(x => x.Product2Published) - .ReadOnly() - .ClientTemplate(@Html.SymbolForBool("Product2Published")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Centered(); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(220); - }) - .EnableCustomBinding(true)) - -
        - - - -
        +
        + @(Html.Telerik().Grid() + .Name("relatedproducts-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("RelatedProductList", "Product", new { productId = Model.Id }) + .Update("RelatedProductUpdate", "Product") + .Delete("RelatedProductDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.Product2Name) + .ReadOnly() + .Width("60%") + .ClientTemplate(@Html.LabeledProductName("ProductId2", "Product2Name")); + columns.Bound(x => x.Product2Sku) + .Width(150) + .ReadOnly(); + columns.Bound(x => x.Product2Published) + .ReadOnly() + .Width(100) + .ClientTemplate(@Html.SymbolForBool("Product2Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(100) + .Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }).HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Template(CrossSellingGridCommands)) + .EnableCustomBinding(true)) +
        } else { @@ -70,61 +55,42 @@ }
        -

         

        - -
        -

        @T("Admin.Catalog.Products.CrossSells")

        +
        +

        @T("Admin.Catalog.Products.CrossSells")

        @if (Model.Id > 0) { - - - - - - - -
        - @(Html.Telerik().Grid() - .Name("crosssellproducts-grid") - .DataKeys(keys => - { - keys.Add(x => x.Id); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax() - .Select("CrossSellProductList", "Product", new { productId = Model.Id }) - .Delete("CrossSellProductDelete", "Product"); - }) - .Columns(columns => - { - columns.Bound(x => x.Product2Name) - .ReadOnly() - .Width(520) - .ClientTemplate(@Html.LabeledProductName("ProductId2", "Product2Name")); - columns.Bound(x => x.Product2Sku); - columns.Bound(x => x.Product2Published) - .ClientTemplate(@Html.SymbolForBool("Product2Published")) - .Centered(); - columns.Command(commands => - { - commands.Delete().Localize(T); - }).Width(220); - }) - .EnableCustomBinding(true)) -
        - - - -
        +
        + @(Html.Telerik().Grid() + .Name("crosssellproducts-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("CrossSellProductList", "Product", new { productId = Model.Id }) + .Delete("CrossSellProductDelete", "Product"); + }) + .Columns(columns => + { + columns.Bound(x => x.Product2Name) + .ReadOnly() + .Width("60%") + .ClientTemplate(@Html.LabeledProductName("ProductId2", "Product2Name")); + columns.Bound(x => x.Product2Sku).Width(150); + columns.Bound(x => x.Product2Published).Width(100) + .ClientTemplate(@Html.SymbolForBool("Product2Published")) + .Centered(); + columns.Command(commands => + { + commands.Delete().Localize(T); + }).HtmlAttributes(new { align = "right" }); + }) + .ToolBar(commands => commands.Template(CheckoutSellingGridCommands)) + .EnableCustomBinding(true)) +
        } else { @@ -134,8 +100,63 @@ }
        - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml index 985ff1f3bb..3a9d1e476a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml @@ -7,7 +7,10 @@ @Html.SmartLabelFor(model => model.Locales[item].MetaKeywords) - @Html.TextBoxFor(model => model.Locales[item].MetaKeywords, new { @class = "input-large" }) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextBoxFor(model => model.Locales[item].MetaKeywords) @Html.ValidationMessageFor(model => model.Locales[item].MetaKeywords) @@ -16,7 +19,7 @@ @Html.SmartLabelFor(model => model.Locales[item].MetaDescription) - @Html.TextAreaFor(model => model.Locales[item].MetaDescription, new { @class = "input-large" }) + @Html.TextAreaFor(model => model.Locales[item].MetaDescription) @Html.ValidationMessageFor(model => model.Locales[item].MetaDescription) @@ -25,7 +28,7 @@ @Html.SmartLabelFor(model => model.Locales[item].MetaTitle) - @Html.TextAreaFor(model => model.Locales[item].MetaTitle, new { @class = "input-large" }) + @Html.TextAreaFor(model => model.Locales[item].MetaTitle) @Html.ValidationMessageFor(model => model.Locales[item].MetaTitle) @@ -34,15 +37,10 @@ @Html.SmartLabelFor(model => model.Locales[item].SeName) - @Html.TextAreaFor(model => model.Locales[item].SeName, new { @class = "input-large" }) + @Html.EditorFor(model => model.Locales[item].SeName) @Html.ValidationMessageFor(model => model.Locales[item].SeName) - - - @Html.HiddenFor(model => model.Locales[item].LanguageId) - - , @ @@ -51,7 +49,7 @@ @Html.SmartLabelFor(model => model.MetaKeywords) @@ -60,7 +58,7 @@ @Html.SmartLabelFor(model => model.MetaDescription) @@ -69,7 +67,7 @@ @Html.SmartLabelFor(model => model.MetaTitle) @@ -78,11 +76,11 @@ @Html.SmartLabelFor(model => model.SeName) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml index 87982efc85..e89bcb5e66 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml @@ -11,12 +11,12 @@ { -
        - @Html.TextBoxFor(x => x.MetaKeywords, new { @class = "input-large" }) + @Html.TextBoxFor(x => x.MetaKeywords) @Html.ValidationMessageFor(model => model.MetaKeywords)
        - @Html.TextAreaFor(x => x.MetaDescription, new { @class = "input-large" }) + @Html.TextAreaFor(x => x.MetaDescription) @Html.ValidationMessageFor(model => model.MetaDescription)
        - @Html.TextAreaFor(x => x.MetaTitle, new { @class = "input-large" }) + @Html.TextAreaFor(x => x.MetaTitle) @Html.ValidationMessageFor(model => model.MetaTitle)
        - @Html.TextAreaFor(x => x.SeName, new { @class = "input-large" }) + @Html.EditorFor(x => x.SeName) @if (Model.Id != 0) - { - @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Product", entityId = @Model.Id }) - } + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Product", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
        +
        @@ -119,22 +124,16 @@  
        -
        - -

         

        - +
        @(Html.Telerik().Grid() - .Name("specificationattributes-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("psaId"); - }) - .Columns(columns => - { - columns.Bound(x => x.SpecificationAttributeName).Width(200).ReadOnly(); - columns.Bound(x => x.SpecificationAttributeOptionName) - .ClientTemplate("<#= SpecificationAttributeOptionName #>") - .ReadOnly() - .Width(300); - columns.Bound(x => x.AllowFiltering) - .Width(100) - .Template(item => @Html.SymbolForBool(item.AllowFiltering)) - .ClientTemplate(@Html.SymbolForBool("AllowFiltering")) - .Centered(); - columns.Bound(x => x.ShowOnProductPage) - .Width(100) - .Template(item => @Html.SymbolForBool(item.ShowOnProductPage)) - .ClientTemplate(@Html.SymbolForBool("ShowOnProductPage")) - .Centered(); - columns.Bound(x => x.DisplayOrder) + .Name("specificationattributes-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("psaId"); + }) + .Columns(columns => + { + columns.Bound(x => x.SpecificationAttributeName) + .Width(200) + .ReadOnly(); + columns.Bound(x => x.SpecificationAttributeOptionName) + .ClientTemplate("<#= SpecificationAttributeOptionName #>") + .ReadOnly() + .Width(300); + columns.Bound(x => x.AllowFiltering) + .Width(100) + .ClientTemplate(@Html.SymbolForBool("AllowFiltering")) + .Centered(); + columns.Bound(x => x.ShowOnProductPage) + .Width(100) + .ClientTemplate(@Html.SymbolForBool("ShowOnProductPage")) + .Centered(); + columns.Bound(x => x.DisplayOrder) .Centered() .Width(100); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(180); - - }) - .ClientEvents(e => - { - e.OnEdit("OnGridEdit"); - e.OnRowDataBound("OnGridRowDataBound"); - }) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("ProductSpecAttrList", "Product", new { productId = Model.Id }) - .Update("ProductSpecAttrUpdate", "Product") - .Delete("ProductSpecAttrDelete", "Product"); - }) - .EnableCustomBinding(true)) + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }).Width(180).HtmlAttributes(new { align = "right" }); + }) + .ClientEvents(e => + { + e.OnEdit("OnGridEdit"); + e.OnRowDataBound("OnGridRowDataBound"); + }) + .Editable(x => + { + x.Mode(GridEditMode.InLine); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("ProductSpecAttrList", "Product", new { productId = Model.Id }) + .Update("ProductSpecAttrUpdate", "Product") + .Delete("ProductSpecAttrDelete", "Product"); + }) + .EnableCustomBinding(true))
        } else diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Stores.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Stores.cshtml index 25682b32f8..470cfa438d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Stores.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Stores.cshtml @@ -1,50 +1,3 @@ @model ProductModel - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.LimitedToStores) - - @Html.EditorFor(model => model.LimitedToStores) - @Html.ValidationMessageFor(model => model.LimitedToStores) -
        - @Html.SmartLabelFor(model => model.AvailableStores) - - @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) - { - foreach (var store in Model.AvailableStores) - { - - } - } - else - { -
        @T("Admin.Configuration.Stores.NoStoresDefined")
        - } -
        +@Html.Partial("StoreSelector", Model) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml index 4f5763b2db..e6ab7fdeea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.cshtml @@ -1,12 +1,9 @@ -@model ProductModel - -@using SmartStore.Core.Domain.Catalog; -@using Telerik.Web.Mvc.UI; +@using SmartStore.Core.Domain.Catalog; @using SmartStore.Web.Framework.UI; - +@model ProductModel @{ - Html.AddCssFileParts(true, "~/Content/x-editable/bootstrap-editable.css"); - Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); + Html.AddCssFileParts(true, "~/Content/vendors/x-editable/bootstrap-editable.css"); + Html.AppendScriptParts(true, "~/Content/vendors/x-editable/bootstrap-editable.js"); } @Html.ValidationSummary(false) @@ -18,117 +15,113 @@ } -@Html.SmartStore().TabStrip().Name("product-edit").OnAjaxSuccess("productEditTab_onAjaxSuccess").HtmlAttributes(new { data_product_id = Model.Id }).Style(TabsStyle.Tabs).Position(TabsPosition.Left).Items(x => +@Html.SmartStore().TabStrip().Name("product-edit").OnAjaxSuccess("productEditTab_onAjaxSuccess").HtmlAttributes(new { data_product_id = Model.Id }).Style(TabsStyle.Material).Position(TabsPosition.Left).Items(x => { - x.Add().Text(T("Admin.Catalog.Products.Info").Text) - .Icon("fa fa-pencil fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Info" }) - .Content(Html.Partial("_CreateOrUpdate.Info", Model).ToHtmlString()) - .Selected(true); - - x.Add().Text(T("Admin.Catalog.Products.Inventory").Text) - .Name("tab-inventory") - .Visible(Model.ProductTypeId == (int)ProductType.SimpleProduct || (Model.ProductTypeId == (int)ProductType.BundledProduct && !Model.BundlePerItemShoppingCart)) - .Icon("fa fa-archive fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Inventory" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Inventory" }) - .Ajax(); - - if (Model.Id > 0) - { - if (Model.ProductTypeId == (int)ProductType.GroupedProduct) - { - x.Add().Text(T("Admin.Catalog.Products.AssociatedProducts").Text) - .Icon("fa fa-link fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "AssociatedProducts" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "AssociatedProducts" }) - .Ajax(); - } - else if (Model.ProductTypeId == (int)ProductType.BundledProduct) - { - x.Add().Text(T("Admin.Catalog.Products.BundleItems").Text) - .Icon("fa fa-link fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "BundleItems" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "BundleItems" }) - .Ajax(); - } - } + x.Add().Text(T("Admin.Catalog.Products.Info")) + .Icon("fa fa-pencil-alt fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Info" }) + .Content(Html.Partial("_CreateOrUpdate.Info", Model).ToHtmlString()) + .Selected(true); - if (Model.ProductTypeId != (int)ProductType.GroupedProduct) - { - x.Add().Text(T("Admin.Catalog.Products.Price").Text) - .Icon("fa fa-euro fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Price" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Price" }) - .Ajax(); - } - - x.Add().Text(T("Admin.Catalog.Products.Discounts").Text) - .Name("tab-discounts") - .Visible(!(Model.ProductTypeId == (int)ProductType.BundledProduct && Model.BundlePerItemPricing)) - .Icon("fa fa-signal fa-lg fa-flip-horizontal fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Discounts" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Discounts" }) - .Ajax(); + x.Add().Text(T("Admin.Catalog.Products.Inventory").Text) + .Name("tab-inventory") + .Visible(Model.ProductTypeId == (int)ProductType.SimpleProduct || (Model.ProductTypeId == (int)ProductType.BundledProduct && !Model.BundlePerItemShoppingCart)) + .Icon("fa fa-archive fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Inventory" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Inventory" }) + .Ajax(); - x.Add().Text(T("Admin.Catalog.Products.Pictures").Text) - .Icon("fa fa-picture-o fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Pictures" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Pictures" }) - .Ajax(); - x.Add().Text(T("Admin.Catalog.Products.Categories").Text) - .Icon("fa fa-sitemap fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Categories" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Categories" }) - .Ajax(); - x.Add().Text(T("Admin.Catalog.Products.Manufacturers").Text) - .Icon("fa fa-building-o fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Manufacturers" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Manufacturers" }) - .Ajax(); + if (Model.Id > 0) + { + if (Model.ProductTypeId == (int)ProductType.GroupedProduct) + { + x.Add().Text(T("Admin.Catalog.Products.AssociatedProducts").Text) + .Icon("fa fa-link fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "AssociatedProducts" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "AssociatedProducts" }) + .Ajax(); + } + else if (Model.ProductTypeId == (int)ProductType.BundledProduct) + { + x.Add().Text(T("Admin.Catalog.Products.BundleItems").Text) + .Icon("fa fa-link fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "BundleItems" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "BundleItems" }) + .Ajax(); + } + } - x.Add().Text(T("Admin.Catalog.Products.Promotion").Text) - .Icon("fa fa-bullhorn fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Promotion" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Promotion" }) - .Ajax(); + if (Model.ProductTypeId != (int)ProductType.GroupedProduct) + { + x.Add().Text(T("Admin.Catalog.Products.Price").Text) + .Icon("fa fa-euro-sign fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Price" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Price" }) + .Ajax(); + } - if (Model.ProductTypeId != (int)ProductType.BundledProduct) - { - x.Add().Text(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes").Text) - .Icon("fa fa-list-alt fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Attributes" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Attributes" }) - .Ajax(); - } + x.Add().Text(T("Admin.Catalog.Products.Discounts").Text) + .Name("tab-discounts") + .Visible(!(Model.ProductTypeId == (int)ProductType.BundledProduct && Model.BundlePerItemPricing)) + .Icon("fa fa-percent fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Discounts" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Discounts" }) + .Ajax(); + x.Add().Text(T("Admin.Catalog.Products.Pictures").Text) + .Icon("far fa-image fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Pictures" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Pictures" }) + .Ajax(); + x.Add().Text(T("Admin.Catalog.Products.Categories").Text) + .Icon("fa fa-sitemap fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Categories" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Categories" }) + .Ajax(); + x.Add().Text(T("Admin.Catalog.Products.Manufacturers").Text) + .Icon("far fa-building fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Manufacturers" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Manufacturers" }) + .Ajax(); + x.Add().Text(T("Admin.Catalog.Products.Promotion").Text) + .Icon("fa fa-bullhorn fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Promotion" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Promotion" }) + .Ajax(); - x.Add().Text(T("Admin.Catalog.Products.SpecificationAttributes").Text) - .Icon("fa fa-bars fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "SpecificationAttributes" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "SpecificationAttributes" }) - .Ajax(); + if (Model.ProductTypeId != (int)ProductType.BundledProduct) + { + x.Add().Text(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes").Text) + .Icon("far fa-list-alt fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Attributes" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Attributes" }) + .Ajax(); + } - x.Add().Text(T("Admin.Common.SEO").Text) - .Icon("fa fa-search fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "SEO" }) - .Content(Html.Partial("_CreateOrUpdate.SEO", Model).ToHtmlString()); - //x.Add().Text(T("Admin.Common.SEO").Text) - // .Icon("fa fa-search fa-lg fa-fw") - // .LinkHtmlAttributes(new { data_tab_name = "SEO" }) - // .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "SEO" }) - // .Ajax(); - x.Add().Text(T("Admin.Catalog.Products.Acl").Text) - .Icon("fa fa-users fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Acl" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Acl" }) - .Ajax(); - x.Add().Text(T("Admin.Common.Stores").Text) - .Icon("fa fa-globe fa-lg fa-fw") - .LinkHtmlAttributes(new { data_tab_name = "Stores" }) - .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Stores" }) - .Ajax(); + x.Add().Text(T("Admin.Catalog.Products.SpecificationAttributes").Text) + .Icon("fa fa-bars fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "SpecificationAttributes" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "SpecificationAttributes" }) + .Ajax(); + x.Add().Text(T("Admin.Common.SEO").Text) + .Icon("fa fa-search fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "SEO" }) + .Content(Html.Partial("_CreateOrUpdate.SEO", Model).ToHtmlString()); + x.Add().Text(T("Admin.Catalog.Products.Acl").Text) + .Icon("fa fa-users fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Acl" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Acl" }) + .Ajax(); + x.Add().Text(T("Admin.Common.Stores").Text) + .Icon("fa fa-globe fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Stores" }) + .Action("LoadEditTab", "Product", new { id = Model.Id, tabName = "Stores" }) + .Ajax(); + x.Add().Text(T("Common.Downloads").Text) + .Icon("fa fa-download fa-lg fa-fw") + .LinkHtmlAttributes(new { data_tab_name = "Downloads" }) + .Content(Html.Partial("_CreateOrUpdate.Downloads", Model).ToHtmlString()); - //generate an event - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "product-edit", this.Html, this.Model)); + // generate an event + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "product-edit", this.Html, this.Model)); }) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml index 9e07dff329..7069c9f549 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateAttributeCombinationPopup.cshtml @@ -3,26 +3,15 @@ @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id)@*otherwise we get a model state error*@ -@if (ViewBag.RefreshPage == true) -{ - -} - -
        +
        @if (ViewBag.IsEdit) - { -
        - @Html.Raw(Html.TableFormattedVariantAttributes(Model.AttributesXml)) -
        - } - else - { + { + @Html.Raw(Html.TableFormattedVariantAttributes(Model.AttributesXml)) + } + else + {
        - @Html.Partial("_ProductAttributes", Model.ProductVariantAttributes) + @{ Html.RenderPartial("_ProductAttributes", Model); }
        }
        @@ -32,7 +21,7 @@
        @if (Model.Warnings.Count > 0) { -
        +
        @foreach (var warning in Model.Warnings) {

         @warning

        @@ -61,15 +50,19 @@ @Html.SmartLabelFor(model => model.AssignedPictureIds) - @foreach (var x in Model.AssignablePictures) - { -
        - -
        - } +
          + @foreach (var x in Model.AssignablePictures) + { +
        • + +
        • + } +
        @@ -104,7 +97,7 @@ @Html.SmartLabelFor(model => model.Price) - @Html.EditorFor(model => model.Price) + @Html.EditorFor(model => model.Price, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.Price) @@ -143,7 +136,7 @@ @Html.SmartLabelFor(model => model.Length) - @Html.EditorFor(model => model.Length) + @Html.EditorFor(model => model.Length, new { postfix = Model.BaseDimensionIn }) @Html.ValidationMessageFor(model => model.Length) @@ -152,7 +145,7 @@ @Html.SmartLabelFor(model => model.Width) - @Html.EditorFor(model => model.Width) + @Html.EditorFor(model => model.Width, new { postfix = Model.BaseDimensionIn }) @Html.ValidationMessageFor(model => model.Width) @@ -161,7 +154,7 @@ @Html.SmartLabelFor(model => model.Height) - @Html.EditorFor(model => model.Height) + @Html.EditorFor(model => model.Height, new { postfix = Model.BaseDimensionIn }) @Html.ValidationMessageFor(model => model.Height) @@ -190,7 +183,7 @@
        - +
        @@ -118,8 +98,6 @@
        @Html.SmartLabelFor(model => model.ValueTypeId) @@ -93,16 +62,27 @@ @Html.HiddenFor(model => model.LinkedProductId) -
        @Model.LinkedProductName
        - - - +
        + +
        + + @(Html.SmartStore().EntityPicker() + .For(x => x.LinkedProductId) + .IconCssClass("fa fa-link") + .HtmlAttribute("class", "btn btn-outline-secondary") + .HtmlAttribute("id", "AddProductLinkageButton") + .MaxItems(1) + .AppendMode(false) + .DisabledEntityIds(Model.ProductId) + .DialogTitle(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.LinkedProduct.AddNew").Text.EncodeJsString('\'', false)) + .Caption(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.LinkProduct").Text.EncodeJsString('\'', false)) + .OnSelectionCompleted("PickLinkedProduct_Completed")) +
        +
        @Html.ValidationMessageFor(model => model.LinkedProductId)
        -

        - @(Html.LocalizedEditor("productattributevalue-localized", @ @@ -127,13 +105,20 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) - +
        + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name)
        - @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.SmartLabelFor(model => model.Locales[item].Alias) + + @Html.EditorFor(model => Model.Locales[item].Alias) + @Html.ValidationMessageFor(model => model.Locales[item].Alias)
        @@ -148,11 +133,6 @@ @Html.ValidationMessageFor(model => model.Name) - -)) - -@Html.HiddenFor(model => model.DisplayColorSquaresRgb) - - @if (Model.DisplayColorSquaresRgb) +
        @Html.SmartLabelFor(model => model.Alias) @@ -162,15 +142,30 @@ @Html.ValidationMessageFor(model => model.Alias)
        +)) + +@Html.HiddenFor(model => model.IsListTypeAttribute) + + + @if (Model.IsListTypeAttribute) { + + + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductTag.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductTag.cshtml index d6f59de642..116e5601d5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductTag.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdateProductTag.cshtml @@ -2,14 +2,7 @@ @using Telerik.Web.Mvc.UI; @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) -@if (ViewBag.RefreshPage == true) -{ - -} + @(Html.LocalizedEditor("producttag-localized", @
        - @Html.SmartLabelFor(model => model.ColorSquaresRgb) + @Html.SmartLabelFor(model => model.PictureId) + + @Html.EditorFor(model => model.PictureId, new { transientUpload = true }) + @Html.ValidationMessageFor(model => model.PictureId) +
        + @Html.SmartLabelFor(model => model.Color) - @Html.EditorFor(model => model.ColorSquaresRgb) - @Html.ValidationMessageFor(model => model.ColorSquaresRgb) + @Html.EditorFor(model => model.Color) + @Html.ValidationMessageFor(model => model.Color)
        @@ -17,15 +10,13 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) - - -
        + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name)
        - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
        , @ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_ProductAttributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_ProductAttributes.cshtml index 397f6c3b3e..4361fb4c5b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_ProductAttributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_ProductAttributes.cshtml @@ -1,44 +1,39 @@ -@model IList -@using SmartStore.Web.Framework; +@using SmartStore.Web.Framework; @using SmartStore.Core.Domain.Catalog; -@if (Model.Count > 0) +@model ProductVariantAttributeCombinationModel + +@if (Model.ProductVariantAttributes.Count > 0) { -
        - @foreach (var attribute in Model) - { - string controlId = string.Format("product_attribute_{0}_{1}", attribute.ProductAttributeId, attribute.Id); -
        - -
        - @if (attribute.AttributeControlType == AttributeControlType.Checkboxes || attribute.AttributeControlType == AttributeControlType.RadioList || attribute.AttributeControlType == AttributeControlType.ColorSquares) - { - @ControlsCheckboxesAndRadios(attribute, controlId, attribute.AttributeControlType == AttributeControlType.Checkboxes ? "checkbox" : "radio") - } - else - { - @ControlsOthers(attribute, controlId) - } + foreach (var attribute in Model.ProductVariantAttributes) + { + var controlId = attribute.GetControlId(Model.ProductId, 0); +
        + +
        + @if (attribute.AttributeControlType == AttributeControlType.Checkboxes || attribute.AttributeControlType == AttributeControlType.RadioList || attribute.AttributeControlType == AttributeControlType.Boxes) + { + @ControlsCheckboxesAndRadios(attribute, controlId, attribute.AttributeControlType == AttributeControlType.Checkboxes ? "checkbox" : "radio") + } + else + { + @ControlsOthers(attribute, controlId) + } +
        -
        } -
        } @helper ControlsCheckboxesAndRadios(ProductVariantAttributeCombinationModel.ProductVariantAttributeModel attribute, string controlId, string type) { foreach (var pvaValue in attribute.Values) { - +
        + + +
        } } @@ -46,28 +41,28 @@ { if(attribute.AttributeControlType == AttributeControlType.DropdownList) { - @if (!attribute.IsRequired) { - + } @foreach (var pvaValue in attribute.Values) { - + } } else if(attribute.AttributeControlType == AttributeControlType.TextBox) { - + } else if (attribute.AttributeControlType == AttributeControlType.MultilineTextbox) { - + } else if (attribute.AttributeControlType == AttributeControlType.Datepicker) { - @Html.DatePickerDropDowns(controlId + "_day", controlId + "_month", controlId + "_year", DateTime.Now.Year, DateTime.Now.Year + 1) + @Html.DatePickerDropDowns(controlId + "-day", controlId + "-month", controlId + "-year", DateTime.Now.Year, DateTime.Now.Year + 1) } else if (attribute.AttributeControlType == AttributeControlType.FileUpload) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Create.cshtml index 26d5720481..33e0e1d91c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Create.cshtml @@ -1,6 +1,5 @@ @model ProductAttributeModel @{ - //page title ViewBag.Title = T("Admin.Catalog.Attributes.ProductAttributes.AddNew").Text; } @using (Html.BeginForm()) @@ -10,9 +9,15 @@ @T("Admin.Catalog.Attributes.ProductAttributes.AddNew") @Html.ActionLink("(" + T("Admin.Catalog.Attributes.ProductAttributes.BackToList") + ")", "List")
        - - + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml index 82f512b76d..6da614eefe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/Edit.cshtml @@ -1,6 +1,5 @@ @model ProductAttributeModel @{ - //page title ViewBag.Title = T("Admin.Catalog.Attributes.ProductAttributes.EditAttributeDetails").Text; } @using (Html.BeginForm()) @@ -10,11 +9,20 @@ @T("Admin.Catalog.Attributes.ProductAttributes.EditAttributeDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.Catalog.Attributes.ProductAttributes.BackToList") + ")", "List")
        - - - + + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("productattribute-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/List.cshtml index 7a65e1a1a1..971c1e8bbb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/List.cshtml @@ -1,7 +1,5 @@ -@model Telerik.Web.Mvc.GridModel -@using Telerik.Web.Mvc.UI +@using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.Catalog.Attributes.ProductAttributes").Text; }
        @@ -9,30 +7,31 @@ @T("Admin.Catalog.Attributes.ProductAttributes")
        -
        - - - -
        - @(Html.Telerik().Grid(Model.Data) - .Name("productattributes-grid") - .Columns(columns => - { - columns.Bound(x => x.Name) - .Width(300) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.Alias); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ProductAttribute")) - .EnableCustomBinding(true)) -
        \ No newline at end of file + +
        + @(Html.Telerik().Grid() + .Name("productattributes-grid") + .Columns(columns => + { + columns.Bound(x => x.Name) + .ClientTemplate("<#= Name #>"); + columns.Bound(x => x.Alias); + columns.Bound(x => x.AllowFiltering) + .Width(200) + .ClientTemplate(@Html.SymbolForBool("AllowFiltering")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(200) + .Centered(); + }) + .Pageable(settings => settings.PageSize((int)ViewData["GridPageSize"]).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ProductAttribute")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/OptionCreatePopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/OptionCreatePopup.cshtml new file mode 100644 index 0000000000..97f2c8e5db --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/OptionCreatePopup.cshtml @@ -0,0 +1,21 @@ +@model ProductAttributeOptionModel +@{ + Layout = "_AdminPopupLayout"; + ViewBag.Title = T("Common.Options.Add").Text; +} +@using (Html.BeginForm()) +{ +
        +
        + @T("Common.Options.Add") +
        +
        + +
        +
        + + Html.RenderPartial("_CreateOrUpdateOption", Model); +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/OptionEditPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/OptionEditPopup.cshtml new file mode 100644 index 0000000000..2dcf025f65 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/OptionEditPopup.cshtml @@ -0,0 +1,21 @@ +@model ProductAttributeOptionModel +@{ + Layout = "_AdminPopupLayout"; + ViewBag.Title = T("Common.Options.Edit").Text; +} +@using (Html.BeginForm()) +{ +
        +
        + @T("Common.Options.Edit") +
        +
        + +
        +
        + + Html.RenderPartial("_CreateOrUpdateOption", Model); +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdate.cshtml index 8979c04659..03ba9ce78a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdate.cshtml @@ -1,66 +1,295 @@ -@model ProductAttributeModel - -@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI; +@using SmartStore.Core.Search.Facets; +@model ProductAttributeModel @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) -@(Html.LocalizedEditor("productattribute-localized", - @ - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.Locales[item].Name) - - @Html.EditorFor(model => Model.Locales[item].Name) - @Html.ValidationMessageFor(model => model.Locales[item].Name) -
        - @Html.SmartLabelFor(model => model.Locales[item].Description) - - @Html.EditorFor(model => model.Locales[item].Description, Html.RichEditorFlavor()) - @Html.ValidationMessageFor(model => model.Locales[item].Description) -
        - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
        - , - @ +@Html.SmartStore().TabStrip().Name("productattribute-edit").Style(TabsStyle.Material).Items(x => +{ + x.Add().Text(T("Common.General").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Catalog.Attributes.OptionsSets").Text).Content(TabOptionsSets()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "productattribute-edit", this.Html, this.Model)); +}) + +@helper TabInfo() +{ + if (Model.Id != 0) + { +
        + + + + +
        + @Html.SmartLabelFor(model => model.Id) + + @Html.TextBoxFor(model => model.Id, new { @readonly = "readonly", @class = "form-control-plaintext" }) + @Html.ValidationMessageFor(model => model.Id) +
        + } + + @(Html.LocalizedEditor("productattribute-localized", + @ + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.Locales[item].Name) + + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.EditorFor(model => Model.Locales[item].Name) + @Html.ValidationMessageFor(model => model.Locales[item].Name) +
        + @Html.SmartLabelFor(model => model.Locales[item].Alias) + + @Html.EditorFor(model => Model.Locales[item].Alias) + @Html.ValidationMessageFor(model => model.Locales[item].Alias) +
        + @Html.SmartLabelFor(model => model.Locales[item].Description) + + @Html.EditorFor(model => model.Locales[item].Description, "Html") + @Html.ValidationMessageFor(model => model.Locales[item].Description) +
        + , + @ + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
        + @Html.SmartLabelFor(model => model.Alias) + + @Html.EditorFor(model => model.Alias) + @Html.ValidationMessageFor(model => model.Alias) +
        + @Html.SmartLabelFor(model => model.Description) + + @Html.EditorFor(x => x.Description, "Html") + @Html.ValidationMessageFor(model => model.Description) +
        + )) + + + + + + + + + + + + + + -
        + @Html.SmartLabelFor(model => model.DisplayOrder) + + @Html.EditorFor(model => model.DisplayOrder) + @Html.ValidationMessageFor(model => model.DisplayOrder) +
        + @Html.SmartLabelFor(model => model.AllowFiltering) + + @Html.CheckBoxFor(model => model.AllowFiltering, new { data_toggler_for = "#pnlAllowFiltering" }) + @Html.ValidationMessageFor(model => model.AllowFiltering) +
        + @Html.SmartLabelFor(model => model.FacetTemplateHint) + + @Html.DropDownListFor(model => model.FacetTemplateHint, + Model.FacetTemplateHint.ToSelectList().Where(x => x.Value.ToInt() != (int)FacetTemplateHint.NumericRange).ToList()) + @Html.ValidationMessageFor(model => model.FacetTemplateHint) +
        - @Html.SmartLabelFor(model => model.Name) + @Html.SmartLabelFor(model => model.IndexOptionNames) - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) + @Html.EditorFor(model => model.IndexOptionNames) + @Html.ValidationMessageFor(model => model.IndexOptionNames)
        - @Html.SmartLabelFor(model => model.Description) + @Html.SmartLabelFor(model => model.ExportMappings) - @Html.EditorFor(x => x.Description, Html.RichEditorFlavor()) - @Html.ValidationMessageFor(model => model.Description) + @Html.TextAreaFor(model => model.ExportMappings, new { style = "min-height: 120px;" }) + @Html.ValidationMessageFor(model => model.ExportMappings)
        -)) + + + +
        + @Html.Raw(T("Admin.Catalog.Attributes.ProductAttributes.Fields.ExportMappings.Note")) +
        + + + +} + +@helper TabOptionsSets() +{ + if (Model.Id > 0) + { +
        + @(Html.Telerik().Grid() + .Name("productattributeoptionsset-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id); + keys.Add(x => x.ProductAttributeId).RouteKey("productAttributeId"); + }) + .ToolBar(commands => commands.Insert()) + .Columns(columns => + { + columns.Bound(x => x.Name); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right" }) + .Width(120); + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("OptionsSetList", "ProductAttribute", new { productAttributeId = Model.Id }) + .Insert("OptionsSetInsert", "ProductAttribute", new { productAttributeId = Model.Id }) + .Update("OptionsSetUpdate", "ProductAttribute") + .Delete("OptionsSetDelete", "ProductAttribute") + ) + .EnableCustomBinding(true) + .DetailView(details => details.ClientTemplate( + Html.Telerik().Grid() + .Name("productattributeoptions-grid<#= Id #>") + .DataKeys(keys => + { + keys.Add(x => x.Id); + keys.Add(x => x.ProductAttributeOptionsSetId).RouteKey("optionsSetId"); + }) + .ToolBar(commands => commands.Template(OptionGridCommands)) + .Columns(columns => + { + columns.Bound(x => x.Name) + .ClientTemplate(@Html.VariantAttributeValueName()); + columns.Bound(x => x.Alias); + columns.Bound(x => x.LinkedProductName) + .ClientTemplate(@Html.LabeledProductName("LinkedProductId", "LinkedProductName", "LinkedProductTypeName", "LinkedProductTypeLabelHint")); + columns.Bound(x => x.PriceAdjustmentString) + .Centered(); + columns.Bound(x => x.WeightAdjustmentString) + .Centered(); + columns.Bound(x => x.IsPreSelected) + .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Centered(); + columns.Bound(x => x.Id) + .Width(220) + .Centered() + .Title("") + .ClientTemplate( + "" + @T("Admin.Common.Edit") + "" + + "" + @T("Admin.Common.Delete") + "") + .HtmlAttributes(new { align = "right" }); + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("OptionsSetListDetails", "ProductAttribute", new { id = "<#= Id #>" }) + ) + .ToHtmlString() + ))) +
        + + + + + + } + else + { +
        + @T("Admin.SaveBeforeEdit") +
        + } +} - - - - - -
        - @Html.SmartLabelFor(model => model.Alias) - - @Html.EditorFor(model => model.Alias) - @Html.ValidationMessageFor(model => model.Alias) -
        +@helper OptionGridCommands(Grid grid) +{ + +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdateOption.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdateOption.cshtml new file mode 100644 index 0000000000..0f98d75ee6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductAttribute/_CreateOrUpdateOption.cshtml @@ -0,0 +1,208 @@ +@using SmartStore.Core.Domain.Catalog; +@using Telerik.Web.Mvc.UI; +@model ProductAttributeOptionModel + +@Html.ValidationSummary(false) +@Html.HiddenFor(model => model.Id) +@Html.HiddenFor(model => model.ProductVariantAttributeId) +@Html.HiddenFor(model => model.ProductAttributeOptionsSetId) +@Html.HiddenFor(model => model.IsListTypeAttribute) + + + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.ValueTypeId) + + @Html.DropDownListFor(model => model.ValueTypeId, ((ProductVariantAttributeValueType)Model.ValueTypeId).ToSelectList()) + @Html.ValidationMessageFor(model => model.ValueTypeId) +
        + @Html.SmartLabelFor(model => model.LinkedProductId) + + @Html.HiddenFor(model => model.LinkedProductId) +
        + +
        + + @(Html.SmartStore().EntityPicker() + .For(x => x.LinkedProductId) + .IconCssClass("fa fa-link") + .HtmlAttribute("class", "btn btn-secondary") + .HtmlAttribute("id", "AddProductLinkageButton") + .MaxItems(1) + .AppendMode(false) + .DisabledEntityIds(Model.ProductId) + .DialogTitle(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.LinkedProduct.AddNew").Text.EncodeJsString('\'', false)) + .Caption(T("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.LinkProduct").Text.EncodeJsString('\'', false)) + .OnSelectionCompleted("PickLinkedProduct_Completed")) +
        +
        + + @Html.ValidationMessageFor(model => model.LinkedProductId) +
        + @Html.SmartLabelFor(model => model.Quantity) + + @Html.EditorFor(model => model.Quantity) + @Html.ValidationMessageFor(model => model.Quantity) +
        + +

        + +@(Html.LocalizedEditor("productattribute-localized", +@ + + + + + + + + +
        + @Html.SmartLabelFor(model => model.Locales[item].Name) + + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.EditorFor(model => Model.Locales[item].Name) + @Html.ValidationMessageFor(model => model.Locales[item].Name) +
        + @Html.SmartLabelFor(model => model.Locales[item].Alias) + + @Html.EditorFor(model => Model.Locales[item].Alias) + @Html.ValidationMessageFor(model => model.Locales[item].Alias) +
        +, +@ + + + + + + + + +
        + @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
        + @Html.SmartLabelFor(model => model.Alias) + + @Html.EditorFor(model => model.Alias) + @Html.ValidationMessageFor(model => model.Alias) +
        +)) + + @if (Model.IsListTypeAttribute) + { + + + + + + + + + } + + + + + + + + + + + + + + + + +
        + @Html.SmartLabelFor(model => model.PictureId) + + @Html.EditorFor(model => model.PictureId, new { transientUpload = true }) + @Html.ValidationMessageFor(model => model.PictureId) +
        + @Html.SmartLabelFor(model => model.Color) + + @Html.EditorFor(model => model.Color) + @Html.ValidationMessageFor(model => model.Color) +
        + @Html.SmartLabelFor(model => model.PriceAdjustment) + + @Html.EditorFor(model => model.PriceAdjustment) + @Html.ValidationMessageFor(model => model.PriceAdjustment) +
        + @Html.SmartLabelFor(model => model.WeightAdjustment) + + @Html.EditorFor(model => model.WeightAdjustment) + @Html.ValidationMessageFor(model => model.WeightAdjustment) +
        + @Html.SmartLabelFor(model => model.IsPreSelected) + + @Html.EditorFor(model => model.IsPreSelected) + @Html.ValidationMessageFor(model => model.IsPreSelected) +
        + @Html.SmartLabelFor(model => model.DisplayOrder) + + @Html.EditorFor(model => model.DisplayOrder) + @Html.ValidationMessageFor(model => model.DisplayOrder) +
        + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml index 2d026ef588..dad114749a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/Edit.cshtml @@ -1,6 +1,5 @@ @model ProductReviewModel @{ - //page title ViewBag.Title = T("Admin.Catalog.ProductReviews.EditProductReviewDetails").Text; } @using (Html.BeginForm()) @@ -10,11 +9,20 @@ @T("Admin.Catalog.ProductReviews.EditProductReviewDetails") @Html.ActionLink("(" + T("Admin.Catalog.ProductReviews.BackToList") + ")", "List")
        - - - + + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("productreview-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml index dd1c42971f..6fc0887407 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/List.cshtml @@ -2,8 +2,7 @@ @using Telerik.Web.Mvc.UI @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title + ViewBag.Title = T("Admin.Catalog.ProductReviews").Text; }
        @@ -14,200 +13,198 @@ @T("Admin.Catalog.ProductReviews")
        - - - + + +
        - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.CreatedOnFrom) - - @Html.EditorFor(model => model.CreatedOnFrom) -
        - @Html.SmartLabelFor(model => model.CreatedOnTo) - - @Html.EditorFor(model => Model.CreatedOnTo) -
          - -
        - - - - - -
        - @(Html.Telerik().Grid() - .Name("productreviews-grid") - .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - - columns.Bound(x => x.ProductName) - .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.CustomerId) - .Template(x => Html.ActionLink(x.CustomerName, "Edit", "Customer", new { id = x.CustomerId }, new { })) - .ClientTemplate("\"><#= CustomerName #>"); - columns.Bound(x => x.Title); - columns.Bound(x => x.ReviewText) - .Width(500) - .ClientTemplate("<#= _.str.truncate(ReviewText, 400, ' [...]') #>") - .Encoded(false); - columns.Bound(x => x.Rating) - .Width(50) - .Centered(); - columns.Bound(x => x.IsApproved) - .Width(80) - .Template(item => @Html.SymbolForBool(item.IsApproved)) - .ClientTemplate(@Html.SymbolForBool("IsApproved")) - .Centered(); - columns.Bound(x => x.CreatedOn) - .Centered() - .Width(140); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ProductReview")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        - - + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/_CreateOrUpdate.cshtml index e3cb60068a..14b2d2c991 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ProductReview/_CreateOrUpdate.cshtml @@ -8,7 +8,9 @@ @Html.SmartLabelFor(model => model.ProductName) - @Html.ActionLink(Model.ProductName, "Edit", "Product", new { id = Model.ProductId }, new { }) +
        + @Html.ActionLink(Model.ProductName, "Edit", "Product", new { id = Model.ProductId }, new { }) +
        @@ -16,15 +18,17 @@ @Html.SmartLabelFor(model => model.CustomerId) - @Html.ActionLink(Model.CustomerName, "Edit", "Customer", new { id = Model.CustomerId }, new { }) - +
        + @Html.ActionLink(Model.CustomerName, "Edit", "Customer", new { id = Model.CustomerId }, new { }) +
        + @Html.SmartLabelFor(model => model.IpAddress) - @Html.DisplayFor(model => model.IpAddress) + @Html.TextBoxFor(model => model.IpAddress, new { @readonly = "readonly", @class = "form-control-plaintext" }) @@ -41,7 +45,7 @@ @Html.SmartLabelFor(model => model.ReviewText) - @Html.TextAreaFor(model => model.ReviewText, new { style = "Width: 500px; Height: 150px;" }) + @Html.TextAreaFor(model => model.ReviewText, new { style = "height:150px" }) @Html.ValidationMessageFor(model => model.ReviewText) @@ -50,7 +54,7 @@ @Html.SmartLabelFor(model => model.Rating) - @Html.DisplayFor(model => model.Rating) + @Html.TextBoxFor(model => model.Rating, new { @readonly = "readonly", @class = "form-control-plaintext" }) @@ -67,7 +71,9 @@ @Html.SmartLabelFor(model => model.CreatedOn) - @Html.DisplayFor(model => model.CreatedOn) +
        + @Html.DisplayFor(model => model.CreatedOn) +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Create.cshtml index f585adf2bb..311c5de9f5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Create.cshtml @@ -9,9 +9,15 @@ @T("Admin.Configuration.QuantityUnit.AddNew") @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List")
        - - + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml index a10fca8033..965b8600ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/Edit.cshtml @@ -1,6 +1,5 @@ @model QuantityUnitModel @{ - //page title ViewBag.Title = T("Admin.Configuration.QuantityUnit.EditQuantityUnitDetails").Text; } @using (Html.BeginForm()) @@ -10,11 +9,20 @@ @T("Admin.Configuration.QuantityUnit.EditQuantityUnitDetails") - @Model.Name @Html.ActionLink("(" + @T("Admin.Common.BackToList") + ")", "List")
        - - - + + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("measure-unit-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/List.cshtml index a97430855e..9b04b55a9d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/List.cshtml @@ -12,34 +12,32 @@ - - - - -
        - @(Html.Telerik().Grid() - .Name("measure-unit-grid") - .BindTo(Model.Data) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.Description) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - columns.Bound(x => x.IsDefault) - .Template(item => @Html.SymbolForBool(item.IsDefault)) - .ClientTemplate(@Html.SymbolForBool("IsDefault")) - .Width(100) - .Centered(); - }) - ) -
        +
        + @(Html.Telerik().Grid() + .Name("measure-unit-grid") + .BindTo(Model.Data) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .ClientTemplate("<#= Name #>"); + columns.Bound(x => x.NamePlural) + .Title(T("Common.Plural").Text); + columns.Bound(x => x.Description) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(180) + .Centered(); + columns.Bound(x => x.IsDefault) + .Template(item => @Html.SymbolForBool(item.IsDefault)) + .ClientTemplate(@Html.SymbolForBool("IsDefault")) + .Width(180) + .Centered(); + }) + ) +
        \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/_CreateOrUpdate.cshtml index c1022d62af..7c1c850279 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QuantityUnit/_CreateOrUpdate.cshtml @@ -1,8 +1,4 @@ @model QuantityUnitModel -@using Telerik.Web.Mvc.UI; -@{ - var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.SpecificCultures).ToList(); -} @Html.HiddenFor(model => model.Id) @(Html.LocalizedEditor("measure-unit-info-localized", @@ -12,10 +8,22 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name) + + + @Html.SmartLabelFor(model => Model.Locales[item].NamePlural) + + + @Html.EditorFor(model => Model.Locales[item].NamePlural) + @Html.ValidationMessageFor(model => Model.Locales[item].NamePlural) + + @Html.SmartLabelFor(model => Model.Locales[item].Description) @@ -25,11 +33,6 @@ @Html.ValidationMessageFor(model => Model.Locales[item].Description) - - - @Html.HiddenFor(model => model.Locales[item].LanguageId) - - , @ @@ -41,6 +44,15 @@ @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) + + + + - - - - - - - - @@ -86,13 +65,15 @@ @Html.SmartLabelFor(model => model.Attachments) } @@ -100,8 +81,8 @@ - @@ -130,11 +111,13 @@ @@ -143,7 +126,7 @@ @Html.SmartLabelFor(model => model.EmailAccountName) @@ -151,7 +134,9 @@ @Html.SmartLabelFor(model => model.CreatedOn)
        + @Html.SmartLabelFor(model => model.NamePlural) + + @Html.EditorFor(model => model.NamePlural) + @Html.ValidationMessageFor(model => model.NamePlural) +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml index b6638c1736..7a9991407d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/Edit.cshtml @@ -9,18 +9,31 @@ @T("Admin.System.QueuedEmails.EditQueuedEmailDetails") @Html.ActionLink("(" + T("Admin.System.QueuedEmails.BackToList") + ")", "List")
        - - - + + + @if (Model.SendManually) { - } - +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("queuedEmail-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml index 44c2fe7f1e..5038df4680 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/List.cshtml @@ -8,143 +8,125 @@ {
        - + @T("Admin.System.QueuedEmails")
        - + +
        +
        + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - @Html.SmartLabelFor(model => model.SearchStartDate) - - @Html.EditorFor(model => model.SearchStartDate) -
        - @Html.SmartLabelFor(model => model.SearchEndDate) - - @Html.EditorFor(model => Model.SearchEndDate) -
        - @Html.SmartLabelFor(model => model.SearchFromEmail) - - @Html.EditorFor(model => Model.SearchFromEmail) -
        - @Html.SmartLabelFor(model => model.SearchToEmail) - - @Html.EditorFor(model => Model.SearchToEmail) -
        - @Html.SmartLabelFor(model => model.SearchLoadNotSent) - - @Html.EditorFor(model => Model.SearchLoadNotSent) -
        - @Html.SmartLabelFor(model => model.SearchSendManually) - - @Html.EditorFor(model => Model.SearchSendManually) -
        - @Html.SmartLabelFor(model => model.SearchMaxSentTries) - - @Html.EditorFor(model => Model.SearchMaxSentTries) -
        - @Html.SmartLabelFor(model => model.GoDirectlyToNumber) - - @Html.EditorFor(model => Model.GoDirectlyToNumber) - -
        -   - - -
        - -

        - - - - - -
        - @(Html.Telerik().Grid() - .Name("queuedEmails-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id) - .ClientTemplate("\"><#= Id #>"); - columns.Bound(x => x.AttachmentsCount) - .HeaderTemplate(@) - .Width(20); - columns.Bound(x => x.Subject); - columns.Bound(x => x.Priority) - .Centered(); - columns.Bound(x => x.From) - .ClientTemplate("<#= FromName #> (<#= From #>)"); - columns.Bound(x => x.SendManually) - .ClientTemplate(@Html.SymbolForBool("SendManually")) - .Centered(); - columns.Bound(x => x.SentTries) - .Centered(); - columns.Bound(x => x.SentOn); - columns.Bound(x => x.CreatedOn); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("QueuedEmailList", "QueuedEmail")) - .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        - + + + +
        + @(Html.Telerik().Grid() + .Name("queuedEmails-grid") + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .ClientTemplate("\"><#= Id #>"); + columns.Bound(x => x.AttachmentsCount) + .HeaderTemplate(@) + .Width(20); + columns.Bound(x => x.Subject); + columns.Bound(x => x.Priority) + .Centered(); + columns.Bound(x => x.From) + .ClientTemplate("<#= From #>"); + columns.Bound(x => x.SendManually) + .ClientTemplate(@Html.SymbolForBool("SendManually")) + .Centered(); + columns.Bound(x => x.SentTries) + .Centered(); + columns.Bound(x => x.SentOn); + columns.Bound(x => x.CreatedOn); + }) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("QueuedEmailList", "QueuedEmail")) + .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        + } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/_CreateOrUpdate.cshtml index b1720f9b11..e85c4f9550 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/QueuedEmail/_CreateOrUpdate.cshtml @@ -1,7 +1,4 @@ @model QueuedEmailModel - - -@using Telerik.Web.Mvc.UI; @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) @@ -25,15 +22,6 @@ @Html.ValidationMessageFor(model => model.From)
        - @Html.SmartLabelFor(model => model.FromName) - - @Html.EditorFor(model => model.FromName) - @Html.ValidationMessageFor(model => model.FromName) -
        @Html.SmartLabelFor(model => model.To) @@ -43,15 +31,6 @@ @Html.ValidationMessageFor(model => model.To)
        - @Html.SmartLabelFor(model => model.ToName) - - @Html.EditorFor(model => model.ToName) - @Html.ValidationMessageFor(model => model.ToName) -
        @Html.SmartLabelFor(model => model.CC) @@ -75,7 +54,7 @@ @Html.SmartLabelFor(model => model.Subject) - @Html.TextBoxFor(model => model.Subject, new { @class = "input-large" }) + @Html.TextBoxFor(model => model.Subject) @Html.ValidationMessageFor(model => model.Subject)
        - @foreach (var attach in Model.Attachments) - { - - - @attach.Name - - } +
        + @foreach (var attach in Model.Attachments) + { + + + @attach.Name + + } +
        @Html.SmartLabelFor(model => model.Body) - @Html.EditorFor(model => model.Body, Html.RichEditorFlavor()) + + @Html.EditorFor(model => model.Body, "Html") @Html.ValidationMessageFor(model => model.Body)
        @if (Model.SentOn.HasValue) { - @Html.DisplayFor(model => model.SentOn) - } - else - { - @("".NaIfEmpty()) +
        + @Html.DisplayFor(model => model.SentOn) +
        + } + else + { +
        @("".NaIfEmpty())
        }
        - @Html.DisplayFor(model => model.EmailAccountName) + @Html.TextBoxFor(model => model.EmailAccountName, new { @readonly = "readonly", @class = "form-control-plaintext" })
        - @Html.DisplayFor(model => model.CreatedOn) +
        + @Html.DisplayFor(model => model.CreatedOn) +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml index 3cdeed38c0..0eba5eb7ec 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/Edit.cshtml @@ -1,6 +1,5 @@ @model RecurringPaymentModel @{ - //page title ViewBag.Title = T("Admin.RecurringPayments.EditPaymentDetails").Text; } @using (Html.BeginForm()) @@ -10,11 +9,20 @@ @T("Admin.RecurringPayments.EditPaymentDetails") @Html.ActionLink("(" + T("Admin.RecurringPayments.BackToList") + ")", "List")
        - - - + + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("payment-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml index 7c20b8063e..5e496ca4cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/List.cshtml @@ -4,59 +4,54 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.RecurringPayments").Text; }
        - + @T("Admin.RecurringPayments")
        - - - - -
        - @(Html.Telerik().Grid(Model.Data) - .Name("recurringpayments-grid") - .Columns(columns => - { - columns.Bound(x => x.CustomerId) - .Template(x => Html.ActionLink(x.CustomerEmail, "Edit", "Customer", new { id = x.CustomerId }, new { })) - .ClientTemplate("\"><#= CustomerEmail #>") - .Width(100); - columns.Bound(x => x.CycleLength) - .Width(50); - columns.Bound(x => x.CyclePeriodStr) - .Width(100) - .Centered(); - columns.Bound(x => x.IsActive) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsActive)) - .ClientTemplate(@Html.SymbolForBool("IsActive")) - .Centered(); - columns.Bound(x => x.StartDate) - .Width(100) - .Centered(); - columns.Bound(x => x.NextPaymentDate) - .Width(100); - columns.Bound(x => x.TotalCycles) - .Width(50); - columns.Bound(x => x.CyclesRemaining) - .Width(50); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); - }) - .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "RecurringPayment")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        \ No newline at end of file + +
        + @(Html.Telerik().Grid(Model.Data) + .Name("recurringpayments-grid") + .Columns(columns => + { + columns.Bound(x => x.CustomerId) + .Template(x => Html.ActionLink(x.CustomerEmail, "Edit", "Customer", new { id = x.CustomerId }, new { })) + .ClientTemplate("\"><#= CustomerEmail #>") + .Width(100); + columns.Bound(x => x.CycleLength) + .Width(50); + columns.Bound(x => x.CyclePeriodStr) + .Width(100) + .Centered(); + columns.Bound(x => x.IsActive) + .Width(100) + .Template(item => @Html.SymbolForBool(item.IsActive)) + .ClientTemplate(@Html.SymbolForBool("IsActive")) + .Centered(); + columns.Bound(x => x.StartDate) + .Width(100); + columns.Bound(x => x.NextPaymentDate) + .Width(100); + columns.Bound(x => x.TotalCycles) + .Width(50); + columns.Bound(x => x.CyclesRemaining) + .Width(50); + columns.Bound(x => x.Id) + .Width(100) + .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id }, new { @class = "t-button" })) + .ClientTemplate("\" class=\"t-button\">" + T("Admin.Common.Edit").Text + "") + .Title(String.Empty) + .HtmlAttributes(new { align = "right" }); + }) + .Pageable(settings => settings.Total(Model.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "RecurringPayment")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        diff --git a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/_CreateOrUpdate.cshtml index 9091971da4..5d2e13b9ef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/RecurringPayment/_CreateOrUpdate.cshtml @@ -5,24 +5,27 @@ @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) -@Html.SmartStore().TabStrip().Name("recurringpayment-edit").Items(x => + +@Html.SmartStore().TabStrip().Name("recurringpayment-edit").Style(TabsStyle.Material).Items(x => { - var tabInfo = x.Add().Text(T("Admin.RecurringPayments.Info").Text).Content(TabInfo()).Selected(true); - var tabHistory = x.Add().Text(T("Admin.RecurringPayments.History").Text).Content(TabHistory()); + x.Add().Text(T("Admin.RecurringPayments.Info").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.RecurringPayments.History").Text).Content(TabHistory()); //generate an event EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "recurringpayment-edit", this.Html, this.Model)); }) + @helper TabInfo() - { - +{ @@ -30,15 +33,17 @@ @Html.SmartLabelFor(model => model.CustomerEmail) +
        + @Html.ActionLink(Model.CustomerEmail, "Edit", "Customer", new { id = Model.CustomerId }, new { }) +
        + @@ -56,7 +61,7 @@ @Html.SmartLabelFor(model => model.TotalCycles) @@ -65,15 +70,19 @@ @Html.SmartLabelFor(model => model.CyclesRemaining) +
        + @Model.CyclesRemaining +
        + @@ -81,7 +90,9 @@ @Html.SmartLabelFor(model => model.StartDate) @@ -95,28 +106,35 @@
        @Html.SmartLabelFor(model => model.InitialOrderId) - @Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = Model.InitialOrderId }, new { }) +
        + @Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = Model.InitialOrderId }, new { }) +
        - @Html.ActionLink(Model.CustomerEmail, "Edit", "Customer", new { id = Model.CustomerId }, new { }) -
        @Html.SmartLabelFor(model => model.CycleLength) - @Html.Telerik().IntegerTextBoxFor(model => model.CycleLength).MinValue(1) + @Html.EditorFor(model => model.CycleLength) @Html.ValidationMessageFor(model => model.CycleLength)
        - @Html.Telerik().IntegerTextBoxFor(model => model.TotalCycles).MinValue(1) + @Html.EditorFor(model => model.TotalCycles) @Html.ValidationMessageFor(model => model.TotalCycles)
        - @Model.CyclesRemaining -
        @Html.SmartLabelFor(model => model.PaymentType) - @Model.PaymentType +
        + @Model.PaymentType +
        - @Model.StartDate +
        + @Html.DisplayFor(model => model.StartDate) +
        } + @helper TabHistory() - { - +{ + - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml index ef688bf949..6241edb6bf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/Edit.cshtml @@ -9,25 +9,39 @@ @T("Admin.ReturnRequests.EditReturnRequestDetails") @Html.ActionLink("(" + T("Admin.ReturnRequests.BackToList") + ")", "List")
        - @if(Model.CanAccept) - { - - } + } - @if(Model.CanSendEmailToCustomer) - { - - } + @if (Model.CanSendEmailToCustomer) + { + + } - - - + + +
        - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } + @Html.DeleteConfirmation("returnrequest-delete") -@Html.Partial("~/Administration/Views/Order/_AutoUpdateOrderItem.cshtml", Model.AutoUpdateOrderItem) \ No newline at end of file +@{ Html.RenderPartial("~/Administration/Views/Order/_AutoUpdateOrderItem.cshtml", Model.AutoUpdateOrderItem); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml index c9a62c9b5c..3ee82d345f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml @@ -13,96 +13,76 @@ -
        - @if (!String.IsNullOrEmpty(Model.NextPaymentDate)) - { - @T("Admin.RecurringPayments.History.NextPaymentDate") - - : @Model.NextPaymentDate - - - } - @if (Model.CanCancelRecurringPayment) - { - - } + +
        + @if (!String.IsNullOrEmpty(Model.NextPaymentDate)) + { +
        + @T("Admin.RecurringPayments.History.NextPaymentDate"): @Model.NextPaymentDate +
        + +
        + +
        + } + @if (Model.CanCancelRecurringPayment) + { +
        + +
        + } +
        - - - - +
        +
        + @Html.SmartLabelFor(model => model.SearchId) + @Html.TextBoxFor(model => Model.SearchId, new { @class = "form-control" }) +
        @if (Model.AvailableStores.Count > 1) { -
        - - - +
        + @Html.SmartLabelFor(model => model.SearchStoreId) + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All"), new { @class = "form-control" }) +
        } - - - - - - - - -
        - @Html.SmartLabelFor(model => model.SearchId) - - @Html.EditorFor(model => Model.SearchId) -
        - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All")) -
        - @Html.SmartLabelFor(model => model.SearchReturnRequestStatusId) - - @Html.DropDownListFor(model => model.SearchReturnRequestStatusId, Model.AvailableReturnRequestStatus, T("Admin.Common.All")) -
        -   - - -
        - -

        +
        + @Html.SmartLabelFor(model => model.SearchReturnRequestStatusId) + @Html.DropDownListFor(model => model.SearchReturnRequestStatusId, Model.AvailableReturnRequestStatus, T("Admin.Common.All")) +
        +
        + @Html.SmartLabel(string.Empty, string.Empty) + +
        + - - - - -
        - @(Html.Telerik().Grid() - .Name("returnrequests-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .Centered() - .Width(80); - columns.Bound(x => x.ProductName) - .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.Quantity) - .Width(80) - .Centered(); - columns.Bound(x => x.CustomerId) - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Customer", new { id = x.CustomerId }, new { })) - .ClientTemplate("\"><#= CustomerFullName #>"); - columns.Bound(x => x.OrderId) - .Width(80) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) - .ClientTemplate("\"><#= OrderId #>"); - columns.Bound(x => x.ReturnRequestStatusStr) - .Width(180); - columns.Bound(x => x.CreatedOn) - .Width(180); - columns.Bound(x => x.StoreName) - .Hidden(Model.AvailableStores.Count <= 1); - columns.Bound(x => x.Id) - .Width(100) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ReturnRequest")) - .ClientEvents(events => events.OnDataBinding("returnRequestGrid_onDataBinding")) - .PreserveGridState() - .EnableCustomBinding(true)) -
        +
        + @(Html.Telerik().Grid() + .Name("returnrequests-grid") + .Columns(columns => + { + columns.Bound(x => x.Id) + .Centered() + .Width(100); + columns.Bound(x => x.ProductName) + .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) + .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); + columns.Bound(x => x.Quantity) + .Width(100) + .Centered(); + columns.Bound(x => x.CustomerId) + .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Customer", new { id = x.CustomerId }, new { })) + .ClientTemplate("\"><#= CustomerFullName #>"); + columns.Bound(x => x.OrderId) + .Width(100) + .Centered() + .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) + .ClientTemplate("\"><#= OrderId #>"); + columns.Bound(x => x.ReturnRequestStatusStr) + .Width(180); + columns.Bound(x => x.CreatedOn) + .Width(180); + columns.Bound(x => x.StoreName) + .Hidden(Model.AvailableStores.Count <= 1); + columns.Bound(x => x.Id) + .Centered() + .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id }, new { @class = "t-button" })) + .ClientTemplate("\" class=\"t-button\">" + T("Admin.Common.Edit").Text + "") + .Title(String.Empty) + .HtmlAttributes(new { align = "right", @class = "omega" }); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ReturnRequest")) + .ClientEvents(events => events.OnDataBinding("returnRequestGrid_onDataBinding")) + .PreserveGridState() + .EnableCustomBinding(true)) +
        + + @Html.SmartCssFiles(this.Url, ResourceLocation.Head) + @Html.SmartScripts(this.Url, ResourceLocation.Head) + + + + + + + + + + + +
        +
        + + + + +
        +
        +
        Loading directories...
        + +
        +
        +
          +
          +
          + + +
          +
          + +
          +
          + + + + +
          +
          + + +
          +
          + + + +
          +
          + +
          +
          +
          +
          +
          +
          Loading files...
          +
          +
          +
          +
          +
          + This folder is empty +
          +
          + No files found +
          +
            +
            +
            +
            +    © 2013 - RoxyFileman + +
            Status bar
            +
            + + +
            +
            + +
            +
            + +
            +
            +
            +
            +
            +
            +
            + + +
            +
            + +
            +
            +
            + +
            + + @Html.SmartScripts(this.Url, ResourceLocation.Foot) + @Scripts.Render("~/bundles/roxyfm") + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, "~/Administration/Content/filemanager/lang/", "*.js", null) + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml index a1018ad9ea..7e4b02b0a1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml @@ -1,9 +1,8 @@ -@model ScheduleTaskModel -@using SmartStore +@using SmartStore @using SmartStore.Core +@model ScheduleTaskModel @{ ViewBag.Title = T("Admin.System.ScheduleTasks.EditTask") + " - " + Model.Name; - var cronHelpLink = GenerateHelpUrl("Managing+Scheduled+Tasks#ManagingScheduledTasks-Cron"); var returnUrl = ((string)ViewBag.ReturnUrl).NullEmpty(); } @@ -15,22 +14,41 @@
            - + @ViewBag.Title (@T("Common.Back"))
            - - - @if (!Model.IsRunning) + + + @if (!Model.LastHistoryEntry.IsRunning) { -  @T("Admin.System.ScheduleTasks.RunNow") + + + @T("Admin.System.ScheduleTasks.RunNow") + }
            @Html.ValidationSummary(false) - + + @Html.SmartStore().TabStrip().Name("schedule-task-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Common.General").Text).Content(TabGeneral()).Selected(true); + x.Add().Text(T("Common.History").Text).Content(TabHistory()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "schedule-task-edit", this.Html, this.Model)); + }) +} + +@helper TabGeneral() +{ + + + + - @if (Model.Duration.HasValue()) + @if (Model.LastHistoryEntry.Duration.HasValue()) { } - @if (Model.LastError.HasValue()) + @if (Model.LastHistoryEntry.Error.HasValue()) { - - if (Model.LastSuccess.HasValue && Model.LastSuccess != Model.LastEnd) + if (Model.LastHistoryEntry.SucceededOn.HasValue && Model.LastHistoryEntry.SucceededOn != Model.LastHistoryEntry.FinishedOn) { } @@ -108,7 +150,9 @@ @Html.SmartLabelFor(model => model.NextRun) } @@ -124,7 +168,7 @@ @@ -136,6 +180,75 @@
            @@ -41,6 +59,16 @@ @Html.ValidationMessageFor(model => model.Name)
            + @Html.SmartLabelFor(model => model.RunPerMachine) + +
            + @T(Model.RunPerMachine ? "Common.Yes" : "Common.No") +
            +
            @Html.SmartLabelFor(model => model.Enabled) @@ -61,42 +89,56 @@
            - @Html.SmartLabelFor(model => model.LastStart) + @Html.SmartLabelFor(model => model.LastHistoryEntry.StartedOn) - - @(Model.LastStart.HasValue ? Model.LastStart.Value.ToString("g") : T("Common.Never").Text) +
            + @if (Model.LastHistoryEntry.Id != 0) + { + @Html.DisplayFor(model => model.LastHistoryEntry.StartedOn) + } + else + { + @T("Common.Never") + } +
            - @Html.SmartLabelFor(model => model.Duration) + @Html.SmartLabelFor(model => model.LastHistoryEntry.Duration) - @Html.DisplayFor(model => model.Duration) +
            + @Html.DisplayFor(model => model.LastHistoryEntry.Duration) +
            - @Html.SmartLabelFor(model => model.LastError) + + @Html.SmartLabelFor(model => model.LastHistoryEntry.Error) - @Html.DisplayFor(model => model.LastError) +
            +
            @Html.DisplayFor(model => model.LastHistoryEntry.Error)
            +
            - @Html.SmartLabelFor(model => model.LastSuccess) + @Html.SmartLabelFor(model => model.LastHistoryEntry.SucceededOn) - @Model.LastSuccess.Value.ToString("g") +
            + @Html.DisplayFor(model => model.LastHistoryEntry.SucceededOn) +
            - @Model.NextRun.Value.ToString("g") +
            + @Html.DisplayFor(model => model.NextRun) +
              -
            @T("Admin.System.ScheduleTasks.CronHelp", cronHelpLink)
            +
            @T("Admin.System.ScheduleTasks.CronHelp", GenerateHelpUrl(HelpTopic.CronExpressions))
            } +@helper TabHistory() +{ +
            + @Html.Raw((string)ViewBag.HistoryCleanupNote) + +
            + + +
            + @(Html.Telerik().Grid() + .Name("schedule-task-history-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id).RouteKey("id"); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("HistoryList", "ScheduleTask", new { taskId = Model.Id }) + .Delete("DeleteHistoryEntry", "ScheduleTask"); + }) + .ClientEvents(e => + { + e.OnRowDataBound("OnTaskHistoryGridRowDataBound"); + }) + .Columns(columns => + { + columns.Bound(x => x.StartedOn) + .Title(T("Common.ExecutedOn")) + .Width(260) + .ClientTemplate( + "
            " + + "
            <#= StartedOnString #>
            " + + "
            <#= StartedOnPretty #>
            " + + "
            "); + columns.Bound(x => x.FinishedOn) + .Title(T("Common.FinishedOn")) + .Width(260) + .ClientTemplate( + "
            " + + "
            <#= FinishedOnString #>
            " + + "
            <#= FinishedOnPretty #>
            " + + "
            "); + columns.Bound(x => x.Duration) + .Width(260); + columns.Bound(x => x.Succeeded) + .ClientTemplate( + "<# if(Succeeded){ #>" + @Html.SymbolForBool("Succeeded") + "" + T("Common.Succeeded") + "<# } #>" + + "<# if(!Succeeded){ #>
            " + T("Common.Error") + ":
            " + T("Common.Error") + ":  <#= Error #>
            <# } #>"); + columns.Bound(x => x.MachineName); + columns.Command(commands => + { + commands.Delete().Localize(T); + }) + .Width(200) + .HtmlAttributes(new { align = "right" }); + }) + .Pageable(settings => settings.PageSize((int)ViewBag.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true)) +
            +} + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml index 2ad6b7fb2e..dfe34c40b8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml @@ -1,7 +1,5 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title ViewBag.Title = T("Admin.Configuration.Settings.AllSettings").Text; } @using Telerik.Web.Mvc.UI; @@ -17,55 +15,63 @@ @T("Admin.Configuration.Settings.AllSettings.Description") - - - - -
            - @(Html.Telerik().Grid() - .Name("settings-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name).Width("25%"); - columns.Bound(x => x.Value).Width("55%").EditorTemplateName("MultilineText"); - columns.Bound(x => x.Store).EditorTemplateName("Store").Width("15%"); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }); +
            + @(Html.Telerik().Grid() + .Name("settings-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name).Width("25%"); + columns.Bound(x => x.Value) + .Width("55%") + .HtmlAttributes(new { style = "max-width: 600px;" }) + .EditorTemplateName("MultilineText"); + columns.Bound(x => x.Store).EditorTemplateName("Store").Width("15%"); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right" }); + }) + .ToolBar(x => x.Insert()) + .Editable(x => + { + x.Mode(GridEditMode.InLine); + }) + .Filterable() + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("AllSettings", "Setting") + .Update("SettingUpdate", "Setting") + .Delete("SettingDelete", "Setting") + .Insert("SettingAdd", "Setting"); + }) + .ClientEvents(x => x.OnError("grid_onError").OnEdit("grid_onStoreEdit")) + .PreserveGridState() + .EnableCustomBinding(true)) +
            - }) - .ToolBar(x => x.Insert()) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .Filterable() - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("AllSettings", "Setting") - .Update("SettingUpdate", "Setting") - .Delete("SettingDelete", "Setting") - .Insert("SettingAdd", "Setting"); - }) - .ClientEvents(x => x.OnError("grid_onError").OnEdit("grid_onStoreEdit")) - .PreserveGridState() - .EnableCustomBinding(true)) - -
            + function grid_onStoreEdit(e) { + if (e.mode == "edit") { + _.delay(function () { + $('#Store') + .data('select-selected-id', e.dataItem['StoreId']) + .data('select-init-text', e.dataItem['Store']) + .val(e.dataItem['StoreId']) + .trigger('change'); + }, 0); + } + } + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml index 973aa0703f..eb065d6098 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml @@ -1,33 +1,40 @@ @model BlogSettingsModel @using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Blog").Text; } @using (Html.BeginForm()) {
            - + @T("Admin.Configuration.Settings.Blog")
            - +
            - @Html.Action("StoreScopeConfiguration", "Setting") + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + @Html.ValidationSummary(false) - - - - - +
            +
            +
            + @Html.SmartLabelFor(model => model.Enabled) +
            +
            + @Html.SettingEditorFor(model => model.Enabled, Html.CheckBoxFor(model => model.Enabled, new { data_toggler_for = "#pnlBlogEnabled" })) + @Html.ValidationMessageFor(model => model.Enabled) +
            +
            +
            + +
            - @Html.SmartLabelFor(model => model.Enabled) - - @Html.SettingEditorFor(model => model.Enabled) - @Html.ValidationMessageFor(model => model.Enabled) -
            - - + + + + + + + + + + + + + + +
            @Html.SmartLabelFor(model => model.PostsPageSize) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml index 56664aafbb..b0cdb0d98e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml @@ -1,7 +1,5 @@ @model CatalogSettingsModel -@using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Catalog").Text; } @using (Html.BeginForm()) @@ -12,170 +10,73 @@ @T("Admin.Configuration.Settings.Catalog")
            - +
            - - - @Html.Action("StoreScopeConfiguration", "Setting") @Html.ValidationSummary(false) - @(Html.SmartStore().TabStrip().Name("catalogsettings-edit").Items(x => + @(Html.SmartStore().TabStrip().Name("catalogsettings-edit").Style(TabsStyle.Material).Items(x => { x.Add().Text(T("Admin.Configuration.Settings.Catalog.MiscSettings").Text).Content(@TabMiscSettings()).Selected(true); x.Add().Text(T("Admin.Configuration.Settings.Catalog.ProductListSettings").Text).Content(@TabProductListSettings()); x.Add().Text(T("Admin.Configuration.Settings.Catalog.ProductDetailSettings").Text).Content(@TabProductDetailSettings()); x.Add().Text(T("Admin.Configuration.Settings.Catalog.UserSettings").Text).Content(@TabUserSettings()); - x.Add().Text(T("Admin.Configuration.Settings.Catalog.ProductSearchSettings").Text).Content(@TabProductSearchSettings()); + x.Add().Text(T("Admin.Catalog.Manufacturers").Text).Content(@TabBrandsSettings()); })) @helper TabMiscSettings() { + + + + + + + + + + + + + + + +
            +
            +
            @T("Admin.CatalogSettings.Homepage")
            +
            +
            + @Html.SmartLabelFor(model => model.ShowBestsellersOnHomepage) + + @Html.SettingEditorFor(model => model.ShowBestsellersOnHomepage, Html.CheckBoxFor(model => model.ShowBestsellersOnHomepage, new { data_toggler_for = "#pnlNumberOfBestsellersOnHomepage" })) + @Html.ValidationMessageFor(model => model.ShowBestsellersOnHomepage) +
            + @Html.SmartLabelFor(model => model.NumberOfBestsellersOnHomepage) + + @Html.SettingEditorFor(model => model.NumberOfBestsellersOnHomepage) + @Html.ValidationMessageFor(model => model.NumberOfBestsellersOnHomepage) +
            + @Html.SmartLabelFor(model => model.ShowPopularProductTagsOnHomepage) + + @Html.SettingEditorFor(model => model.ShowPopularProductTagsOnHomepage) + @Html.ValidationMessageFor(model => model.ShowPopularProductTagsOnHomepage) +
            + + + + + + + + + - +
            +
            +
            @T("Admin.CatalogSettings.ProductDisplay")
            +
            +
            @Html.SmartLabelFor(model => model.ShowProductSku) @@ -233,129 +134,141 @@
            - @Html.SmartLabelFor(model => model.IgnoreDiscounts) + @Html.SmartLabelFor(model => model.ShowDefaultQuantityUnit) - @Html.SettingEditorFor(model => model.IgnoreDiscounts) - @Html.ValidationMessageFor(model => model.IgnoreDiscounts) + @Html.SettingEditorFor(model => model.ShowDefaultQuantityUnit) + @Html.ValidationMessageFor(model => model.ShowDefaultQuantityUnit)
            - @Html.SmartLabelFor(model => model.IgnoreFeaturedProducts) + @Html.SmartLabelFor(model => model.ShowDefaultDeliveryTime) - @Html.SettingEditorFor(model => model.IgnoreFeaturedProducts) - @Html.ValidationMessageFor(model => model.IgnoreFeaturedProducts) + @Html.SettingEditorFor(model => model.ShowDefaultDeliveryTime) + @Html.ValidationMessageFor(model => model.ShowDefaultDeliveryTime)
            + @Html.SmartLabelFor(model => model.DeliveryTimeIdForEmptyStock) + + @Html.SettingEditorFor(model => model.DeliveryTimeIdForEmptyStock, @Html.DropDownListFor(model => model.DeliveryTimeIdForEmptyStock, Model.AvailableDeliveryTimes, T("Common.Unspecified"))) + @Html.ValidationMessageFor(model => model.DeliveryTimeIdForEmptyStock) +
            - @Html.SmartLabelFor(model => model.CompareProductsEnabled) + @Html.SmartLabelFor(model => model.HideProductDefaultPictures) - @Html.SettingEditorFor(model => model.CompareProductsEnabled) - @Html.ValidationMessageFor(model => model.CompareProductsEnabled) + @Html.SettingEditorFor(model => model.HideProductDefaultPictures) + @Html.ValidationMessageFor(model => model.HideProductDefaultPictures)
            + + + + + + - + - + +
            +
            +
            @T("Admin.CatalogSettings.Prices")
            +
            +
            - @Html.SmartLabelFor(model => model.IncludeShortDescriptionInCompareProducts) + @Html.SmartLabelFor(model => model.DisplayTextForZeroPrices) - @Html.SettingEditorFor(model => model.IncludeShortDescriptionInCompareProducts) - @Html.ValidationMessageFor(model => model.IncludeShortDescriptionInCompareProducts) + @Html.SettingEditorFor(model => model.DisplayTextForZeroPrices) + @Html.ValidationMessageFor(model => model.DisplayTextForZeroPrices)
            - @Html.SmartLabelFor(model => model.IncludeFullDescriptionInCompareProducts) + @Html.SmartLabelFor(model => model.PriceDisplayStyle) - @Html.SettingEditorFor(model => model.IncludeFullDescriptionInCompareProducts) - @Html.ValidationMessageFor(model => model.IncludeFullDescriptionInCompareProducts) + @Html.EnumSettingEditorFor(model => model.PriceDisplayStyle) + @Html.ValidationMessageFor(model => model.PriceDisplayStyle)
            - @Html.SmartLabelFor(model => model.ShowBestsellersOnHomepage) + @Html.SmartLabelFor(model => model.IgnoreDiscounts) - @Html.SettingEditorFor(model => model.ShowBestsellersOnHomepage) - @Html.ValidationMessageFor(model => model.ShowBestsellersOnHomepage) + @Html.SettingEditorFor(model => model.IgnoreDiscounts) + @Html.ValidationMessageFor(model => model.IgnoreDiscounts)
            - @Html.SmartLabelFor(model => model.NumberOfBestsellersOnHomepage) + @Html.SmartLabelFor(model => model.ApplyTierPricePercentageToAttributePriceAdjustments) - @Html.SettingEditorFor(model => model.NumberOfBestsellersOnHomepage) - @Html.ValidationMessageFor(model => model.NumberOfBestsellersOnHomepage) + @Html.SettingEditorFor(model => model.ApplyTierPricePercentageToAttributePriceAdjustments) + @Html.ValidationMessageFor(model => model.ApplyTierPricePercentageToAttributePriceAdjustments)
            -
            - @Html.SmartLabelFor(model => model.ShowManufacturersOnHomepage) - - @Html.SettingEditorFor(model => model.ShowManufacturersOnHomepage) - @Html.ValidationMessageFor(model => model.ShowManufacturersOnHomepage) + + + - + - - - + + + + + + + + + +
            +
            +
            @T("Admin.CatalogSettings.CompareProducts")
            +
            - @Html.SmartLabelFor(model => model.ManufacturersBlockItemsToDisplay) + @Html.SmartLabelFor(model => model.CompareProductsEnabled) - @Html.SettingEditorFor(model => model.ManufacturersBlockItemsToDisplay) - @Html.ValidationMessageFor(model => model.ManufacturersBlockItemsToDisplay) + @Html.SettingEditorFor(model => model.CompareProductsEnabled, Html.CheckBoxFor(model => model.CompareProductsEnabled, new { data_toggler_for = "#pnlCompareProducts" })) + @Html.ValidationMessageFor(model => model.CompareProductsEnabled)
            - @Html.SmartLabelFor(model => model.ShowManufacturerPictures) - - @Html.SettingEditorFor(model => model.ShowManufacturerPictures) - @Html.ValidationMessageFor(model => model.ShowManufacturerPictures) +
            + @Html.SmartLabelFor(model => model.IncludeShortDescriptionInCompareProducts) + + @Html.SettingEditorFor(model => model.IncludeShortDescriptionInCompareProducts) + @Html.ValidationMessageFor(model => model.IncludeShortDescriptionInCompareProducts) +
            + @Html.SmartLabelFor(model => model.IncludeFullDescriptionInCompareProducts) + + @Html.SettingEditorFor(model => model.IncludeFullDescriptionInCompareProducts) + @Html.ValidationMessageFor(model => model.IncludeFullDescriptionInCompareProducts) +
            + + + + - - - - - - - - - - - - - @@ -364,32 +277,34 @@ @Html.SmartLabelFor(model => model.HtmlTextCollapsedHeight) + + + +
            +
            +
            @T("Common.Misc")
            +
            - @Html.SmartLabelFor(model => model.HideManufacturerDefaultPictures) - - @Html.SettingEditorFor(model => model.HideManufacturerDefaultPictures) - @Html.ValidationMessageFor(model => model.HideManufacturerDefaultPictures) -
            - @Html.SmartLabelFor(model => model.HideCategoryDefaultPictures) - - @Html.SettingEditorFor(model => model.HideCategoryDefaultPictures) - @Html.ValidationMessageFor(model => model.HideCategoryDefaultPictures) -
            - @Html.SmartLabelFor(model => model.HideProductDefaultPictures) - - @Html.SettingEditorFor(model => model.HideProductDefaultPictures) - @Html.ValidationMessageFor(model => model.HideProductDefaultPictures) -
            @Html.SmartLabelFor(model => model.EnableHtmlTextCollapser) - @Html.SettingEditorFor(model => model.EnableHtmlTextCollapser) + @Html.SettingEditorFor(model => model.EnableHtmlTextCollapser, Html.CheckBoxFor(model => model.EnableHtmlTextCollapser, new { data_toggler_for = "#pnlHtmlTextCollapsedHeight" })) @Html.ValidationMessageFor(model => model.EnableHtmlTextCollapser)
            - @Html.SettingEditorFor(model => model.HtmlTextCollapsedHeight) + @Html.SettingEditorFor(model => model.HtmlTextCollapsedHeight, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.HtmlTextCollapsedHeight)
            - @Html.SmartLabelFor(model => model.ShowDefaultQuantityUnit) + @Html.SmartLabelFor(model => model.MaxItemsToDisplayInCatalogMenu) - @Html.SettingEditorFor(model => model.ShowDefaultQuantityUnit) - @Html.ValidationMessageFor(model => model.ShowDefaultQuantityUnit) + @Html.SettingEditorFor(model => model.MaxItemsToDisplayInCatalogMenu) + @Html.ValidationMessageFor(model => model.MaxItemsToDisplayInCatalogMenu) +
            + @Html.SmartLabelFor(model => model.IgnoreFeaturedProducts) + + @Html.SettingEditorFor(model => model.IgnoreFeaturedProducts) + @Html.ValidationMessageFor(model => model.IgnoreFeaturedProducts)
            } - + @helper TabProductListSettings() { - - - - + - - - - - - - - @@ -440,65 +337,26 @@ @Html.SmartLabelFor(model => model.SubCategoryDisplayType) -
            -
            -
            @T("Common.Navigation")
            -
            -
            @Html.SmartLabelFor(model => model.ShowProductsFromSubcategories) @@ -404,35 +319,17 @@ @Html.SmartLabelFor(model => model.IncludeFeaturedProductsInNormalLists) - @Html.SettingEditorFor(model => model.IncludeFeaturedProductsInNormalLists) + @Html.SettingEditorFor(model => model.IncludeFeaturedProductsInNormalLists, Html.CheckBoxFor(model => model.IncludeFeaturedProductsInNormalLists, new { data_toggler_for = "#pnlIncludeFeaturedProductsInSubPages" })) @Html.ValidationMessageFor(model => model.IncludeFeaturedProductsInNormalLists)
            - @Html.SmartLabelFor(model => model.ShowCategoryProductNumber) + @Html.SmartLabelFor(model => model.IncludeFeaturedProductsInSubPages) - @Html.SettingEditorFor(model => model.ShowCategoryProductNumber) - @Html.ValidationMessageFor(model => model.ShowCategoryProductNumber) -
            - @Html.SmartLabelFor(model => model.ShowCategoryProductNumberIncludingSubcategories) - - @Html.SettingEditorFor(model => model.ShowCategoryProductNumberIncludingSubcategories) - @Html.ValidationMessageFor(model => model.ShowCategoryProductNumberIncludingSubcategories) -
            - @Html.SmartLabelFor(model => model.CategoryBreadcrumbEnabled) - - @Html.SettingEditorFor(model => model.CategoryBreadcrumbEnabled) - @Html.ValidationMessageFor(model => model.CategoryBreadcrumbEnabled) + @Html.SettingEditorFor(model => model.IncludeFeaturedProductsInSubPages) + @Html.ValidationMessageFor(model => model.IncludeFeaturedProductsInSubPages)
            - @Html.SettingOverrideCheckbox(model => Model.SubCategoryDisplayType) - @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes) + @Html.SettingEditorFor(model => model.SubCategoryDisplayType, @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes)) @Html.ValidationMessageFor(model => model.SubCategoryDisplayType)
            - - - - - - - - - - - - - - + - -
            -
            -
            @T("Filtering.FilterResults")
            -
            -
            - @Html.SmartLabelFor(model => model.FilterEnabled) - - @Html.SettingEditorFor(model => model.FilterEnabled) - @Html.ValidationMessageFor(model => model.FilterEnabled) -
            - @Html.SmartLabelFor(model => model.MaxFilterItemsToDisplay) - - @Html.SettingEditorFor(model => model.MaxFilterItemsToDisplay) - @Html.ValidationMessageFor(model => model.MaxFilterItemsToDisplay) -
            - @Html.SmartLabelFor(model => model.ExpandAllFilterCriteria) + @Html.SmartLabelFor(model => model.ShowSubCategoriesInSubPages) - @Html.SettingEditorFor(model => model.ExpandAllFilterCriteria) - @Html.ValidationMessageFor(model => model.ExpandAllFilterCriteria) + @Html.SettingEditorFor(model => model.ShowSubCategoriesInSubPages) + @Html.ValidationMessageFor(model => model.ShowSubCategoriesInSubPages)
            - @Html.SmartLabelFor(model => model.SortFilterResultsByMatches) + @Html.SmartLabelFor(model => model.ShowDescriptionInSubPages) - @Html.SettingEditorFor(model => model.SortFilterResultsByMatches) - @Html.ValidationMessageFor(model => model.SortFilterResultsByMatches) -
            - - - - @@ -515,151 +373,221 @@ @Html.SmartLabelFor(model => model.DefaultViewMode) - - - - - - - - - -
            -
            -
            @T("Common.List")
            -
            + @Html.SettingEditorFor(model => model.ShowDescriptionInSubPages) + @Html.ValidationMessageFor(model => model.ShowDescriptionInSubPages)
            - @Html.SettingOverrideCheckbox(model => Model.DefaultViewMode) - @Html.DropDownListFor(model => model.DefaultViewMode, Model.AvailableDefaultViewModes) + @Html.SettingEditorFor(model => model.DefaultViewMode, @Html.DropDownListFor(model => model.DefaultViewMode, Model.AvailableDefaultViewModes)) @Html.ValidationMessageFor(model => model.DefaultViewMode)
            - @Html.SmartLabelFor(model => model.DefaultSortOrder) - - @Html.SettingOverrideCheckbox(model => Model.DefaultSortOrder) - @Html.DropDownListFor(model => model.DefaultSortOrder, Model.AvailableSortOrderModes) - @Html.ValidationMessageFor(model => model.DefaultSortOrder) -
            - @Html.SmartLabelFor(model => model.AllowProductViewModeChanging) - - @Html.SettingEditorFor(model => model.AllowProductViewModeChanging) - @Html.ValidationMessageFor(model => model.AllowProductViewModeChanging) -
            - @Html.SmartLabelFor(model => model.DefaultPageSizeOptions) + @Html.SmartLabelFor(model => model.GridStyleListColumnSpan) - @Html.SettingEditorFor(model => model.DefaultPageSizeOptions) - @Html.ValidationMessageFor(model => model.DefaultPageSizeOptions) + @Html.EnumSettingEditorFor(model => model.GridStyleListColumnSpan) + @Html.ValidationMessageFor(model => model.GridStyleListColumnSpan)
            - @Html.SmartLabelFor(model => model.PriceDisplayType) + @Html.SmartLabelFor(model => model.DefaultSortOrder) - @Html.SettingOverrideCheckbox(model => Model.PriceDisplayType) - @Html.DropDownListFor(model => model.PriceDisplayType, Model.AvailablePriceDisplayTypes) - @Html.ValidationMessageFor(model => model.PriceDisplayType) -
            - - - -
            -
            -
            @T("Admin.Catalog.Products")
            -
            + @Html.SettingEditorFor(model => model.DefaultSortOrder, @Html.DropDownListFor(model => model.DefaultSortOrder, Model.AvailableSortOrderModes)) + @Html.ValidationMessageFor(model => model.DefaultSortOrder)
            - @Html.SmartLabelFor(model => model.ShowDeliveryTimesInProductLists) + @Html.SmartLabelFor(model => model.AllowProductViewModeChanging) - @Html.SettingEditorFor(model => model.ShowDeliveryTimesInProductLists) - @Html.ValidationMessageFor(model => model.ShowDeliveryTimesInProductLists) + @Html.SettingEditorFor(model => model.AllowProductViewModeChanging) + @Html.ValidationMessageFor(model => model.AllowProductViewModeChanging)
            - @Html.SmartLabelFor(model => model.ShowBasePriceInProductLists) + @Html.SmartLabelFor(model => model.DefaultProductListPageSize) - @Html.SettingEditorFor(model => model.ShowBasePriceInProductLists) - @Html.ValidationMessageFor(model => model.ShowBasePriceInProductLists) + @Html.SettingEditorFor(model => model.DefaultProductListPageSize) + @Html.ValidationMessageFor(model => model.DefaultProductListPageSize)
            - @Html.SmartLabelFor(model => model.ShowColorSquaresInLists) + @Html.SmartLabelFor(model => model.DefaultPageSizeOptions) - @Html.SettingEditorFor(model => model.ShowColorSquaresInLists) - @Html.ValidationMessageFor(model => model.ShowColorSquaresInLists) + @Html.SettingEditorFor(model => model.DefaultPageSizeOptions) + @Html.ValidationMessageFor(model => model.DefaultPageSizeOptions)
            - @Html.SmartLabelFor(model => model.HideBuyButtonInLists) + @Html.SmartLabelFor(model => model.PriceDisplayType) - @Html.SettingEditorFor(model => model.HideBuyButtonInLists) - @Html.ValidationMessageFor(model => model.HideBuyButtonInLists) + @Html.SettingEditorFor(model => model.PriceDisplayType, @Html.DropDownListFor(model => model.PriceDisplayType, Model.AvailablePriceDisplayTypes)) + @Html.ValidationMessageFor(model => model.PriceDisplayType)
            - @Html.SmartLabelFor(model => model.LabelAsNewForMaxDays) + @Html.SmartLabelFor(model => model.HideCategoryDefaultPictures) - @Html.SettingEditorFor(model => model.LabelAsNewForMaxDays) - @Html.ValidationMessageFor(model => model.LabelAsNewForMaxDays) + @Html.SettingEditorFor(model => model.HideCategoryDefaultPictures) + @Html.ValidationMessageFor(model => model.HideCategoryDefaultPictures)
            - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            +
            +
            @T("Admin.Catalog.Products")
            +
            +
            + @Html.SmartLabelFor(model => model.ShowShortDescriptionInGridStyleLists) + + @Html.SettingEditorFor(model => model.ShowShortDescriptionInGridStyleLists) + @Html.ValidationMessageFor(model => model.ShowShortDescriptionInGridStyleLists) +
            + @Html.SmartLabelFor(model => model.ShowDeliveryTimesInProductLists) + + @Html.SettingEditorFor(model => model.ShowDeliveryTimesInProductLists) + @Html.ValidationMessageFor(model => model.ShowDeliveryTimesInProductLists) +
            + @Html.SmartLabelFor(model => model.ShowManufacturerInGridStyleLists) + + @Html.SettingEditorFor(model => model.ShowManufacturerInGridStyleLists) + @Html.ValidationMessageFor(model => model.ShowManufacturerInGridStyleLists) +
            + @Html.SmartLabelFor(model => model.ShowManufacturerLogoInLists) + + @Html.SettingEditorFor(model => model.ShowManufacturerLogoInLists) + @Html.ValidationMessageFor(model => model.ShowManufacturerLogoInLists) +
            + @Html.SmartLabelFor(model => model.ShowBasePriceInProductLists) + + @Html.SettingEditorFor(model => model.ShowBasePriceInProductLists) + @Html.ValidationMessageFor(model => model.ShowBasePriceInProductLists) +
            + @Html.SmartLabelFor(model => model.ShowProductOptionsInLists) + + @Html.SettingEditorFor(model => model.ShowProductOptionsInLists) + @Html.ValidationMessageFor(model => model.ShowProductOptionsInLists) +
            + @Html.SmartLabelFor(model => model.ShowColorSquaresInLists) + + @Html.SettingEditorFor(model => model.ShowColorSquaresInLists) + @Html.ValidationMessageFor(model => model.ShowColorSquaresInLists) +
            + @Html.SmartLabelFor(model => model.HideBuyButtonInLists) + + @Html.SettingEditorFor(model => model.HideBuyButtonInLists) + @Html.ValidationMessageFor(model => model.HideBuyButtonInLists) +
            + @Html.SmartLabelFor(model => model.LabelAsNewForMaxDays) + + @Html.SettingEditorFor(model => model.LabelAsNewForMaxDays, null, new { postfix = T("Time.Days").Text }) + @Html.ValidationMessageFor(model => model.LabelAsNewForMaxDays) +
            + - - - - - - - - - - - - - - - - - - - -
            -
            -
            @T("Admin.Catalog.ProductTags")
            -
            -
            - @Html.SmartLabelFor(model => model.NumberOfProductTags) - - @Html.SettingEditorFor(model => model.NumberOfProductTags) - @Html.ValidationMessageFor(model => model.NumberOfProductTags) -
            - @Html.SmartLabelFor(model => model.ProductsByTagPageSize) - - @Html.SettingEditorFor(model => model.ProductsByTagPageSize) - @Html.ValidationMessageFor(model => model.ProductsByTagPageSize) -
            - @Html.SmartLabelFor(model => model.ProductsByTagAllowCustomersToSelectPageSize) - - @Html.SettingEditorFor(model => model.ProductsByTagAllowCustomersToSelectPageSize) - @Html.ValidationMessageFor(model => model.ProductsByTagAllowCustomersToSelectPageSize) -
            - @Html.SmartLabelFor(model => model.ProductsByTagPageSizeOptions) - - @Html.SettingEditorFor(model => model.ProductsByTagPageSizeOptions) - @Html.ValidationMessageFor(model => model.ProductsByTagPageSizeOptions) -
            +
            +
            +
            @T("Common.Navigation")
            +
            +
            + @Html.SmartLabelFor(model => model.ShowCategoryProductNumber) + + @Html.SettingEditorFor(model => model.ShowCategoryProductNumber, Html.CheckBoxFor(model => model.ShowCategoryProductNumber, new { data_toggler_for = "#pnlShowCategoryProductNumberIncludingSubcategories" })) + @Html.ValidationMessageFor(model => model.ShowCategoryProductNumber) +
            + @Html.SmartLabelFor(model => model.ShowCategoryProductNumberIncludingSubcategories) + + @Html.SettingEditorFor(model => model.ShowCategoryProductNumberIncludingSubcategories) + @Html.ValidationMessageFor(model => model.ShowCategoryProductNumberIncludingSubcategories) +
            + @Html.SmartLabelFor(model => model.CategoryBreadcrumbEnabled) + + @Html.SettingEditorFor(model => model.CategoryBreadcrumbEnabled) + @Html.ValidationMessageFor(model => model.CategoryBreadcrumbEnabled) +
            + + + + + + + + + +
            +
            +
            @T("Admin.Catalog.ProductTags")
            +
            +
            + @Html.SmartLabelFor(model => model.NumberOfProductTags) + + @Html.SettingEditorFor(model => model.NumberOfProductTags) + @Html.ValidationMessageFor(model => model.NumberOfProductTags) +
            } @helper TabUserSettings() @@ -724,22 +652,33 @@ @Html.SmartLabelFor(model => model.EmailAFriendEnabled) - @Html.SettingEditorFor(model => model.EmailAFriendEnabled) + @Html.SettingEditorFor(model => model.EmailAFriendEnabled, Html.CheckBoxFor(model => model.EmailAFriendEnabled, new { data_toggler_for = "#pnlAllowAnonymousUsersToEmailAFriend" })) @Html.ValidationMessageFor(model => model.EmailAFriendEnabled) - - - @Html.SmartLabelFor(model => model.AllowAnonymousUsersToEmailAFriend) - - - @Html.SettingEditorFor(model => model.AllowAnonymousUsersToEmailAFriend) - @Html.ValidationMessageFor(model => model.AllowAnonymousUsersToEmailAFriend) - - + + + + @Html.SmartLabelFor(model => model.AllowAnonymousUsersToEmailAFriend) + + + @Html.SettingEditorFor(model => model.AllowAnonymousUsersToEmailAFriend) + @Html.ValidationMessageFor(model => model.AllowAnonymousUsersToEmailAFriend) + + + + + @Html.SmartLabelFor(model => model.AllowDifferingEmailAddressForEmailAFriend) + + + @Html.SettingEditorFor(model => model.AllowDifferingEmailAddressForEmailAFriend) + @Html.ValidationMessageFor(model => model.AllowDifferingEmailAddressForEmailAFriend) + + + } - + @helper TabProductDetailSettings() { @@ -748,7 +687,7 @@ @Html.SmartLabelFor(model => model.RecentlyViewedProductsEnabled) @@ -766,7 +705,7 @@ @Html.SmartLabelFor(model => model.RecentlyAddedProductsEnabled) @@ -779,21 +718,12 @@ @Html.ValidationMessageFor(model => model.RecentlyAddedProductsNumber) - - - - @@ -815,15 +745,24 @@ @Html.ValidationMessageFor(model => model.DisplayAllImagesNumber) - - - - + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + +
            - @Html.SettingEditorFor(model => model.RecentlyViewedProductsEnabled) + @Html.SettingEditorFor(model => model.RecentlyViewedProductsEnabled, Html.CheckBoxFor(model => model.RecentlyViewedProductsEnabled, new { data_toggler_for = "#pnlRecentlyViewedProductsNumber" })) @Html.ValidationMessageFor(model => model.RecentlyViewedProductsEnabled)
            - @Html.SettingEditorFor(model => model.RecentlyAddedProductsEnabled) + @Html.SettingEditorFor(model => model.RecentlyAddedProductsEnabled, Html.CheckBoxFor(model => model.RecentlyAddedProductsEnabled, new { data_toggler_for = "#pnlRecentlyAddedProductsNumber" })) @Html.ValidationMessageFor(model => model.RecentlyAddedProductsEnabled)
            - @Html.SmartLabelFor(model => model.ShowShareButton) - - @Html.SettingEditorFor(model => model.ShowShareButton) - @Html.ValidationMessageFor(model => model.ShowShareButton) -
            @Html.SmartLabelFor(model => model.ProductsAlsoPurchasedEnabled) - @Html.SettingEditorFor(model => model.ProductsAlsoPurchasedEnabled) + @Html.SettingEditorFor(model => model.ProductsAlsoPurchasedEnabled, Html.CheckBoxFor(model => model.ProductsAlsoPurchasedEnabled, new { data_toggler_for = "#pnlProductsAlsoPurchasedNumber" })) @Html.ValidationMessageFor(model => model.ProductsAlsoPurchasedEnabled)
            - @Html.SmartLabelFor(model => model.ShowManufacturerPicturesInProductDetail) - - @Html.SettingEditorFor(model => model.ShowManufacturerPicturesInProductDetail) - @Html.ValidationMessageFor(model => model.ShowManufacturerPicturesInProductDetail) -
            + @Html.SmartLabelFor(model => model.ShowManufacturerInProductDetail) + + @Html.SettingEditorFor(model => model.ShowManufacturerInProductDetail, Html.CheckBoxFor(model => model.ShowManufacturerInProductDetail, new { data_toggler_for = "#pnlManufacturerPictures" })) + @Html.ValidationMessageFor(model => model.ShowManufacturerInProductDetail) +
            + @Html.SmartLabelFor(model => model.ShowManufacturerPicturesInProductDetail) + + @Html.SettingEditorFor(model => model.ShowManufacturerPicturesInProductDetail) + @Html.ValidationMessageFor(model => model.ShowManufacturerPicturesInProductDetail) +
            @Html.SmartLabelFor(model => model.ShowDeliveryTimesInProductDetail) @@ -833,16 +772,6 @@ @Html.ValidationMessageFor(model => model.ShowDeliveryTimesInProductDetail)
            - @Html.SmartLabelFor(model => model.DeliveryTimeIdForEmptyStock) - - @Html.SettingOverrideCheckbox(model => model.DeliveryTimeIdForEmptyStock) - @Html.DropDownListFor(model => model.DeliveryTimeIdForEmptyStock, Model.AvailableDeliveryTimes, T("Common.Unspecified")) - @Html.ValidationMessageFor(model => model.DeliveryTimeIdForEmptyStock) -
            @Html.SmartLabelFor(model => model.EnableDynamicPriceUpdate) @@ -852,15 +781,15 @@ @Html.ValidationMessageFor(model => model.EnableDynamicPriceUpdate)
            - @Html.SmartLabelFor(model => model.BundleItemShowBasePrice) - - @Html.SettingEditorFor(model => model.BundleItemShowBasePrice) - @Html.ValidationMessageFor(model => model.BundleItemShowBasePrice) -
            + @Html.SmartLabelFor(model => model.BundleItemShowBasePrice) + + @Html.SettingEditorFor(model => model.BundleItemShowBasePrice) + @Html.ValidationMessageFor(model => model.BundleItemShowBasePrice) +
            @Html.SmartLabelFor(model => model.ShowVariantCombinationPriceAdjustment) @@ -870,6 +799,15 @@ @Html.ValidationMessageFor(model => model.ShowVariantCombinationPriceAdjustment)
            + @Html.SmartLabelFor(model => model.ShowLoginForPriceNote) + + @Html.SettingEditorFor(model => model.ShowLoginForPriceNote) + @Html.ValidationMessageFor(model => model.ShowLoginForPriceNote) +
            @Html.SmartLabelFor(model => model.ShowLinkedAttributeValueQuantity) @@ -888,88 +826,93 @@ @Html.ValidationMessageFor(model => model.ShowLinkedAttributeValueImage)
            + @Html.SmartLabelFor(model => model.ShowShareButton) + + @Html.SettingEditorFor(model => model.ShowShareButton, Html.CheckBoxFor(model => model.ShowShareButton, new { data_toggler_for = "#pnlPageShareCode" })) + @Html.ValidationMessageFor(model => model.ShowShareButton) +
            + @Html.SmartLabelFor(model => model.PageShareCode) + + @Html.TextAreaFor(model => model.PageShareCode, new { style = "height:150px" }) + @Html.ValidationMessageFor(model => model.PageShareCode) +
            -} -@helper TabProductSearchSettings() +} + +@helper TabBrandsSettings() { - + - + - + + + + + + + + + - - - - - - - - - - - - - - - - +
            - @Html.SmartLabelFor(model => model.SearchPageProductsPerPage) + @Html.SmartLabelFor(model => model.ShowManufacturersOnHomepage) - @Html.SettingEditorFor(model => model.SearchPageProductsPerPage) - @Html.ValidationMessageFor(model => model.SearchPageProductsPerPage) + @Html.SettingEditorFor(model => model.ShowManufacturersOnHomepage, Html.CheckBoxFor(model => model.ShowManufacturersOnHomepage, new { data_toggler_for = "#pnlManufacturerItemsToDisplayOnHomepage" })) + @Html.ValidationMessageFor(model => model.ShowManufacturersOnHomepage)
            - @Html.SmartLabelFor(model => model.ProductSearchAllowCustomersToSelectPageSize) + @Html.SmartLabelFor(model => model.ManufacturerItemsToDisplayOnHomepage) - @Html.SettingEditorFor(model => model.ProductSearchAllowCustomersToSelectPageSize) - @Html.ValidationMessageFor(model => model.ProductSearchAllowCustomersToSelectPageSize) + @Html.SettingEditorFor(model => model.ManufacturerItemsToDisplayOnHomepage) + @Html.ValidationMessageFor(model => model.ManufacturerItemsToDisplayOnHomepage)
            - @Html.SmartLabelFor(model => model.ProductSearchPageSizeOptions) + @Html.SmartLabelFor(model => model.ShowManufacturersInOffCanvas) - @Html.SettingEditorFor(model => model.ProductSearchPageSizeOptions) - @Html.ValidationMessageFor(model => model.ProductSearchPageSizeOptions) + @Html.SettingEditorFor(model => model.ShowManufacturersInOffCanvas, Html.CheckBoxFor(model => model.ShowManufacturersInOffCanvas, new { data_toggler_for = "#pnlManufacturerItemsToDisplayInOffCanvas" })) + @Html.ValidationMessageFor(model => model.ShowManufacturersInOffCanvas) +
            + @Html.SmartLabelFor(model => model.ManufacturerItemsToDisplayInOffCanvasMenu) + + @Html.SettingEditorFor(model => model.ManufacturerItemsToDisplayInOffCanvasMenu) + @Html.ValidationMessageFor(model => model.ManufacturerItemsToDisplayInOffCanvasMenu) +
            + @Html.SmartLabelFor(model => model.ShowManufacturerPictures) + + @Html.SettingEditorFor(model => model.ShowManufacturerPictures) + @Html.ValidationMessageFor(model => model.ShowManufacturerPictures)
            - @Html.SmartLabelFor(model => model.SuppressSkuSearch) - - @Html.SettingEditorFor(model => model.SuppressSkuSearch) - @Html.ValidationMessageFor(model => model.SuppressSkuSearch) -
            - @Html.SmartLabelFor(model => model.SearchDescriptions) - - @Html.SettingEditorFor(model => model.SearchDescriptions) - @Html.ValidationMessageFor(model => model.SearchDescriptions) -
            -
            -
            - @Html.SmartLabelFor(model => model.ProductSearchAutoCompleteEnabled) + @Html.SmartLabelFor(model => model.HideManufacturerDefaultPictures) - @Html.SettingEditorFor(model => model.ProductSearchAutoCompleteEnabled) - @Html.ValidationMessageFor(model => model.ProductSearchAutoCompleteEnabled) + @Html.SettingEditorFor(model => model.HideManufacturerDefaultPictures) + @Html.ValidationMessageFor(model => model.HideManufacturerDefaultPictures)
            - @Html.SmartLabelFor(model => model.ShowProductImagesInSearchAutoComplete) - - @Html.SettingEditorFor(model => model.ShowProductImagesInSearchAutoComplete) - @Html.ValidationMessageFor(model => model.ShowProductImagesInSearchAutoComplete) -
            - @Html.SmartLabelFor(model => model.ProductSearchAutoCompleteNumberOfProducts) + @Html.SmartLabelFor(model => model.SortManufacturersAlphabetically) - @Html.SettingEditorFor(model => model.ProductSearchAutoCompleteNumberOfProducts) - @Html.ValidationMessageFor(model => model.ProductSearchAutoCompleteNumberOfProducts) + @Html.SettingEditorFor(model => model.SortManufacturersAlphabetically) + @Html.ValidationMessageFor(model => model.SortManufacturersAlphabetically)
            -} -} \ No newline at end of file +} +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index 72a1003911..7415f8c4e1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -1,7 +1,5 @@ @model CustomerUserSettingsModel -@using Telerik.Web.Mvc.UI; @using SmartStore.Core.Domain.Customers; -@using SmartStore.Core.Domain.Security; @{ ViewBag.Title = T("Admin.Configuration.Settings.CustomerUser").Text; } @@ -13,22 +11,25 @@ @T("Admin.Configuration.Settings.CustomerUser")
            - +
            - - @Html.Action("StoreScopeConfiguration", "Setting") + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + @Html.ValidationSummary(false) - - @Html.SmartStore().TabStrip().Name("customersettings-edit").Items(x => - { + + @Html.SmartStore().TabStrip().Name("customersettings-edit").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => + { x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.CustomerSettings").Text).Content(TabCustomerSettings()).Selected(true); x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.CustomerFormFields").Text).Content(TabCustomerFormFields().ToHtmlString()); x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.AddressFormFields").Text).Content(TabAddressFormFields().ToHtmlString()); x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.DateTimeSettings").Text).Content(TabDateTimeSettings()); x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.ExternalAuthenticationSettings").Text).Content(TabExternalAuthenticationSettings()); - - //generate an event + x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.Privacy").Text).Content(TabPrivacy()); EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "customersettings-edit", this.Html, this.Model)); }) } @@ -36,75 +37,52 @@ @helper TabCustomerSettings() { - - - - - - - - + + + + + + + + + + @@ -123,8 +101,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberMethod) @@ -133,8 +110,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberVisibility) @@ -144,8 +120,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.UserRegistrationType) @@ -163,8 +138,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.RegisterCustomerRoleId) @@ -174,19 +149,11 @@ @Html.SmartLabelFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars) - - - - - - - -
            - @Html.SmartLabelFor(model => model.CustomerSettings.UsernamesEnabled) - - @Html.SettingEditorFor(model => model.CustomerSettings.UsernamesEnabled) - @Html.ValidationMessageFor(model => model.CustomerSettings.UsernamesEnabled) -
            - @Html.SmartLabelFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) - - @Html.SettingEditorFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) - @Html.ValidationMessageFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) -
            - @Html.SmartLabelFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerLoginType) - @Html.SettingEditorFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) - @Html.ValidationMessageFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) + @Html.DropDownListForEnum(model => model.CustomerSettings.CustomerLoginType) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerLoginType)
            + @Html.SmartLabelFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) + + @Html.SettingEditorFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) + @Html.ValidationMessageFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) +
            + @Html.SmartLabelFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) + + @Html.SettingEditorFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) + @Html.ValidationMessageFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) +
            @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormat) - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNameFormat, ((CustomerNameFormat)Model.CustomerSettings.CustomerNameFormat).ToSelectList()) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.CustomerNameFormat) @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormat)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberMethod) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberMethod, Model.CustomerSettings.AvailableCustomerNumberMethods) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.CustomerNumberMethod) @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberMethod)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberVisibility) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberVisibility, Model.CustomerSettings.AvailableCustomerNumberVisibilities) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.CustomerNumberVisibility) @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberVisibility)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) - @Html.DropDownListFor(model => model.CustomerSettings.UserRegistrationType, ((UserRegistrationType)Model.CustomerSettings.UserRegistrationType).ToSelectList()) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.UserRegistrationType) @Html.ValidationMessageFor(model => model.CustomerSettings.UserRegistrationType)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.RegisterCustomerRoleId) - @Html.DropDownListFor(model => model.CustomerSettings.RegisterCustomerRoleId, Model.CustomerSettings.AvailableRegisterCustomerRoles, T("Common.Unspecified")) + @Html.SettingEditorFor(model => model.CustomerSettings.RegisterCustomerRoleId, + Html.DropDownListFor(model => model.CustomerSettings.RegisterCustomerRoleId, Model.CustomerSettings.AvailableRegisterCustomerRoles, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CustomerSettings.RegisterCustomerRoleId)
            - @Html.SettingEditorFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars) + @Html.SettingEditorFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars, + Html.CheckBoxFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars)) @Html.ValidationMessageFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars)
            - @Html.SmartLabelFor(model => model.CustomerSettings.DefaultAvatarEnabled) - - @Html.SettingEditorFor(model => model.CustomerSettings.DefaultAvatarEnabled) - @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultAvatarEnabled) -
            @Html.SmartLabelFor(model => model.CustomerSettings.ShowCustomersLocation) @@ -259,101 +226,14 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastVisitedPage)
            - @Html.SmartLabelFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) - - @Html.SettingEditorFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) - @Html.ValidationMessageFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) -
            } + @helper TabCustomerFormFields() { - - -
            - +
            @T("Admin.Configuration.Settings.CustomerUser.CustomerFormFields.Description") +
            @@ -375,6 +255,24 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.TitleEnabled) + + + + + + + + @@ -407,7 +306,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.StreetAddressEnabled) @@ -425,7 +325,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.StreetAddress2Enabled) @@ -443,7 +344,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.ZipPostalCodeEnabled) @@ -461,7 +363,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CityEnabled) @@ -479,7 +382,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CountryEnabled) @@ -497,7 +401,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.PhoneEnabled) @@ -515,7 +420,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.FaxEnabled) @@ -539,98 +445,12 @@
            + @Html.SmartLabelFor(model => model.CustomerSettings.FirstNameRequired) + + @Html.SettingEditorFor(model => model.CustomerSettings.FirstNameRequired) + @Html.ValidationMessageFor(model => model.CustomerSettings.FirstNameRequired) +
            + @Html.SmartLabelFor(model => model.CustomerSettings.LastNameRequired) + + @Html.SettingEditorFor(model => model.CustomerSettings.LastNameRequired) + @Html.ValidationMessageFor(model => model.CustomerSettings.LastNameRequired) +
            @Html.SmartLabelFor(model => model.CustomerSettings.DateOfBirthEnabled) @@ -389,7 +287,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CompanyEnabled) - @Html.SettingEditorFor(model => model.CustomerSettings.CompanyEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.CompanyEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.CompanyEnabled, new { data_toggler_for = "#pnlCompanyRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.CompanyEnabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.StreetAddressEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.StreetAddressEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.StreetAddressEnabled, new { data_toggler_for = "#pnlStreetAddressRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.StreetAddressEnabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.StreetAddress2Enabled) + @Html.SettingEditorFor(model => model.CustomerSettings.StreetAddress2Enabled, + Html.CheckBoxFor(model => model.CustomerSettings.StreetAddress2Enabled, new { data_toggler_for = "#pnlStreetAddress2Required" })) @Html.ValidationMessageFor(model => model.CustomerSettings.StreetAddress2Enabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.ZipPostalCodeEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.ZipPostalCodeEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.ZipPostalCodeEnabled, new { data_toggler_for = "#pnlZipPostalCodeRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.ZipPostalCodeEnabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.CityEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.CityEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.CityEnabled, new { data_toggler_for = "#pnlCityRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.CityEnabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.CountryEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.CountryEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.CountryEnabled, new { data_toggler_for = "#pnlStateProvincEnabled" })) @Html.ValidationMessageFor(model => model.CustomerSettings.CountryEnabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.PhoneEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.PhoneEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.PhoneEnabled, new { data_toggler_for = "#pnlPhoneRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.PhoneEnabled)
            - @Html.SettingEditorFor(model => model.CustomerSettings.FaxEnabled) + @Html.SettingEditorFor(model => model.CustomerSettings.FaxEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.FaxEnabled, new { data_toggler_for = "#pnlFaxRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.FaxEnabled)
            } + @helper TabAddressFormFields() { - - -
            - +
            @T("Admin.Configuration.Settings.CustomerUser.AddressFormFields.Description") +
            @@ -648,42 +468,43 @@ @Html.SmartLabelFor(model => model.AddressSettings.SalutationEnabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.SalutationEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.SalutationEnabled, + Html.CheckBoxFor(model => model.AddressSettings.SalutationEnabled, new { data_toggler_for = "#pnlAddressSettingsSalutations" })) @Html.ValidationMessageFor(model => model.AddressSettings.SalutationEnabled)
            - - @(Html.LocalizedEditor("setting-customer-localized", - @ - - - - - - - -
            - @Html.SmartLabelFor(model => model.Locales[item].Salutations) - - @Html.TextBoxFor(model => Model.Locales[item].Salutations, new { @class = "input-xlarge" }) - @Html.ValidationMessageFor(model => model.Locales[item].Salutations) -
            - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
            - , - @ - - - - -
            - @Html.SmartLabelFor(model => model.AddressSettings.Salutations) - - @Html.TextBoxFor(model => model.AddressSettings.Salutations, new { @class = "input-xlarge" }) - @Html.ValidationMessageFor(model => model.AddressSettings.Salutations) -
            - )) + +
            + @(Html.LocalizedEditor("setting-customer-localized", + @ + + + + +
            + @Html.SmartLabelFor(model => model.Locales[item].Salutations) + + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextBoxFor(model => Model.Locales[item].Salutations) + @Html.ValidationMessageFor(model => model.Locales[item].Salutations) +
            + , + @ + + + + +
            + @Html.SmartLabelFor(model => model.AddressSettings.Salutations) + + @Html.TextBoxFor(model => model.AddressSettings.Salutations) + @Html.ValidationMessageFor(model => model.AddressSettings.Salutations) +
            + )) +
            @@ -700,7 +521,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.CompanyEnabled) @@ -718,7 +540,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.StreetAddressEnabled) @@ -736,7 +559,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.StreetAddress2Enabled) @@ -754,7 +578,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.ZipPostalCodeEnabled) @@ -772,7 +597,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.CityEnabled) @@ -790,25 +616,47 @@ @Html.SmartLabelFor(model => model.AddressSettings.CountryEnabled) - - - - + + + + + + + + + + + + + + @@ -826,7 +674,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.FaxEnabled) @@ -841,6 +690,7 @@
            - @Html.SettingEditorFor(model => model.AddressSettings.CompanyEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.CompanyEnabled, + Html.CheckBoxFor(model => model.AddressSettings.CompanyEnabled, new { data_toggler_for = "#pnlAddressCompanyRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.CompanyEnabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.StreetAddressEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.StreetAddressEnabled, + Html.CheckBoxFor(model => model.AddressSettings.StreetAddressEnabled, new { data_toggler_for = "#pnlAddressStreetAddressRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.StreetAddressEnabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.StreetAddress2Enabled) + @Html.SettingEditorFor(model => model.AddressSettings.StreetAddress2Enabled, + Html.CheckBoxFor(model => model.AddressSettings.StreetAddress2Enabled, new { data_toggler_for = "#pnlAddressStreetAddress2Required" })) @Html.ValidationMessageFor(model => model.AddressSettings.StreetAddress2Enabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.ZipPostalCodeEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.ZipPostalCodeEnabled, + Html.CheckBoxFor(model => model.AddressSettings.ZipPostalCodeEnabled, new { data_toggler_for = "#pnlAddressZipPostalCodeRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.ZipPostalCodeEnabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.CityEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.CityEnabled, + Html.CheckBoxFor(model => model.AddressSettings.CityEnabled, new { data_toggler_for = "#pnlAddressCityRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.CityEnabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.CountryEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.CountryEnabled, + Html.CheckBoxFor(model => model.AddressSettings.CountryEnabled, new { data_toggler_for = "#pnlAddressStateProvinceEnabled" })) @Html.ValidationMessageFor(model => model.AddressSettings.CountryEnabled)
            - @Html.SmartLabelFor(model => model.AddressSettings.StateProvinceEnabled) - - @Html.SettingEditorFor(model => model.AddressSettings.StateProvinceEnabled) - @Html.ValidationMessageFor(model => model.AddressSettings.StateProvinceEnabled) -
            + @Html.SmartLabelFor(model => model.AddressSettings.CountryRequired) + + @Html.SettingEditorFor(model => model.AddressSettings.CountryRequired) + @Html.ValidationMessageFor(model => model.AddressSettings.CountryRequired) +
            + @Html.SmartLabelFor(model => model.AddressSettings.StateProvinceEnabled) + + @Html.SettingEditorFor(model => model.AddressSettings.StateProvinceEnabled) + @Html.ValidationMessageFor(model => model.AddressSettings.StateProvinceEnabled) +
            + @Html.SmartLabelFor(model => model.AddressSettings.StateProvinceRequired) + + @Html.SettingEditorFor(model => model.AddressSettings.StateProvinceRequired) + @Html.ValidationMessageFor(model => model.AddressSettings.StateProvinceRequired) +
            @Html.SmartLabelFor(model => model.AddressSettings.PhoneEnabled) - @Html.SettingEditorFor(model => model.AddressSettings.PhoneEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.PhoneEnabled, + Html.CheckBoxFor(model => model.AddressSettings.PhoneEnabled, new { data_toggler_for = "#pnlAddressPhoneRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.PhoneEnabled)
            - @Html.SettingEditorFor(model => model.AddressSettings.FaxEnabled) + @Html.SettingEditorFor(model => model.AddressSettings.FaxEnabled, + Html.CheckBoxFor(model => model.AddressSettings.FaxEnabled, new { data_toggler_for = "#pnlAddressFaxRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.FaxEnabled)
            } + @helper TabDateTimeSettings() { @@ -858,13 +708,14 @@ @Html.SmartLabelFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId)
            - @Html.SettingOverrideCheckbox(model => model.DateTimeSettings.DefaultStoreTimeZoneId) - @Html.DropDownListFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId, Model.DateTimeSettings.AvailableTimeZones) + @Html.SettingEditorFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId, + Html.DropDownListFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId, Model.DateTimeSettings.AvailableTimeZones)) @Html.ValidationMessageFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId)
            } + @helper TabExternalAuthenticationSettings() { @@ -878,4 +729,91 @@
            +} + +@helper TabPrivacy() +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            + @Html.SmartLabelFor(model => model.PrivacySettings.EnableCookieConsent) + + @Html.SettingEditorFor(model => model.PrivacySettings.EnableCookieConsent, + Html.CheckBoxFor(model => model.PrivacySettings.EnableCookieConsent, new { data_toggler_for = "#pnlCookieConsent" })) + @Html.ValidationMessageFor(model => model.PrivacySettings.EnableCookieConsent) +
            + @(Html.LocalizedEditor("setting-privacy-localized", + @
            +
            +
            + @Html.SmartLabelFor(model => model.Locales[item].CookieConsentBadgetext) +
            +
            + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextAreaFor(model => Model.Locales[item].CookieConsentBadgetext) + @Html.ValidationMessageFor(model => model.Locales[item].CookieConsentBadgetext) +
            +
            +
            + , + @
            +
            +
            + @Html.SmartLabelFor(model => model.PrivacySettings.CookieConsentBadgetext) +
            +
            + @Html.TextAreaFor(model => model.PrivacySettings.CookieConsentBadgetext) + @Html.ValidationMessageFor(model => model.PrivacySettings.CookieConsentBadgetext) +
            +
            +
            + )) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.StoreLastIpAddress) + + @Html.SettingEditorFor(model => model.PrivacySettings.StoreLastIpAddress) + @Html.ValidationMessageFor(model => model.PrivacySettings.StoreLastIpAddress) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.DisplayGdprConsentOnForms) + + @Html.SettingEditorFor(model => model.PrivacySettings.DisplayGdprConsentOnForms) + @Html.ValidationMessageFor(model => model.PrivacySettings.DisplayGdprConsentOnForms) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.FullNameOnContactUsRequired) + + @Html.SettingEditorFor(model => model.PrivacySettings.FullNameOnContactUsRequired) + @Html.ValidationMessageFor(model => model.PrivacySettings.FullNameOnContactUsRequired) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.FullNameOnProductRequestRequired) + + @Html.SettingEditorFor(model => model.PrivacySettings.FullNameOnProductRequestRequired) + @Html.ValidationMessageFor(model => model.PrivacySettings.FullNameOnProductRequestRequired) +
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml index 88bcdab917..774fdab474 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml @@ -6,16 +6,20 @@ {
            - + @T("Admin.Common.DataExchange")
            - +
            - - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) @@ -48,7 +52,7 @@ @Html.SmartLabelFor(model => model.ImageDownloadTimeout) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml index dd72d82f6d..3612b4f72a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml @@ -1,10 +1,9 @@ @* codehint: sm-edit (see also AllSettings.cshtml grid_onStoreEdit) *@ + placeholder='@T("Admin.Common.StoresAll")' + data-select-url="@Url.Action("AllStores", "Store")"> + - function toggleStoreClosed() { - if ($('#@Html.FieldIdFor(model => model.StoreInformationSettings.StoreClosed)').is(':checked')) { - $('#pnlStoreClosedAllowForAdmins').show(); - } - else { - $('#pnlStoreClosedAllowForAdmins').hide(); - } - } + Html.RenderAction("StoreScopeConfiguration", "Setting"); - function toggleSeoLanguageUrls() { - if ($('#@Html.FieldIdFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled)').is(':checked')) { - $('#pnlDefaultLanguageRedirectBehaviour').show(); - $('#pnlInvalidLanguageRedirectBehaviour').show(); - } - else { - $('#pnlDefaultLanguageRedirectBehaviour').hide(); - $('#pnlInvalidLanguageRedirectBehaviour').hide(); - } - } - - @Html.Action("StoreScopeConfiguration", "Setting") @Html.ValidationSummary(false) - @(Html.SmartStore().TabStrip().Name("generalsettings-edit").Items(x => - { - x.Add().Text(T("Common.General").Text).Content(@TabStoreInformationSettings()).Selected(true); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.SEOSettings").Text).Content(@TabSEOSettings()); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.SecuritySettings").Text).Content(@TabSecuritySettings()); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.LocalizationSettings").Text).Content(TabLocalizationSettings()); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.PdfSettings").Text).Content(@TabPdfSettings()); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.CompanyInformationSettings").Text).Content(@TabCompanyInformationSettings()); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.ContactDataSettings").Text).Content(@TabContactDataSettings()); - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.SocialSettings").Text).Content(@TabSocialSettings()); - - x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.FullTextSettings").Text).Content(@TabFullTextSettings()); - })) - + @(Html.SmartStore().TabStrip().Name("generalsettings-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Common.General").Text).Content(@TabStoreInformationSettings()).Selected(true); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.SEOSettings").Text).Content(@TabSEOSettings()); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.SecuritySettings").Text).Content(@TabSecuritySettings()); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.LocalizationSettings").Text).Content(TabLocalizationSettings()); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.PdfSettings").Text).Content(@TabPdfSettings()); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.CompanyInformationSettings").Text).Content(@TabCompanyInformationSettings()); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.ContactDataSettings").Text).Content(@TabContactDataSettings()); + x.Add().Text(T("Admin.Configuration.Settings.GeneralCommon.SocialSettings").Text).Content(@TabSocialSettings()); + })) } + @helper TabStoreInformationSettings() - { +{
            - @Html.SettingEditorFor(model => model.ImageDownloadTimeout) + @Html.SettingEditorFor(model => model.ImageDownloadTimeout, null, new { postfix = T("Time.Minutes").Text }) @Html.ValidationMessageFor(model => model.ImageDownloadTimeout)
            @@ -110,8 +77,9 @@
            @Html.SmartLabelFor(model => model.StoreInformationSettings.StoreClosed) - @Html.SettingEditorFor(model => model.StoreInformationSettings.StoreClosed) + @Html.SettingEditorFor(model => model.StoreInformationSettings.StoreClosed, + Html.CheckBoxFor(model => model.StoreInformationSettings.StoreClosed, new { data_toggler_for = "#pnlStoreClosedAllowForAdmins" })) @Html.ValidationMessageFor(model => model.StoreInformationSettings.StoreClosed)
            } + @helper TabSEOSettings() - { +{ @@ -164,16 +131,17 @@ @Html.SmartLabelFor(model => model.SeoSettings.MetaRobotsContent) @@ -191,8 +159,7 @@ @Html.SmartLabelFor(model => model.SeoSettings.CanonicalHostNameRule) @@ -201,7 +168,7 @@ @Html.SmartLabelFor(model => model.SeoSettings.ExtraRobotsDisallows) @@ -235,7 +202,7 @@ @Html.SmartLabelFor(model => model.SeoSettings.SeoNameCharConversion) @@ -244,11 +211,13 @@ @Html.SmartLabelFor(model => model.SeoSettings.TestSeoNameCreation) @@ -256,61 +225,87 @@  
            @@ -127,8 +95,7 @@ @Html.SmartLabelFor(model => model.SeoSettings.PageTitleSeoAdjustment) - @Html.SettingOverrideCheckbox(model => model.SeoSettings.PageTitleSeoAdjustment) - @Html.DropDownListForEnum(model => model.SeoSettings.PageTitleSeoAdjustment ) + @Html.EnumSettingEditorFor(model => model.SeoSettings.PageTitleSeoAdjustment) @Html.ValidationMessageFor(model => model.SeoSettings.PageTitleSeoAdjustment)
            - @Html.SettingOverrideCheckbox(model => model.SeoSettings.MetaRobotsContent) - @Html.DropDownListFor(model => model.SeoSettings.MetaRobotsContent, new List - { - new SelectListItem { Text = "index", Value = "index" }, - new SelectListItem { Text = "noindex", Value = "noindex" }, - new SelectListItem { Text = "index, follow", Value = "index, follow" }, - new SelectListItem { Text = "index, nofollow", Value = "index, nofollow" }, - new SelectListItem { Text = "noindex, follow", Value = "noindex, follow" }, - new SelectListItem { Text = "noindex, nofollow", Value = "noindex, nofollow" } - }, T("Common.Unspecified")) + @Html.SettingEditorFor(model => model.SeoSettings.MetaRobotsContent, Html.DropDownListFor(model => model.SeoSettings.MetaRobotsContent, + new List + { + new SelectListItem { Text = "index", Value = "index" }, + new SelectListItem { Text = "noindex", Value = "noindex" }, + new SelectListItem { Text = "index, follow", Value = "index, follow" }, + new SelectListItem { Text = "index, nofollow", Value = "index, nofollow" }, + new SelectListItem { Text = "noindex, follow", Value = "noindex, follow" }, + new SelectListItem { Text = "noindex, nofollow", Value = "noindex, nofollow" } + }, + T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.SeoSettings.MetaRobotsContent)
            - @Html.SettingOverrideCheckbox(model => model.SeoSettings.CanonicalHostNameRule) - @Html.DropDownListForEnum(model => model.SeoSettings.CanonicalHostNameRule) + @Html.EnumSettingEditorFor(model => model.SeoSettings.CanonicalHostNameRule) @Html.ValidationMessageFor(model => model.SeoSettings.CanonicalHostNameRule)
            - @Html.TextAreaFor(model => model.SeoSettings.ExtraRobotsDisallows, new { @class = "input-large", style = "height:250px" }) + @Html.TextAreaFor(model => model.SeoSettings.ExtraRobotsDisallows, new { style = "height:250px" }) @Html.ValidationMessageFor(model => model.SeoSettings.ExtraRobotsDisallows)
            - @Html.TextAreaFor(model => model.SeoSettings.SeoNameCharConversion, new { @class = "input-large", style = "height:250px" }) + @Html.TextAreaFor(model => model.SeoSettings.SeoNameCharConversion, new { style = "height:250px" }) @Html.ValidationMessageFor(model => model.SeoSettings.SeoNameCharConversion)
            - @Html.EditorFor(model => model.SeoSettings.TestSeoNameCreation) - - +
            + @Html.EditorFor(model => model.SeoSettings.TestSeoNameCreation, new { @class = "form-control" }) + +
            +
            } + @helper TabSecuritySettings() - { - - - - - - - - - +{ +
            - @Html.SmartLabelFor(model => model.SecuritySettings.EncryptionKey) - - @Html.EditorFor(model => model.SecuritySettings.EncryptionKey) - @Html.ValidationMessageFor(model => model.SecuritySettings.EncryptionKey) - -
            - @Html.SmartLabelFor(model => model.SecuritySettings.AdminAreaAllowedIpAddresses) - - @Html.EditorFor(model => model.SecuritySettings.AdminAreaAllowedIpAddresses) - @Html.ValidationMessageFor(model => model.SecuritySettings.AdminAreaAllowedIpAddresses) -
            + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - @@ -319,7 +314,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnLoginPage) - + @@ -328,7 +323,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnRegistrationPage) - + @@ -337,7 +332,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnContactUsPage) - + @@ -346,7 +341,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnEmailWishlistToFriendPage) - + @@ -355,7 +350,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnEmailProductToFriendPage) - + @@ -364,7 +359,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnAskQuestionPage) - + @@ -373,7 +368,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnBlogCommentPage) - + @@ -382,7 +377,16 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnNewsCommentPage) - + + + + + @@ -391,7 +395,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnProductReviewPage) - + @@ -400,7 +404,7 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ReCaptchaPublicKey) - + @@ -409,10 +413,12 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ReCaptchaPrivateKey) -
            + @Html.SmartLabelFor(model => model.SecuritySettings.EncryptionKey) + +
            + @Html.EditorFor(model => model.SecuritySettings.EncryptionKey, new { @class = "form-control" }) + +
            + @Html.ValidationMessageFor(model => model.SecuritySettings.EncryptionKey) +
            + @Html.SmartLabelFor(model => model.SecuritySettings.AdminAreaAllowedIpAddresses) + + @Html.EditorFor(model => model.SecuritySettings.AdminAreaAllowedIpAddresses) + @Html.ValidationMessageFor(model => model.SecuritySettings.AdminAreaAllowedIpAddresses) +
            + @Html.SmartLabelFor(model => model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions) + + @Html.EditorFor(model => model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions) + @Html.ValidationMessageFor(model => model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions) +
            + @Html.SmartLabelFor(model => model.SecuritySettings.EnableHoneypotProtection) + + @Html.EditorFor(model => model.SecuritySettings.EnableHoneypotProtection) + @Html.ValidationMessageFor(model => model.SecuritySettings.EnableHoneypotProtection) +
             
            +
            + @T("Admin.Configuration.Settings.General.Common.Captcha.Hint") +
            +
            + @Html.SmartLabelFor(model => model.CaptchaSettings.Enabled) + + @Html.SettingEditorFor(model => model.CaptchaSettings.Enabled, + Html.CheckBoxFor(model => model.CaptchaSettings.Enabled, new { data_toggler_for = "#pnlCaptchaSettings" })) + @Html.ValidationMessageFor(model => model.CaptchaSettings.Enabled) +
            - @Html.SmartLabelFor(model => model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions) + @Html.SmartLabelFor(model => model.CaptchaSettings.UseInvisibleReCaptcha) - @Html.EditorFor(model => model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions) - @Html.ValidationMessageFor(model => model.SecuritySettings.HideAdminMenuItemsBasedOnPermissions) -
             
            -
            - @T("Admin.Configuration.Settings.General.Common.Captcha.Hint") -
            + @Html.SettingEditorFor(model => model.CaptchaSettings.UseInvisibleReCaptcha) + @Html.ValidationMessageFor(model => model.CaptchaSettings.UseInvisibleReCaptcha)
            - @Html.SmartLabelFor(model => model.CaptchaSettings.Enabled) - - @Html.SettingEditorFor(model => model.CaptchaSettings.Enabled) - @Html.ValidationMessageFor(model => model.CaptchaSettings.Enabled) -
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnLoginPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnRegistrationPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnContactUsPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnEmailWishlistToFriendPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnEmailProductToFriendPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnAskQuestionPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnBlogCommentPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnNewsCommentPage)
            + @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnForumPage) + + @Html.SettingEditorFor(model => model.CaptchaSettings.ShowOnForumPage) + @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnForumPage) +
            @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnProductReviewPage)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ReCaptchaPublicKey)
            @Html.SmartLabelFor(model => model.CaptchaSettings.ReCaptchaPrivateKey)
            + + } + @helper TabPdfSettings() - { +{
            @@ -466,45 +472,42 @@
            } + @helper TabLocalizationSettings() { - - - - - - - - - - - - + + + + + + + + + + @@ -584,8 +587,8 @@ @Html.SmartLabelFor(model => model.CompanyInformationSettings.CompanyManagementDescription) @@ -641,8 +644,8 @@ @Html.SmartLabelFor(model => model.CompanyInformationSettings.CountryId) @@ -841,121 +844,57 @@ @Html.SmartLabelFor(model => model.SocialSettings.ShowSocialLinksInFooter) - - - - - - - - - - - - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.LocalizationSettings.LoadAllLocaleRecordsOnStartup) - - @Html.EditorFor(model => model.LocalizationSettings.LoadAllLocaleRecordsOnStartup) - @Html.ValidationMessageFor(model => model.LocalizationSettings.LoadAllLocaleRecordsOnStartup) -
            @Html.SmartLabelFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled) - @Html.EditorFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled) + @Html.SettingEditorFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled, + Html.CheckBoxFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled, new { data_toggler_for = "#pnlSeoFriendlyUrlsForLanguages" })) @Html.ValidationMessageFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled)
            - @Html.SmartLabelFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) - - @Html.DropDownListForEnum(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) - @Html.ValidationMessageFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) -
            - @Html.SmartLabelFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) - - @Html.DropDownListForEnum(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) - @Html.ValidationMessageFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) -
            + @Html.SmartLabelFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) + + @Html.SettingEditorFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour, + Html.DropDownListForEnum(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour)) + @Html.ValidationMessageFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) +
            + @Html.SmartLabelFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) + + @Html.SettingEditorFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour, + Html.DropDownListForEnum(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour)) + @Html.ValidationMessageFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) +
            @Html.SmartLabelFor(model => model.LocalizationSettings.UseImagesForLanguageSelection) @@ -547,8 +550,8 @@ @Html.SmartLabelFor(model => model.CompanyInformationSettings.Salutation) - @Html.SettingOverrideCheckbox(model => model.CompanyInformationSettings.Salutation) - @Html.DropDownListFor(model => model.CompanyInformationSettings.Salutation, Model.CompanyInformationSettings.Salutations) + @Html.SettingEditorFor(model => model.CompanyInformationSettings.Salutation, + Html.DropDownListFor(model => model.CompanyInformationSettings.Salutation, Model.CompanyInformationSettings.Salutations, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.Salutation)
            - @Html.SettingOverrideCheckbox(model => model.CompanyInformationSettings.CompanyManagementDescription) - @Html.DropDownListFor(model => model.CompanyInformationSettings.CompanyManagementDescription, Model.CompanyInformationSettings.ManagementDescriptions) + @Html.SettingEditorFor(model => model.CompanyInformationSettings.CompanyManagementDescription, + Html.DropDownListFor(model => model.CompanyInformationSettings.CompanyManagementDescription, Model.CompanyInformationSettings.ManagementDescriptions, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.CompanyManagementDescription)
            - @Html.SettingOverrideCheckbox(model => model.CompanyInformationSettings.CountryId) - @Html.DropDownListFor(model => model.CompanyInformationSettings.CountryId, Model.CompanyInformationSettings.AvailableCountries, new { style = "min-width:289px" } ) + @Html.SettingEditorFor(model => model.CompanyInformationSettings.CountryId, + Html.DropDownListFor(model => model.CompanyInformationSettings.CountryId, Model.CompanyInformationSettings.AvailableCountries, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.CountryId)
            - @Html.SettingEditorFor(model => model.SocialSettings.ShowSocialLinksInFooter) + @Html.SettingEditorFor(model => model.SocialSettings.ShowSocialLinksInFooter, + Html.CheckBoxFor(model => model.SocialSettings.ShowSocialLinksInFooter, new { data_toggler_for = "#pnlSocialLinks" })) @Html.ValidationMessageFor(model => model.SocialSettings.ShowSocialLinksInFooter)
            -} - -@helper TabFullTextSettings() -{ - - - - - @if (Model.FullTextSettings.Supported) - { - - - - - if (Model.FullTextSettings.Enabled) - { - - - - - } - } + + + + + + + + + + + + + + + + + + + + + +
            - @if (Model.FullTextSettings.Supported) - { -
            - @T("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.Supported") -
            - } - else - { -
            - @T("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.NotSupported") -
            - } -
            -
            - @if (Model.FullTextSettings.Enabled) - { - @T("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.CurrenlyEnabled") - } - else - { - @T("Admin.Configuration.Settings.GeneralCommon.FullTextSettings.CurrenlyDisabled") - } -
            -
            - @if (Model.FullTextSettings.Enabled) - { - - } - else - { - - } -
            - @Html.SmartLabelFor(model => model.FullTextSettings.SearchMode) - - @Html.DropDownListFor(model => model.FullTextSettings.SearchMode, Model.FullTextSettings.SearchModeValues) - @Html.ValidationMessageFor(model => model.FullTextSettings.SearchMode) -
            + @Html.SmartLabelFor(model => model.SocialSettings.FacebookLink) + + @Html.SettingEditorFor(model => model.SocialSettings.FacebookLink) + @Html.ValidationMessageFor(model => model.SocialSettings.FacebookLink) +
            + @Html.SmartLabelFor(model => model.SocialSettings.TwitterLink) + + @Html.SettingEditorFor(model => model.SocialSettings.TwitterLink) + @Html.ValidationMessageFor(model => model.SocialSettings.TwitterLink) +
            + @Html.SmartLabelFor(model => model.SocialSettings.PinterestLink) + + @Html.SettingEditorFor(model => model.SocialSettings.PinterestLink) + @Html.ValidationMessageFor(model => model.SocialSettings.PinterestLink) +
            + @Html.SmartLabelFor(model => model.SocialSettings.YoutubeLink) + + @Html.SettingEditorFor(model => model.SocialSettings.YoutubeLink) + @Html.ValidationMessageFor(model => model.SocialSettings.YoutubeLink) +
            + @Html.SmartLabelFor(model => model.SocialSettings.InstagramLink) + + @Html.SettingEditorFor(model => model.SocialSettings.InstagramLink) + @Html.ValidationMessageFor(model => model.SocialSettings.InstagramLink) +
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml index fbd72c4cd3..51d388f7fd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml @@ -1,5 +1,4 @@ @model MediaSettingsModel -@using Telerik.Web.Mvc.UI; @{ ViewBag.Title = T("Admin.Configuration.Settings.Media").Text; } @@ -7,39 +6,37 @@ {
            - + @T("Admin.Configuration.Settings.Media")
            - +
            - - - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) - + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) + - + + + + + @@ -48,7 +45,7 @@ @Html.SmartLabelFor(model => model.ProductThumbPictureSize) @@ -57,7 +54,7 @@ @Html.SmartLabelFor(model => model.ProductThumbPictureSizeOnProductDetailsPage) @@ -66,7 +63,7 @@ @Html.SmartLabelFor(model => model.MessageProductThumbPictureSize) @@ -75,7 +72,7 @@ @Html.SmartLabelFor(model => model.ProductDetailsPictureSize) @@ -88,22 +85,12 @@ @Html.ValidationMessageFor(model => model.DefaultPictureZoomEnabled) - - - - @@ -112,7 +99,7 @@ @Html.SmartLabelFor(model => model.BundledProductPictureSize) @@ -121,7 +108,7 @@ @Html.SmartLabelFor(model => model.CategoryThumbPictureSize) @@ -130,7 +117,7 @@ @Html.SmartLabelFor(model => model.ManufacturerThumbPictureSize) @@ -139,7 +126,7 @@ @Html.SmartLabelFor(model => model.CartThumbPictureSize) @@ -148,7 +135,7 @@ @Html.SmartLabelFor(model => model.CartThumbBundleItemPictureSize) @@ -157,7 +144,7 @@ @Html.SmartLabelFor(model => model.MiniCartThumbPictureSize) @@ -166,35 +153,62 @@ @Html.SmartLabelFor(model => model.MaximumImageSize) + + + +
            + @Html.SmartLabelFor(model => model.AutoGenerateAbsoluteUrls) + + @Html.SettingEditorFor(model => model.AutoGenerateAbsoluteUrls) + @Html.ValidationMessageFor(model => model.AutoGenerateAbsoluteUrls) +
            @Html.SmartLabelFor(model => model.AvatarPictureSize) - @Html.SettingEditorFor(model => model.AvatarPictureSize) + @Html.SettingEditorFor(model => model.AvatarPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.AvatarPictureSize)
            - @Html.SettingEditorFor(model => model.ProductThumbPictureSize) + @Html.SettingEditorFor(model => model.ProductThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ProductThumbPictureSize)
            - @Html.SettingEditorFor(model => model.ProductThumbPictureSizeOnProductDetailsPage) + @Html.SettingEditorFor(model => model.ProductThumbPictureSizeOnProductDetailsPage, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ProductThumbPictureSizeOnProductDetailsPage)
            - @Html.SettingEditorFor(model => model.MessageProductThumbPictureSize) + @Html.SettingEditorFor(model => model.MessageProductThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.MessageProductThumbPictureSize)
            - @Html.SettingEditorFor(model => model.ProductDetailsPictureSize) + @Html.SettingEditorFor(model => model.ProductDetailsPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ProductDetailsPictureSize)
            - @Html.SmartLabelFor(model => model.PictureZoomType) - - @Html.SettingOverrideCheckbox(model => Model.PictureZoomType) - @Html.DropDownListFor(model => model.PictureZoomType, Model.AvailablePictureZoomTypes) - @Html.ValidationMessageFor(model => model.PictureZoomType) -
            @Html.SmartLabelFor(model => model.AssociatedProductPictureSize) - @Html.SettingEditorFor(model => model.AssociatedProductPictureSize) + @Html.SettingEditorFor(model => model.AssociatedProductPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.AssociatedProductPictureSize)
            - @Html.SettingEditorFor(model => model.BundledProductPictureSize) + @Html.SettingEditorFor(model => model.BundledProductPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.BundledProductPictureSize)
            - @Html.SettingEditorFor(model => model.CategoryThumbPictureSize) + @Html.SettingEditorFor(model => model.CategoryThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.CategoryThumbPictureSize)
            - @Html.SettingEditorFor(model => model.ManufacturerThumbPictureSize) + @Html.SettingEditorFor(model => model.ManufacturerThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ManufacturerThumbPictureSize)
            - @Html.SettingEditorFor(model => model.CartThumbPictureSize) + @Html.SettingEditorFor(model => model.CartThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.CartThumbPictureSize)
            - @Html.SettingEditorFor(model => model.CartThumbBundleItemPictureSize) + @Html.SettingEditorFor(model => model.CartThumbBundleItemPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.CartThumbBundleItemPictureSize)
            - @Html.SettingEditorFor(model => model.MiniCartThumbPictureSize) + @Html.SettingEditorFor(model => model.MiniCartThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.MiniCartThumbPictureSize)
            - @Html.SettingEditorFor(model => model.MaximumImageSize) + @Html.SettingEditorFor(model => model.MaximumImageSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.MaximumImageSize)
            -
            @T("Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase")
            +
            + @(T("Admin.Configuration.Settings.Media.MediaIsStoredIn"))… +
            - @T("Admin.Configuration.Settings.Media.MovePicturesNote") +

            + @T("Admin.Configuration.Settings.Media.MoveMediaNote") +

            +

            + @(T("Admin.Configuration.Settings.Media.CurrentStorageLocation")): + @Model.StorageProvider +

            - - @T(Model.PicturesStoredIntoDatabase ? "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.Database" : "Admin.Configuration.Settings.Media.PicturesStoredIntoDatabase.FileSystem") - +
            + @Html.SmartLabelFor(model => model.StorageProvider) + + @Html.DropDownListFor(model => model.StorageProvider, Model.AvailableStorageProvider) - + + @Html.ValidationMessageFor(model => model.StorageProvider)
            -} \ No newline at end of file +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/News.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/News.cshtml index cd65a40c30..2e3d98fc13 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/News.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/News.cshtml @@ -1,7 +1,5 @@ @model NewsSettingsModel -@using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.News").Text; } @using (Html.BeginForm()) @@ -12,11 +10,16 @@ @T("Admin.Configuration.Settings.News")
            - +
            - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) @@ -24,72 +27,74 @@ @Html.SmartLabelFor(model => model.Enabled) - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            - @Html.SettingEditorFor(model => model.Enabled) + @Html.SettingEditorFor(model => model.Enabled, Html.CheckBoxFor(model => model.Enabled, new { data_toggler_for = "#pnlNewsEnabled" })) @Html.ValidationMessageFor(model => model.Enabled)
            - @Html.SmartLabelFor(model => model.AllowNotRegisteredUsersToLeaveComments) - - @Html.SettingEditorFor(model => model.AllowNotRegisteredUsersToLeaveComments) - @Html.ValidationMessageFor(model => model.AllowNotRegisteredUsersToLeaveComments) -
            - @Html.SmartLabelFor(model => model.NotifyAboutNewNewsComments) - - @Html.SettingEditorFor(model => model.NotifyAboutNewNewsComments) - @Html.ValidationMessageFor(model => model.NotifyAboutNewNewsComments) -
            - @Html.SmartLabelFor(model => model.ShowNewsOnMainPage) - - @Html.SettingEditorFor(model => model.ShowNewsOnMainPage) - @Html.ValidationMessageFor(model => model.ShowNewsOnMainPage) -
            - @Html.SmartLabelFor(model => model.MainPageNewsCount) - - @Html.SettingEditorFor(model => model.MainPageNewsCount) - @Html.ValidationMessageFor(model => model.MainPageNewsCount) -
            - @Html.SmartLabelFor(model => model.NewsArchivePageSize) - - @Html.SettingEditorFor(model => model.NewsArchivePageSize) - @Html.ValidationMessageFor(model => model.NewsArchivePageSize) -
            - @Html.SmartLabelFor(model => model.MaxAgeInDays) - - @Html.SettingEditorFor(model => model.MaxAgeInDays) - @Html.ValidationMessageFor(model => model.MaxAgeInDays) -
            - @Html.SmartLabelFor(model => model.ShowHeaderRssUrl) - - @Html.SettingEditorFor(model => model.ShowHeaderRssUrl) - @Html.ValidationMessageFor(model => model.ShowHeaderRssUrl) -
            + @Html.SmartLabelFor(model => model.AllowNotRegisteredUsersToLeaveComments) + + @Html.SettingEditorFor(model => model.AllowNotRegisteredUsersToLeaveComments) + @Html.ValidationMessageFor(model => model.AllowNotRegisteredUsersToLeaveComments) +
            + @Html.SmartLabelFor(model => model.NotifyAboutNewNewsComments) + + @Html.SettingEditorFor(model => model.NotifyAboutNewNewsComments) + @Html.ValidationMessageFor(model => model.NotifyAboutNewNewsComments) +
            + @Html.SmartLabelFor(model => model.ShowNewsOnMainPage) + + @Html.SettingEditorFor(model => model.ShowNewsOnMainPage) + @Html.ValidationMessageFor(model => model.ShowNewsOnMainPage) +
            + @Html.SmartLabelFor(model => model.MainPageNewsCount) + + @Html.SettingEditorFor(model => model.MainPageNewsCount) + @Html.ValidationMessageFor(model => model.MainPageNewsCount) +
            + @Html.SmartLabelFor(model => model.NewsArchivePageSize) + + @Html.SettingEditorFor(model => model.NewsArchivePageSize) + @Html.ValidationMessageFor(model => model.NewsArchivePageSize) +
            + @Html.SmartLabelFor(model => model.MaxAgeInDays) + + @Html.SettingEditorFor(model => model.MaxAgeInDays, null, new { postfix = T("Time.Days").Text }) + @Html.ValidationMessageFor(model => model.MaxAgeInDays) +
            + @Html.SmartLabelFor(model => model.ShowHeaderRssUrl) + + @Html.SettingEditorFor(model => model.ShowHeaderRssUrl) + @Html.ValidationMessageFor(model => model.ShowHeaderRssUrl) +
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml index c10c4c8f52..b0c8ce9f1a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml @@ -1,7 +1,4 @@ @model OrderSettingsModel -@using Telerik.Web.Mvc.UI; -@using SmartStore.Core.Domain.Orders; - @{ ViewBag.Title = T("Admin.Configuration.Settings.Order").Text; } @@ -10,23 +7,28 @@ {
            - + @T("Admin.Configuration.Settings.Order")
            - +
            - @Html.Action("StoreScopeConfiguration", "Setting") + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + @Html.ValidationSummary(false) - @Html.SmartStore().TabStrip().Name("ordersettings-edit").Items(x => + @Html.SmartStore().TabStrip().Name("ordersettings-edit").Style(TabsStyle.Material).Items(x => { - x.Add().Text(T("Admin.Configuration.Settings.Order.OrderSettings").Text).Content(TabOrderSettings()).Selected(true); - x.Add().Text(T("Admin.Configuration.Settings.Order.ReturnRequestSettings").Text).Content(TabReturnRequestSettings()); - - //generate an event - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "ordersettings-edit", this.Html, this.Model)); + x.Add().Text(T("Admin.Configuration.Settings.Order.OrderSettings").Text).Content(TabOrderSettings()).Selected(true); + x.Add().Text(T("Admin.Configuration.Settings.Order.ReturnRequestSettings").Text).Content(TabReturnRequestSettings()); + + //generate an event + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "ordersettings-edit", this.Html, this.Model)); }) } @@ -47,8 +49,8 @@ @Html.SmartLabelFor(model => model.MinOrderSubtotalAmount) - @Html.SettingEditorFor(model => model.MinOrderSubtotalAmount) - @Html.ValidationMessageFor(model => model.MinOrderSubtotalAmount) @Model.PrimaryStoreCurrencyCode + @Html.SettingEditorFor(model => model.MinOrderSubtotalAmount, null, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.MinOrderSubtotalAmount) @@ -56,8 +58,8 @@ @Html.SmartLabelFor(model => model.MinOrderTotalAmount) - @Html.SettingEditorFor(model => model.MinOrderTotalAmount) - @Html.ValidationMessageFor(model => model.MinOrderTotalAmount) @Model.PrimaryStoreCurrencyCode + @Html.SettingEditorFor(model => model.MinOrderTotalAmount, null, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.MinOrderTotalAmount) @@ -142,14 +144,14 @@ @helper TabReturnRequestSettings() { -
            - +
            @T("Admin.Configuration.Settings.Order.ReturnRequestsDescription1")
            @T("Admin.Configuration.Settings.Order.ReturnRequestsDescription2") +
            - +
            @Html.SmartLabelFor(model => model.ReturnRequestsEnabled) @@ -164,14 +166,12 @@ @Html.SmartLabelFor(model => model.NumberOfDaysReturnRequestAvailable) - @Html.SettingEditorFor(model => model.NumberOfDaysReturnRequestAvailable) + @Html.SettingEditorFor(model => model.NumberOfDaysReturnRequestAvailable, null, new { postfix = T("Time.Days").Text }) @Html.ValidationMessageFor(model => model.NumberOfDaysReturnRequestAvailable)
            -

            - @(Html.LocalizedEditor("setting-order-localized", @ @@ -179,7 +179,10 @@ @Html.SmartLabelFor(model => model.Locales[item].ReturnRequestReasons) @@ -188,15 +191,10 @@ @Html.SmartLabelFor(model => model.Locales[item].ReturnRequestActions) - - -
            - @Html.TextBoxFor(model => Model.Locales[item].ReturnRequestReasons, new { @class = "input-xlarge" }) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextBoxFor(model => Model.Locales[item].ReturnRequestReasons) @Html.ValidationMessageFor(model => model.Locales[item].ReturnRequestReasons)
            - @Html.TextBoxFor(model => model.Locales[item].ReturnRequestActions, new { @class = "input-xlarge" }) + @Html.TextBoxFor(model => model.Locales[item].ReturnRequestActions) @Html.ValidationMessageFor(model => model.Locales[item].ReturnRequestActions)
            - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
            , @ @@ -205,7 +203,7 @@ @Html.SmartLabelFor(model => model.ReturnRequestReasons) @@ -214,7 +212,7 @@ @Html.SmartLabelFor(model => model.ReturnRequestActions) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Partials/CommonFacet.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Partials/CommonFacet.cshtml new file mode 100644 index 0000000000..7422c9209f --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Partials/CommonFacet.cshtml @@ -0,0 +1,79 @@ +@model CommonFacetSettingsModel +@{ + var fieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix; +} + +@if (fieldPrefix != "CategoryFacet") +{ +
            - @Html.TextBoxFor(model => model.ReturnRequestReasons, new { @class = "input-xlarge" }) + @Html.TextBoxFor(model => model.ReturnRequestReasons) @Html.ValidationMessageFor(model => model.ReturnRequestReasons)
            - @Html.TextBoxFor(x => x.ReturnRequestActions, new { @class = "input-xlarge" }) + @Html.TextBoxFor(x => x.ReturnRequestActions) @Html.ValidationMessageFor(model => model.ReturnRequestActions)
            + + + + + + + + + @if (fieldPrefix == "AvailabilityFacet") + { + + + + + } +
            + @Html.SmartLabelFor(model => model.Disabled) + + @Html.SettingEditorFor(model => model.Disabled) + @Html.ValidationMessageFor(model => model.Disabled) +
            + @Html.SmartLabelFor(model => model.DisplayOrder) + + @Html.SettingEditorFor(model => model.DisplayOrder) + @Html.ValidationMessageFor(model => model.DisplayOrder) +
            + @Html.SmartLabelFor(model => model.IncludeNotAvailable) + + @Html.SettingEditorFor(model => model.IncludeNotAvailable) + @Html.ValidationMessageFor(model => model.IncludeNotAvailable) +
            +} + +@if (Model.Locales.Count == 1) +{ + + + + + + + + +
            + @Html.SmartLabelFor(model => model.Locales[0].Alias) + + @Html.EditorFor(model => Model.Locales[0].Alias) + @Html.ValidationMessageFor(model => model.Locales[0].Alias) +
            + @Html.HiddenFor(model => model.Locales[0].LanguageId) +
            +} +else +{ +@(Html.LocalizedEditor(fieldPrefix + "-localized", + @ + + + + +
            + @Html.SmartLabelFor(model => model.Locales[item].Alias) + + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.EditorFor(model => Model.Locales[item].Alias) + @Html.ValidationMessageFor(model => model.Locales[item].Alias) +
            + , null)) +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Payment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Payment.cshtml new file mode 100644 index 0000000000..9612bfe431 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Payment.cshtml @@ -0,0 +1,36 @@ +@model PaymentSettingsModel +@{ + ViewBag.Title = T("Admin.Configuration.Payment").Text; +} +@using (Html.BeginForm()) +{ +
            +
            + + @T("Admin.Configuration.Payment") +
            +
            + +
            +
            + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) + + + + + + +
            + @Html.SmartLabelFor(model => model.CapturePaymentReason) + + @Html.SettingEditorFor(model => model.CapturePaymentReason, + Html.DropDownListFor(model => model.CapturePaymentReason, Model.AvailableCapturePaymentReasons, T("Common.Unspecified"))) + @Html.ValidationMessageFor(model => model.CapturePaymentReason) +
            +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml index 2db648327b..0306047d97 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/RewardPoints.cshtml @@ -1,9 +1,8 @@ @model RewardPointsSettingsModel -@using Telerik.Web.Mvc.UI; -@using SmartStore.Core.Domain.Orders; @{ ViewBag.Title = T("Admin.Configuration.Settings.RewardPoints").Text; } + @using (Html.BeginForm()) {
            @@ -12,16 +11,20 @@ @T("Admin.Configuration.Settings.RewardPoints")
            - +
            - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) -
            - +
            @T("Admin.Configuration.Settings.RewardPoints.Description") +
            @@ -30,97 +33,104 @@ @Html.SmartLabelFor(model => model.Enabled) - - - - - - - - -
            - @Html.SettingEditorFor(model => model.Enabled) + @Html.SettingEditorFor(model => model.Enabled, Html.CheckBoxFor(model => model.Enabled, new { data_toggler_for = "#pnlRewardPointSettings" })) @Html.ValidationMessageFor(model => model.Enabled)
            - @Html.SmartLabelFor(model => model.ExchangeRate) - - @Html.SettingOverrideCheckbox(model => Model.ExchangeRate) - @T("Admin.Configuration.Settings.RewardPoints.ExchangeRate.Hint2") - @Html.EditorFor(model => model.ExchangeRate, new { Small = true }) - @Model.PrimaryStoreCurrencyCode - @Html.ValidationMessageFor(model => model.ExchangeRate) -
            - @Html.SmartLabelFor(model => model.RoundDownRewardPoints) - - @Html.SettingEditorFor(model => model.RoundDownRewardPoints) - @Html.ValidationMessageFor(model => model.RoundDownRewardPoints) -
            + + + + @Html.SmartLabelFor(model => model.ExchangeRate) + + + @Html.SettingEditorFor(model => Model.ExchangeRate, @
            +
            + @T("Admin.Configuration.Settings.RewardPoints.ExchangeRate.Hint2") +
            + @Html.SettingEditorFor(model => model.ExchangeRate, null, new { postfix = Model.PrimaryStoreCurrencyCode }) +
            ) + @Html.ValidationMessageFor(model => model.ExchangeRate) + + + + + @Html.SmartLabelFor(model => model.RoundDownRewardPoints) + + + @Html.SettingEditorFor(model => model.RoundDownRewardPoints) + @Html.ValidationMessageFor(model => model.RoundDownRewardPoints) + + + + +
            +
            @T("Admin.Configuration.Settings.RewardPoints.Earning")
            +
            + + + + + @Html.SmartLabelFor(model => model.PointsForRegistration) + + + @Html.SettingEditorFor(model => model.PointsForRegistration) + @Html.ValidationMessageFor(model => model.PointsForRegistration) + + + + + @Html.SmartLabelFor(model => model.PointsForProductReview) + + + @Html.SettingEditorFor(model => model.PointsForProductReview) + @Html.ValidationMessageFor(model => model.PointsForProductReview) + + + + + @Html.SmartLabelFor(model => model.PointsForPurchases_Amount) + + + @Html.SettingEditorFor(model => Model.PointsForPurchases_Amount, @
            +
            +
            @T("Admin.Configuration.Settings.RewardPoints.Earning.Hint1")
            +
            +
            + @Html.EditorFor(model => model.PointsForPurchases_Amount, new { postfix = Model.PrimaryStoreCurrencyCode }) +
            +
            +
            @T("Admin.Configuration.Settings.RewardPoints.Earning.Hint2")
            +
            +
            + @Html.EditorFor(model => model.PointsForPurchases_Points) +
            +
            +
            @T("Admin.Configuration.Settings.RewardPoints.Earning.Hint3")
            +
            +
            ) - - - - - - - - - - - - - - - - - - - - - - - - + @Html.ValidationMessageFor(model => model.PointsForPurchases_Amount) + @Html.ValidationMessageFor(model => model.PointsForPurchases_Points) + + + + + + + + + + +
            -
            -
            @T("Admin.Configuration.Settings.RewardPoints.Earning")
            -
            -
            - @Html.SmartLabelFor(model => model.PointsForRegistration) - - @Html.SettingOverrideCheckbox(model => Model.PointsForRegistration) - @Html.EditorFor(model => model.PointsForRegistration, new { Small = true }) - @Html.ValidationMessageFor(model => model.PointsForRegistration) -
            - @Html.SmartLabelFor(model => model.PointsForProductReview) - - @Html.SettingOverrideCheckbox(model => Model.PointsForProductReview) - @Html.EditorFor(model => model.PointsForProductReview, new { Small = true }) - @Html.ValidationMessageFor(model => model.PointsForProductReview) -
            - @Html.SmartLabelFor(model => model.PointsForPurchases_Amount) - - @Html.SettingOverrideCheckbox(model => Model.PointsForPurchases_OverrideForStore, "#pnlPointsForPurchases") - @T("Admin.Configuration.Settings.RewardPoints.Earning.Hint1") - @Html.EditorFor(model => model.PointsForPurchases_Amount, new { Small = true }) - @Model.PrimaryStoreCurrencyCode -   - @T("Admin.Configuration.Settings.RewardPoints.Earning.Hint2") - @Html.EditorFor(model => model.PointsForPurchases_Points, new { Small = true }) - @T("Admin.Configuration.Settings.RewardPoints.Earning.Hint3") - @Html.ValidationMessageFor(model => model.PointsForPurchases_Amount) - @Html.ValidationMessageFor(model => model.PointsForPurchases_Points) -
            - @Html.SmartLabelFor(model => model.PointsForPurchases_Awarded) - - @Html.SettingOverrideCheckbox(model => Model.PointsForPurchases_Awarded) - @Html.DropDownListFor(model => model.PointsForPurchases_Awarded, ((OrderStatus)Model.PointsForPurchases_Awarded).ToSelectList()) - @Html.ValidationMessageFor(model => model.PointsForPurchases_Awarded) -
            - @Html.SmartLabelFor(model => model.PointsForPurchases_Canceled) - - @Html.SettingOverrideCheckbox(model => Model.PointsForPurchases_Canceled) - @Html.DropDownListFor(model => model.PointsForPurchases_Canceled, ((OrderStatus)Model.PointsForPurchases_Canceled).ToSelectList()) - @Html.ValidationMessageFor(model => model.PointsForPurchases_Canceled) -
            + @Html.SmartLabelFor(model => model.PointsForPurchases_Awarded) + + @Html.EnumSettingEditorFor(model => model.PointsForPurchases_Awarded) + @Html.ValidationMessageFor(model => model.PointsForPurchases_Awarded) +
            + @Html.SmartLabelFor(model => model.PointsForPurchases_Canceled) + + @Html.EnumSettingEditorFor(model => model.PointsForPurchases_Canceled) + @Html.ValidationMessageFor(model => model.PointsForPurchases_Canceled) +
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml new file mode 100644 index 0000000000..1251dcdfff --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Search.cshtml @@ -0,0 +1,340 @@ +@using SmartStore.Core.Search; +@model SearchSettingsModel +@{ + ViewBag.Title = T("Search.Title").Text; +} +@using (Html.BeginForm()) +{ +
            +
            + + @T("Search.Title") +
            +
            + +
            +
            + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) + + @(Html.SmartStore().TabStrip().Name("searchsettings-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Admin.Catalog").Text).Content(@TabCatalogSettings()).Selected(true); + x.Add().Text(T("Forum.Forum").Text).Content(@TabForumSettings()); + })) +} + +@helper TabCatalogSettings() +{ + + + + + + + + + + + + + + @if (Model.SearchFieldsNote.HasValue()) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if (Model.IsMegaSearchInstalled) + { + + + + + } + + + + +
            + @Html.SmartLabelFor(x => x.SearchMode) + + @Html.SettingEditorFor(x => x.SearchMode, Html.DropDownListFor(x => x.SearchMode, Model.AvailableSearchModes)) + @Html.ValidationMessageFor(x => x.SearchMode) +
            +
            + @T("Admin.Configuration.Settings.Search.WildcardSearchNote") +
            +
            + @Html.SmartLabelFor(x => x.SearchFields) + + @Html.ListBoxFor(x => x.SearchFields, + new MultiSelectList(Model.AvailableSearchFields, "Value", "Text"), + new { multiple = "multiple" }) + @Html.ValidationMessageFor(x => x.SearchFields) +
            +
            + @Html.Raw(Model.SearchFieldsNote) +
            +
            + @Html.SmartLabelFor(x => x.DefaultSortOrder) + + @Html.SettingEditorFor(x => x.DefaultSortOrder, Html.DropDownListFor(x => x.DefaultSortOrder, Model.AvailableSortOrderModes)) + @Html.ValidationMessageFor(x => x.DefaultSortOrder) +
            +
            +
            @T("Search.InstantSearch")
            +
            +
            + @Html.SmartLabelFor(x => x.InstantSearchEnabled) + + @Html.SettingEditorFor(x => x.InstantSearchEnabled, Html.CheckBoxFor(x => x.InstantSearchEnabled, new { data_toggler_for = "#pnlInstantSearch" })) + @Html.ValidationMessageFor(x => x.InstantSearchEnabled) +
            + @Html.SmartLabelFor(x => x.ShowProductImagesInInstantSearch) + + @Html.SettingEditorFor(x => x.ShowProductImagesInInstantSearch) + @Html.ValidationMessageFor(x => x.ShowProductImagesInInstantSearch) +
            + @Html.SmartLabelFor(x => x.InstantSearchNumberOfProducts) + + @Html.SettingEditorFor(x => x.InstantSearchNumberOfProducts) + @Html.ValidationMessageFor(x => x.InstantSearchNumberOfProducts) +
            + @Html.SmartLabelFor(x => x.InstantSearchTermMinLength) + + @Html.SettingEditorFor(x => x.InstantSearchTermMinLength) + @Html.ValidationMessageFor(x => x.InstantSearchTermMinLength) +
            +
            +
            @T("Search.ResultFiltering")
            +
            +
            + @Html.SmartLabelFor(x => x.FilterMinHitCount) + + @Html.SettingEditorFor(x => x.FilterMinHitCount) + @Html.ValidationMessageFor(x => x.FilterMinHitCount) +
            + @Html.SmartLabelFor(x => x.FilterMaxChoicesCount) + + @Html.SettingEditorFor(x => x.FilterMaxChoicesCount) + @Html.ValidationMessageFor(x => x.FilterMaxChoicesCount) +
            + +
            + @Html.SmartStore().TabStrip().Name("common-catalog-facets-settings").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => + { + x.Add() + .Text(T("Search.Facet.Category").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.CategoryFacet, CreateFacetViewData("CategoryFacet")).ToHtmlString()) + .Selected(true); + + x.Add() + .Text(T("Search.Facet.Manufacturer").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.BrandFacet, CreateFacetViewData("BrandFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.Price").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.PriceFacet, CreateFacetViewData("PriceFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.Rating").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.RatingFacet, CreateFacetViewData("RatingFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.DeliveryTime").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.DeliveryTimeFacet, CreateFacetViewData("DeliveryTimeFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.Availability").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.AvailabilityFacet, CreateFacetViewData("AvailabilityFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.NewArrivals").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.NewArrivalsFacet, CreateFacetViewData("NewArrivalsFacet")).ToHtmlString()); + }) +
            +} + +@helper TabForumSettings() +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if (Model.IsMegaSearchInstalled) + { + + + + + } + + + + +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.SearchMode) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.SearchMode, Html.DropDownListFor(x => x.ForumSearchSettings.SearchMode, Model.ForumSearchSettings.AvailableSearchModes)) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.SearchMode) +
            +
            + @T("Admin.Configuration.Settings.Search.WildcardSearchNote") +
            +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.SearchFields) + + @Html.ListBoxFor(x => x.ForumSearchSettings.SearchFields, + new MultiSelectList(Model.ForumSearchSettings.AvailableSearchFields, "Value", "Text"), + new { multiple = "multiple" }) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.SearchFields) +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.DefaultSortOrder) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.DefaultSortOrder, Html.DropDownListFor(x => x.ForumSearchSettings.DefaultSortOrder, Model.ForumSearchSettings.AvailableDefaultSortOrders)) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.DefaultSortOrder) +
            +
            +
            @T("Search.InstantSearch")
            +
            +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.InstantSearchEnabled) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.InstantSearchEnabled, Html.CheckBoxFor(x => x.ForumSearchSettings.InstantSearchEnabled, new { data_toggler_for = "#pnlForumInstantSearch" })) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.InstantSearchEnabled) +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.InstantSearchNumberOfHits) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.InstantSearchNumberOfHits) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.InstantSearchNumberOfHits) +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.InstantSearchTermMinLength) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.InstantSearchTermMinLength) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.InstantSearchTermMinLength) +
            +
            +
            @T("Search.ResultFiltering")
            +
            +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.FilterMinHitCount) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.FilterMinHitCount) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.FilterMinHitCount) +
            + @Html.SmartLabelFor(x => x.ForumSearchSettings.FilterMaxChoicesCount) + + @Html.SettingEditorFor(x => x.ForumSearchSettings.FilterMaxChoicesCount) + @Html.ValidationMessageFor(x => x.ForumSearchSettings.FilterMaxChoicesCount) +
            + +
            + @Html.SmartStore().TabStrip().Name("common-forum-facets-settings").Style(TabsStyle.Material).Position(TabsPosition.Top).Items(x => + { + x.Add() + .Text(T("Search.Facet.Forum").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.ForumSearchSettings.ForumFacet, CreateFacetViewData("ForumSearchSettings.ForumFacet")).ToHtmlString()) + .Selected(true); + + x.Add() + .Text(T("Search.Facet.Customer").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.ForumSearchSettings.CustomerFacet, CreateFacetViewData("ForumSearchSettings.CustomerFacet")).ToHtmlString()); + + x.Add() + .Text(T("Search.Facet.Date").Text) + .Content(Html.Partial("Partials/CommonFacet", Model.ForumSearchSettings.DateFacet, CreateFacetViewData("ForumSearchSettings.DateFacet")).ToHtmlString()); + }) +
            +} + +@functions +{ + private ViewDataDictionary CreateFacetViewData(string fieldPrefix) + { + // Do not change field prefix of this.ViewData. + var viewData = new ViewDataDictionary(ViewData) + { + TemplateInfo = new TemplateInfo { HtmlFieldPrefix = fieldPrefix } + }; + return viewData; + } +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml index 0201bf61ce..1cfc0d969d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml @@ -3,37 +3,23 @@ ViewBag.Title = T("Admin.Configuration.Settings.Shipping").Text; } @using (Html.BeginForm()) -{ - - +{
            @T("Admin.Configuration.Settings.Shipping")
            - +
            - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false) @@ -41,28 +27,30 @@ @Html.SmartLabelFor(model => model.FreeShippingOverXEnabled) - - - - - - - - + + + + + + + + + + - - + + + + + - - -
            - @Html.SettingEditorFor(model => model.FreeShippingOverXEnabled) + @Html.SettingEditorFor(model => model.FreeShippingOverXEnabled, Html.CheckBoxFor(model => model.FreeShippingOverXEnabled, new { data_toggler_for = "#pnlFreeShipping" })) @Html.ValidationMessageFor(model => model.FreeShippingOverXEnabled)
            - @Html.SmartLabelFor(model => model.FreeShippingOverXValue) - - @Html.SettingEditorFor(model => model.FreeShippingOverXValue) - @Html.ValidationMessageFor(model => model.FreeShippingOverXValue) -
            - @Html.SmartLabelFor(model => model.FreeShippingOverXIncludingTax) - - @Html.SettingEditorFor(model => model.FreeShippingOverXIncludingTax) - @Html.ValidationMessageFor(model => model.FreeShippingOverXIncludingTax) -
            + @Html.SmartLabelFor(model => model.FreeShippingOverXValue) + + @Html.SettingEditorFor(model => model.FreeShippingOverXValue, null, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.ValidationMessageFor(model => model.FreeShippingOverXValue) +
            + @Html.SmartLabelFor(model => model.FreeShippingOverXIncludingTax) + + @Html.SettingEditorFor(model => model.FreeShippingOverXIncludingTax) + @Html.ValidationMessageFor(model => model.FreeShippingOverXIncludingTax) +
            @Html.SmartLabelFor(model => model.EstimateShippingEnabled) @@ -83,26 +71,31 @@
            - @Html.SmartLabelFor(model => model.SuppressShippingOptsCheckoutStepIfOnlyOneActiveOpt) + @Html.SmartLabelFor(model => model.SkipShippingIfSingleOption) - @Html.SettingEditorFor(model => model.SuppressShippingOptsCheckoutStepIfOnlyOneActiveOpt) - @Html.ValidationMessageFor(model => model.SuppressShippingOptsCheckoutStepIfOnlyOneActiveOpt) + @Html.SettingEditorFor(model => model.SkipShippingIfSingleOption) + @Html.ValidationMessageFor(model => model.SkipShippingIfSingleOption)
            + @Html.SmartLabelFor(model => model.ChargeOnlyHighestProductShippingSurcharge) + + @Html.SettingEditorFor(model => model.ChargeOnlyHighestProductShippingSurcharge) + @Html.ValidationMessageFor(model => model.ChargeOnlyHighestProductShippingSurcharge) +
            @Html.SmartLabelFor(model => model.ShippingOriginAddress) - @Html.SettingOverrideCheckbox(model => Model.ShippingOriginAddress, "#pnlShippingOriginAddress") + @Html.SettingEditorFor(model => Model.ShippingOriginAddress, @
            + @Html.EditorFor(model => model.ShippingOriginAddress, "Address") +
            )
            - @Html.EditorFor(model => model.ShippingOriginAddress, "Address") -
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml index 8c1db1d995..8eb7e14064 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml @@ -10,56 +10,32 @@ @T("Admin.Configuration.Settings.ShoppingCart")
            - +
            + - function toggleMiniShoppingCartEnabled() { - if ($('#@Html.FieldIdFor(model => model.MiniShoppingCartEnabled)').is(':checked')) { - $('#pnlMiniShoppingCartProductNumber').show(); - $('#pnlShowProductImagesInMiniShoppingCart').show(); - } - else { - $('#pnlMiniShoppingCartProductNumber').hide(); - $('#pnlShowProductImagesInMiniShoppingCart').hide(); - } - } + Html.RenderAction("StoreScopeConfiguration", "Setting"); - function toggleEmailWishlistEnabled() { - if ($('#@Html.FieldIdFor(model => model.EmailWishlistEnabled)').is(':checked')) { - $('#pnlAllowAnonymousUsersToEmailWishlist').show(); - } - else { - $('#pnlAllowAnonymousUsersToEmailWishlist').hide(); - } - } - - - - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) + @Html.ValidationSummary(false) - @(Html.SmartStore().TabStrip().Name("catalogsettings-edit").Items(x => - { - x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.CartSettings").Text).Content(@TabCartSettings()).Selected(true); - x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.Checkout").Text).Content(@TabCheckoutSettings()); - x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.WishlistSettings").Text).Content(@TabWishlistSettings()); - })) + @(Html.SmartStore().TabStrip().Name("catalogsettings-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.CartSettings").Text).Content(@TabCartSettings()).Selected(true); + x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.Checkout").Text).Content(@TabCheckoutSettings()); + x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.WishlistSettings").Text).Content(@TabWishlistSettings()); + })) } @helper TabCartSettings() @@ -158,29 +134,20 @@ @Html.SmartLabelFor(model => model.MiniShoppingCartEnabled) - @Html.SettingEditorFor(model => model.MiniShoppingCartEnabled) + @Html.SettingEditorFor(model => model.MiniShoppingCartEnabled, Html.CheckBoxFor(model => model.MiniShoppingCartEnabled, new { data_toggler_for = "#pnlShowProductImagesInMiniShoppingCart" })) @Html.ValidationMessageFor(model => model.MiniShoppingCartEnabled) - - - @Html.SmartLabelFor(model => model.MiniShoppingCartProductNumber) - - - @Html.SettingEditorFor(model => model.MiniShoppingCartProductNumber) - @Html.ValidationMessageFor(model => model.MiniShoppingCartProductNumber) - - - - - @Html.SmartLabelFor(model => model.ShowProductImagesInMiniShoppingCart) - - - @Html.SettingEditorFor(model => model.ShowProductImagesInMiniShoppingCart) - @Html.ValidationMessageFor(model => model.ShowProductImagesInMiniShoppingCart) - - - + + + @Html.SmartLabelFor(model => model.ShowProductImagesInMiniShoppingCart) + + + @Html.SettingEditorFor(model => model.ShowProductImagesInMiniShoppingCart) + @Html.ValidationMessageFor(model => model.ShowProductImagesInMiniShoppingCart) + + +
            @@ -214,15 +181,6 @@ @Html.ValidationMessageFor(model => model.CrossSellsNumber) - - - @Html.SmartLabelFor(model => model.RoundPricesDuringCalculation) - - - @Html.SettingEditorFor(model => model.RoundPricesDuringCalculation) - @Html.ValidationMessageFor(model => model.RoundPricesDuringCalculation) - - } @@ -279,7 +237,7 @@ @Html.SmartLabelFor(model => model.EmailWishlistEnabled) - @Html.SettingEditorFor(model => model.EmailWishlistEnabled) + @Html.SettingEditorFor(model => model.EmailWishlistEnabled, Html.CheckBoxFor(model => model.EmailWishlistEnabled, new { data_toggler_for = "#pnlAllowAnonymousUsersToEmailWishlist" })) @Html.ValidationMessageFor(model => model.EmailWishlistEnabled) @@ -297,7 +255,7 @@ @helper TabCheckoutSettings() { - +
            @@ -347,16 +304,13 @@ @Html.SmartLabelFor(model => model.ThirdPartyEmailHandOver)
            @@ -319,8 +277,7 @@ @Html.SmartLabelFor(model => model.NewsLetterSubscription)
            - @Html.SettingOverrideCheckbox(model => Model.NewsLetterSubscription) - @Html.DropDownListFor(model => model.NewsLetterSubscription, Model.AvailableNewsLetterSubscriptions) + @Html.SettingEditorFor(model => model.NewsLetterSubscription, Html.DropDownListFor(model => model.NewsLetterSubscription, Model.AvailableNewsLetterSubscriptions)) @Html.ValidationMessageFor(model => model.NewsLetterSubscription)
            - @Html.SettingOverrideCheckbox(model => Model.ThirdPartyEmailHandOver) - @Html.DropDownListFor(model => model.ThirdPartyEmailHandOver, Model.AvailableThirdPartyEmailHandOver) + @Html.SettingEditorFor(model => model.ThirdPartyEmailHandOver, Html.DropDownListFor(model => model.ThirdPartyEmailHandOver, Model.AvailableThirdPartyEmailHandOver)) @Html.ValidationMessageFor(model => model.ThirdPartyEmailHandOver)
            -

            - @(Html.LocalizedEditor("setting-shopping-cart-localized", @ @@ -364,13 +318,11 @@ @Html.SmartLabelFor(model => model.Locales[item].ThirdPartyEmailHandOverLabel) - - -
            - @Html.TextAreaFor(model => Model.Locales[item].ThirdPartyEmailHandOverLabel, new { @class = "input-large" }) - @Html.ValidationMessageFor(model => model.Locales[item].ThirdPartyEmailHandOverLabel) -
            + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextAreaFor(model => Model.Locales[item].ThirdPartyEmailHandOverLabel) + @Html.ValidationMessageFor(model => model.Locales[item].ThirdPartyEmailHandOverLabel)
            @@ -381,7 +333,7 @@ @Html.SmartLabelFor(model => model.ThirdPartyEmailHandOverLabel) - @Html.TextAreaFor(model => model.ThirdPartyEmailHandOverLabel, new { @class = "input-large" }) + @Html.TextAreaFor(model => model.ThirdPartyEmailHandOverLabel) @Html.ValidationMessageFor(model => model.ThirdPartyEmailHandOverLabel) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml index 10acb90540..4c99912b3b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml @@ -1,13 +1,11 @@ @model StoreScopeConfigurationModel @using SmartStore.Core -
            - +
            +
            - - + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml index 7f2e872994..da8451c205 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml @@ -1,73 +1,26 @@ -@model TaxSettingsModel -@using Telerik.Web.Mvc.UI; +@using SmartStore.Core.Domain.Tax +@model TaxSettingsModel @{ ViewBag.Title = T("Admin.Configuration.Settings.Tax").Text; } @using (Html.BeginForm()) -{ - +{
            @@ -75,11 +28,16 @@ @T("Admin.Configuration.Settings.Tax")
            - +
            - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.ValidationSummary(false) + + Html.RenderAction("StoreScopeConfiguration", "Setting"); + + @Html.ValidationSummary(false)
            -
            - @T("Admin.Configuration.Settings.StoreScope") -
            + @Html.SmartLabel("store-scope-configuration", T("Admin.Configuration.Settings.StoreScope"))
            @Html.DropDownList("store-scope-configuration", Model.AllStores, new { onchange = "setLocation(this.value);" }) @@ -16,13 +14,12 @@ @if (Model.StoreId > 0) {
              - +   +
            + + +
            @@ -93,25 +51,24 @@ @*not relevant for european market but we have to deal with this option later*@ - @* - - - *@ + @* + + + *@ - + - + - + - - - + + + + - - - - - - - - - - - + + + + + + + + + + - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + +
            - @Html.SmartLabelFor(model => model.AllowCustomersToSelectTaxDisplayType) - - @Html.EditorFor(model => model.AllowCustomersToSelectTaxDisplayType) - @Html.ValidationMessageFor(model => model.AllowCustomersToSelectTaxDisplayType) -
            + @Html.SmartLabelFor(model => model.AllowCustomersToSelectTaxDisplayType) + + @Html.CheckBoxFor(model => model.AllowCustomersToSelectTaxDisplayType, new { data_toggler_for = "#pnlTaxDisplayType" }) + @Html.ValidationMessageFor(model => model.AllowCustomersToSelectTaxDisplayType) +
            @Html.SmartLabelFor(model => model.TaxDisplayType) - @Html.SettingOverrideCheckbox(model => Model.TaxDisplayType) - @Html.DropDownListForEnum(model => model.TaxDisplayType) - @Html.ValidationMessageFor(model => model.TaxDisplayType) - + @Html.EnumSettingEditorFor(model => model.TaxDisplayType) + @Html.ValidationMessageFor(model => model.TaxDisplayType) +
            @@ -185,91 +142,95 @@ @Html.SmartLabelFor(model => model.TaxBasedOn) - @Html.SettingOverrideCheckbox(model => Model.TaxBasedOn) - @Html.DropDownListForEnum(model => model.TaxBasedOn) - @Html.ValidationMessageFor(model => model.TaxBasedOn) - + @Html.EnumSettingEditorFor(model => model.TaxBasedOn) + @Html.ValidationMessageFor(model => model.TaxBasedOn) +
            @Html.SmartLabelFor(model => model.DefaultTaxAddress) - @Html.SettingOverrideCheckbox(model => Model.DefaultTaxAddress, "#pnlDefaultTaxAddress") + @Html.SettingEditorFor(model => Model.DefaultTaxAddress, @
            + @Html.EditorFor(x => x.DefaultTaxAddress, "Address") +
            )
            - @Html.EditorFor(x => x.DefaultTaxAddress, "Address") -

            + @Html.SmartLabelFor(model => model.AuxiliaryServicesTaxingType) + + @Html.EnumSettingEditorFor(model => model.AuxiliaryServicesTaxingType) + @Html.ValidationMessageFor(model => model.AuxiliaryServicesTaxingType) +
            @Html.SmartLabelFor(model => model.ShippingIsTaxable) - @Html.SettingEditorFor(model => model.ShippingIsTaxable) + @Html.SettingEditorFor(model => model.ShippingIsTaxable, Html.CheckBoxFor(model => model.ShippingIsTaxable, new { data_toggler_for = "#pnlShippingIsTaxable" })) @Html.ValidationMessageFor(model => model.ShippingIsTaxable)
            - @Html.SmartLabelFor(model => model.ShippingPriceIncludesTax) - - @Html.SettingEditorFor(model => model.ShippingPriceIncludesTax) - @Html.ValidationMessageFor(model => model.ShippingPriceIncludesTax) -
            - @Html.SmartLabelFor(model => model.ShippingTaxClassId) - - @Html.SettingOverrideCheckbox(model => Model.ShippingTaxClassId) - @Html.DropDownListFor(model => model.ShippingTaxClassId, Model.ShippingTaxCategories, T("Common.PleaseSelect").Text) - @Html.ValidationMessageFor(model => model.ShippingTaxClassId) -
            -
            -
            + @Html.SmartLabelFor(model => model.ShippingPriceIncludesTax) + + @Html.SettingEditorFor(model => model.ShippingPriceIncludesTax) + @Html.ValidationMessageFor(model => model.ShippingPriceIncludesTax) +
            + @Html.SmartLabelFor(model => model.ShippingTaxClassId) + + @Html.SettingEditorFor(model => model.ShippingTaxClassId, Html.DropDownListFor(model => model.ShippingTaxClassId, Model.ShippingTaxCategories, T("Common.PleaseSelect"))) + @Html.ValidationMessageFor(model => model.ShippingTaxClassId) +
            @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeIsTaxable) - @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeIsTaxable) + @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeIsTaxable, + Html.CheckBoxFor(model => model.PaymentMethodAdditionalFeeIsTaxable, new { data_toggler_for = "#pnlPaymentMethodAdditionalFeeIsTaxable" })) @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeIsTaxable)
            - @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeIncludesTax) - - @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeIncludesTax) - @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeIncludesTax) -
            - @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeTaxClassId) - - @Html.SettingOverrideCheckbox(model => Model.PaymentMethodAdditionalFeeTaxClassId) - @Html.DropDownListFor(model => model.PaymentMethodAdditionalFeeTaxClassId, Model.PaymentMethodAdditionalFeeTaxCategories, T("Common.PleaseSelect").Text) - @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeTaxClassId) -
            + @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeIncludesTax) + + @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeIncludesTax) + @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeIncludesTax) +
            + @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeTaxClassId) + + @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeTaxClassId, + Html.DropDownListFor(model => model.PaymentMethodAdditionalFeeTaxClassId, Model.PaymentMethodAdditionalFeeTaxCategories, T("Common.PleaseSelect"))) + @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeTaxClassId) +

            @@ -280,55 +241,56 @@ @Html.SmartLabelFor(model => model.EuVatEnabled)
            - @Html.SettingEditorFor(model => model.EuVatEnabled) + @Html.SettingEditorFor(model => model.EuVatEnabled, Html.CheckBoxFor(model => model.EuVatEnabled, new { data_toggler_for = "#pnlEuVat" })) @Html.ValidationMessageFor(model => model.EuVatEnabled)
            - @Html.SmartLabelFor(model => model.EuVatShopCountryId) - - @Html.SettingOverrideCheckbox(model => Model.EuVatShopCountryId) - @Html.DropDownListFor(model => model.EuVatShopCountryId, Model.EuVatShopCountries, T("Admin.Address.SelectCountry").Text) - @Html.ValidationMessageFor(model => model.EuVatShopCountryId) -
            - @Html.SmartLabelFor(model => model.EuVatAllowVatExemption) - - @Html.SettingEditorFor(model => model.EuVatAllowVatExemption) - @Html.ValidationMessageFor(model => model.EuVatAllowVatExemption) -
            - @Html.SmartLabelFor(model => model.EuVatUseWebService) - - @Html.SettingEditorFor(model => model.EuVatUseWebService) - @Html.ValidationMessageFor(model => model.EuVatUseWebService) -
            - @Html.SmartLabelFor(model => model.EuVatEmailAdminWhenNewVatSubmitted) - - @Html.SettingEditorFor(model => model.EuVatEmailAdminWhenNewVatSubmitted) - @Html.ValidationMessageFor(model => model.EuVatEmailAdminWhenNewVatSubmitted) -
            - @Html.SmartLabelFor(model => model.VatRequired) - - @Html.SettingEditorFor(model => model.VatRequired) - @Html.ValidationMessageFor(model => model.VatRequired) -
            + @Html.SmartLabelFor(model => model.EuVatShopCountryId) + + @Html.SettingEditorFor(model => model.EuVatShopCountryId, Html.DropDownListFor(model => model.EuVatShopCountryId, Model.EuVatShopCountries, T("Admin.Address.SelectCountry"))) + @Html.ValidationMessageFor(model => model.EuVatShopCountryId) +
            + @Html.SmartLabelFor(model => model.EuVatAllowVatExemption) + + @Html.SettingEditorFor(model => model.EuVatAllowVatExemption) + @Html.ValidationMessageFor(model => model.EuVatAllowVatExemption) +
            + @Html.SmartLabelFor(model => model.EuVatUseWebService) + + @Html.SettingEditorFor(model => model.EuVatUseWebService) + @Html.ValidationMessageFor(model => model.EuVatUseWebService) +
            + @Html.SmartLabelFor(model => model.EuVatEmailAdminWhenNewVatSubmitted) + + @Html.SettingEditorFor(model => model.EuVatEmailAdminWhenNewVatSubmitted) + @Html.ValidationMessageFor(model => model.EuVatEmailAdminWhenNewVatSubmitted) +
            + @Html.SmartLabelFor(model => model.VatRequired) + + @Html.SettingEditorFor(model => model.VatRequired) + @Html.ValidationMessageFor(model => model.VatRequired) +
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Delete.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Delete.cshtml deleted file mode 100644 index 2ecbda038a..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Delete.cshtml +++ /dev/null @@ -1,17 +0,0 @@ -@model SmartStore.Web.Framework.Modelling.DeleteConfirmationModel -@*codehint: sm-edit*@ -@using (Html.BeginForm(Model.ActionName, Model.ControllerName, new { id = Model.Id }, FormMethod.Post, new { style = "margin:0;" })) { -
            -

            - @T("Admin.Common.DeleteConfirmation") -

            -
            - - -
            -
            -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/AccessPermissions.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/AccessPermissions.cshtml new file mode 100644 index 0000000000..2aa5b1eb8b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/AccessPermissions.cshtml @@ -0,0 +1,32 @@ +@{ + var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); + var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + var value = ViewData.Model != null ? string.Join(",", ViewData.Model) : null; + var url = Url.Action("AllAccessPermissions", "Security", new { selected = value }); +} + + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml index 3cd761d0c3..15061f8537 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml @@ -1,33 +1,7 @@ @model AddressModel -@if (Model.CountryEnabled && Model.StateProvinceEnabled) -{ - -} @Html.HiddenFor(model => model.Id) +@Html.HiddenFor(model => model.TitleEnabled) @Html.HiddenFor(model => model.FirstNameEnabled) @Html.HiddenFor(model => model.FirstNameRequired) @Html.HiddenFor(model => model.LastNameEnabled) @@ -51,7 +25,20 @@ @Html.HiddenFor(model => model.PhoneRequired) @Html.HiddenFor(model => model.FaxEnabled) @Html.HiddenFor(model => model.FaxRequired) - + +
            + @if (Model.TitleEnabled) + { + + + + + } @if (Model.FirstNameEnabled) { @@ -119,8 +106,21 @@ @Html.SmartLabelFor(model => model.CountryId) } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml index 04de1aee37..e4b8123850 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml @@ -1,4 +1,5 @@ @model int? + @using SmartStore.Core; @using SmartStore.Web.Framework.UI; @using SmartStore.Utilities; @@ -6,16 +7,19 @@ @functions { bool? _minimalMode = null; string _fieldName = null; - + int _entityId = 0; + string _entityName = null; + string _fileTypes = null; + private bool MinimalMode { get { if (!_minimalMode.HasValue) { - _minimalMode = ViewData.ContainsKey("minimalMode") ? ViewData["minimalMode"].Convert() : false; + _minimalMode = ViewData.ContainsKey("minimalMode") ? ViewData["minimalMode"].Convert() : false; } - return _minimalMode.Value; + return _minimalMode.Value; } } @@ -30,98 +34,130 @@ return _fieldName; } } + + private int EntityId + { + get + { + if (_entityId == 0) + { + _entityId = ViewData.ContainsKey("entityId") ? ViewData["entityId"].Convert() : 0; + } + return _entityId; + } + } + + private string EntityName + { + get + { + if (_entityName == null) + { + _entityName = ViewData.ContainsKey("entityName") ? ViewData["entityName"].Convert() : ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + } + return _entityName; + } + } + + private string FileTypes + { + get + { + if (_fileTypes == null) + { + _fileTypes = ViewData.ContainsKey("fileTypes") ? ViewData["fileTypes"].Convert() : ""; + } + return _fileTypes; + } + } } @{ - var clientId = "download-editor-" + CommonHelper.GenerateRandomInteger(); + var random = CommonHelper.GenerateRandomInteger(); + var clientId = "download-editor-" + random; var downloadService = EngineContext.Current.Resolve(); - var download = downloadService.GetDownloadById(Model.GetValueOrDefault()); - var initiallyShowUrlPanel = false; - var hasFile = false; - var downloadUrl = ""; - if (download != null) { - downloadUrl = Url.Action("DownloadFile", "Download", new { downloadId = download.Id }); - initiallyShowUrlPanel = !MinimalMode && download.UseDownloadUrl; - hasFile = !download.UseDownloadUrl; - } - - Html.AddScriptParts("~/bundles/fileupload"); - Html.AddCssFileParts("~/css/fileupload"); - Html.AddScriptParts(true, "~/Administration/Scripts/smartstore.download.js"); + var download = downloadService.GetDownloadById(Model.GetValueOrDefault()); + var fileName = string.Empty; + var initiallyShowUrlPanel = false; + var hasFile = false; + var downloadUrl = ""; + if (download != null) { + downloadUrl = Url.Action("DownloadFile", "Download", new { downloadId = download.Id }); + initiallyShowUrlPanel = !MinimalMode && download.UseDownloadUrl; + hasFile = !download.UseDownloadUrl; + fileName = string.IsNullOrWhiteSpace(download.Filename) || download.Filename.IsCaseInsensitiveEqual("undefined") + ? download.Id.ToString() + : string.Concat(download.Filename, download.Extension); + } + + Html.AddScriptParts(true, "~/Administration/Scripts/smartstore.download.js"); } -
            + data-upload-url="@Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })" + data-delete-url="@Url.Action("DeleteDownload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })"> - - @if (!MinimalMode) - { -
            - - - + - } - else if (hasFile) - { - - - - } + } +
            -
            - +
            -
            - +
            @if (hasFile) { - - @download.Filename@download.Extension - - } - - - @if (hasFile) - { - - } - else - { - @T("Common.Fileuploader.Upload") - } - - - - - - -
            - -
            -
            + - -
             
            + } +
            + @(Html.SmartStore().FileUploader() + .UploadUrl(Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })) + .AcceptedFileTypes("") + .IconCssClass(MinimalMode ? "fa fa-upload" : "") + .ButtonOutlineStyle(hasFile) + .UploadText(hasFile ? T("Common.Replace").Text : T("Common.Fileuploader.Upload").Text) + .ShowRemoveButton(false) + .ShowRemoveButtonAfterUpload(false) + .OnUploadingHandlerName("onUploading_" + random) + .OnCompletedHandlerName("onCompleted_" + random) + .OnUploadCompletedHandlerName("onUploadCompleted_" + random) + )
            @@ -129,21 +165,31 @@ @if (!MinimalMode) {
            -
            +
            @{ var value = download != null ? download.DownloadUrl : ""; } - - - - + +
            } -
            \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml new file mode 100644 index 0000000000..01cceea655 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml @@ -0,0 +1,157 @@ +@model TreeNode + +@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI.Fluent; +@using SmartStore.Collections; +@using SmartStore.Services.Messages; + +@helper TokenSelector() +{ + + + @T("Admin.Common.ChooseToken") + +} + +@{Html.SmartStore().Window() + .Name("addtoken-window") + .Size(WindowSize.FlexSmall) + .Title(T("Admin.Common.ChooseToken")) + .Content( + @ + @(Html.Telerik().TreeView() + .Name("token-treeview") + .Items(tv => + { + AddItemsToModelTree(tv, Model); + }) + .ShowLines(true) + .ClientEvents(events => + { + if (Model != null) + { + events.OnSelect("onNodeSelect"); + } + }) + .DragAndDrop(false)) + ) + .FooterContent(@ + + + ) + .Render(); +} + +@functions { + public static void AddItemsToModelTree(TreeViewItemFactory tv, TreeNode root) + { + if (root != null) + { + foreach (var node in root.Children) + { + + List tokens = new List(); + var curNode = node; + while (!curNode.IsRoot) + { + tokens.Insert(0, curNode.Value.Name); + curNode = curNode.Parent; + } + + tv.Add() + .Text(node.Value.Name) + .Value(String.Join(".", tokens)) + //.HtmlAttributes(new { _data-leaf = node.IsLeaf.ToString() }) + .HtmlAttributes(new { isleaf = node.IsLeaf.ToString().ToLower() }) + .Items(tvc => + { + + if (!node.IsLeaf) + { + AddItemsToModelTree(tvc, node); + } + }); + } + } + } +} + +@if (Model == null || !Model.HasChildren) +{ +
            + @T("Admin.ContentManagement.MessageTemplates.NoModelTree") +
            +} +else +{ + @TokenSelector() + + +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml deleted file mode 100644 index fc793a9bcb..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Picture.cshtml +++ /dev/null @@ -1,106 +0,0 @@ -@model int? -@using SmartStore.Core; -@using SmartStore.Web.Framework.UI; -@using SmartStore.Utilities; - -@functions { - private bool TransientUpload - { - get - { - if (ViewData.ContainsKey("transientUpload")) - { - var x = ViewData["transientUpload"].Convert(); - return x; - } - return false; - } - } -} - -@{ - var random = CommonHelper.GenerateRandomInteger(); - var clientId = "picture" + random; - var pictureService = EngineContext.Current.Resolve(); - int pictureId = Model.HasValue ? Model.Value : 0; - var picture = pictureService.GetPictureById(pictureId); - - Html.AddScriptParts("~/bundles/fileupload"); - Html.AddCssFileParts("~/css/fileupload"); -} - -
            - - @Html.HiddenFor(x => x, new { @class = "hidden"} ) - - - - - - - - @T("Common.Fileuploader.Upload") - - - - - - -
            - -
            -
            -
            - -
             
            -
            - -
            - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.SummerNote.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.SummerNote.cshtml deleted file mode 100644 index dce7c51a8a..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.SummerNote.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@model String -@using SmartStore.Web.Framework.UI; -@{ - var availableLangs = new string[] { "de-DE", "en-US" }; - var lang = WorkContext.WorkingLanguage.LanguageCulture; - if (!availableLangs.Contains(lang)) - { - lang = "en-US"; - } - - Html.AddScriptParts(true, - "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror.min.js", - "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/mode/xml/xml.min.js", - "//cdnjs.cloudflare.com/ajax/libs/codemirror/2.36.0/formatting.min.js", - "~/Content/editors/summernote/summernote.js", - "~/Content/editors/summernote/globalinit.js"); - - if (lang != "en-US") - { - Html.AddScriptParts(true, "~/Content/editors/summernote/langs/summernote-{0}.js".FormatInvariant(lang)); - } - - Html.AppendCssFileParts(true, - "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror.min.css", - "~/Content/editors/summernote/summernote.css"); -} - - - -@Html.TextArea(string.Empty, /* Name suffix */ - (string)ViewData.TemplateInfo.FormattedModelValue, /* Initial value */ - new { @class = "summernote-editor", data_upload_url = Url.Action("UploadImageAjax", "Media") } -) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml deleted file mode 100644 index 04db8e0e26..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml +++ /dev/null @@ -1,57 +0,0 @@ -@model String -@using SmartStore.Web.Framework.UI; - -@functions { - private bool FullPage - { - get - { - if (ViewData.ContainsKey("fullPage")) - { - var x = ViewData["fullPage"].Convert(); - return x; - } - return false; - } - } -} - -@{ - var availableLangs = new string[] { "de", "en" }; - var lang = WorkContext.WorkingLanguage.UniqueSeoCode; - if (!availableLangs.Contains(lang)) - { - lang = "en"; - } - - Html.AddScriptParts(true, "~/Content/editors/ckeditor/ckeditor.js"); -} - - - -@Html.TextArea( - string.Empty, /* Name suffix */ - (string)ViewData.TemplateInfo.FormattedModelValue, /* Initial value */ - new { @class = "", style = "", data_fullpage = FullPage.ToString().ToLower(), data_upload_url = Url.Action("UploadImageAjax", "Media") } -) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/SelectList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/SelectList.cshtml new file mode 100644 index 0000000000..3a0fa42651 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/SelectList.cshtml @@ -0,0 +1,28 @@ +@using SmartStore.Web.Framework; +@{ + var multiple = GetValue("multiple"); + var dataTags = GetValue("data_tags"); + var selectOptions = GetValue>("selectOptions"); + var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); + var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); +} + + +@functions +{ + private TProperty GetValue(string key, TProperty defaultValue = default(TProperty)) + { + if (ViewData.ContainsKey(key)) + { + return (TProperty)ViewData[key]; + } + + return defaultValue; + } +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml index feba06977d..6b59939225 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml @@ -1,244 +1,90 @@ -@model string +@model string[] + +@using Newtonsoft.Json.Linq; +@using SmartStore.Utilities; + +@functions { + private string[] Value + { + get + { + string[] value = null; + if (ViewData.Model != null) + { + value = ViewData.Model; + } + return value; + } + } +} @{ var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + var widgetProvider = EngineContext.Current.Resolve(); + var jsonZones = (JObject)widgetProvider.GetAllKnownWidgetZones(); + + var areas = + from p in jsonZones["WidgetZonesAreas"] + select p; - string availableZones = ""; - if (ViewData.ContainsKey("AvailableZones")) + var zones = new List(); + var userDefinedZones = Value != null ? Value.ToList() : new List(); + var selectedZones = new HashSet(userDefinedZones, StringComparer.OrdinalIgnoreCase); + + // add system zones + foreach (var area in areas) { - availableZones = (ViewData["AvailableZones"] as string) ?? ""; - } -} + var areaName = area["name"].ToString(); + var areasObj = + from p in area["zones"] + select p; - + var groupName = Inflector.Pascalize(T(areaName.ToString()).Text); + var group = new SelectListGroup { Name = groupName }; - + } + + // get selected items + var selectedValues = zones.Where(x => x.Selected == true).Select(x => x.Value.ToString()).ToArray(); + var cssClass = "form-control x-select2-hidden-accessible"; + var size = GetMetadata("size"); + if (size.HasValue()) + { + cssClass += " form-control-" + size; + } +} +@Html.ListBox("", + new MultiSelectList(zones, "Value", "Text", "Group.Name", selectedValues), + new { @class = cssClass, multiple = "multiple", size = 1, data_tags = "true", value = Value, name = name, id = id }) \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminBareLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminBareLayout.cshtml new file mode 100644 index 0000000000..ba5a4d2a70 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminBareLayout.cshtml @@ -0,0 +1,10 @@ +@{ + Layout = "_AdminLayout"; + Html.AddBodyCssClass("popup bare"); +} + +@section navbar +{ +} + +@RenderBody() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml new file mode 100644 index 0000000000..f7518da158 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml @@ -0,0 +1,82 @@ +@using SmartStore.Web.Framework.UI; + +@* Please keep in mind that this layout view is also referenced and used by plugins! *@ + +@{ + Layout = "_AdminRoot"; +} + +@if (ViewBag.RefreshPage == true) +{ + +} + +
            + @if (ViewData["warning.panel.message"] != null) + { +
            + @Html.Raw(ViewData["warning.panel.message"]) + +
            + } + +
            + @if (IsSectionDefined("header")) + { + @RenderSection("header") + } + else + { + + } + + @{ Html.RenderZone("beforemaincontent"); } + +
            +
            + @{ Html.RenderWidget("admin_content_before"); } + @RenderBody() +
            +
            +
            +
            +
            +
            +
            + + @{ Html.RenderZone("aftermaincontent"); } +
            + + @{ Html.RenderWidget("admin_content_after"); } +
            + +@{ Html.RenderPartial("_Notifications"); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminPopupLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminPopupLayout.cshtml new file mode 100644 index 0000000000..2190edbafe --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminPopupLayout.cshtml @@ -0,0 +1,10 @@ +@{ + Layout = "_AdminLayout"; + Html.AddBodyCssClass("popup"); +} + +@section navbar +{ +} + +@RenderBody() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml new file mode 100644 index 0000000000..10f624709d --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml @@ -0,0 +1,135 @@ +@using SmartStore; +@using SmartStore.Core; +@using SmartStore.Web.Framework.UI; +@using System.Web.Hosting; + +@{ + // page title + string adminPageTitle = ""; + if (!string.IsNullOrWhiteSpace(ViewBag.Title)) + { + adminPageTitle = ViewBag.Title + " / "; + } + adminPageTitle += T("Admin.PageTitle").Text; + + var adminJsRoot = "~/Administration/Scripts/"; + var vendorsRoot = "~/Content/vendors/"; + var contentRoot = "~/Content/"; + var jsRoot = "~/Scripts/"; + + // add css assets + var telerikCssRoot = "~/Administration/Content/telerik/css/2012.2.607/"; + + Html.AppendCssFileParts( + contentRoot + "fontastic/fontastic.css", + vendorsRoot + "fa5/css/all.css", + telerikCssRoot + "telerik.common.css", + vendorsRoot + "pnotify/css/pnotify.css", + vendorsRoot + "pnotify/css/pnotify.mobile.css", + vendorsRoot + "pnotify/css/pnotify.buttons.css"); + + // Theme + Html.AddCssFileParts(true, "~/Administration/Content/theme.scss"); + + if (WorkContext.WorkingLanguage.Rtl) + { + Html.AddCssFileParts(true, + telerikCssRoot + "telerik.rtl.css", + "~/Administration/Content/theme-rtl.scss"); + } + + // add js assets (Head) + Html.AppendScriptParts(ResourceLocation.Head, + vendorsRoot + "modernizr/modernizr.js", + vendorsRoot + "jquery/jquery-3.4.1.js", + //vendorsRoot + "jquery/jquery-migrate-3.0.0.js", + adminJsRoot + "jquery-shims.js"); + + Html.AppendScriptParts(ResourceLocation.Foot, + // Vendors + vendorsRoot + "underscore/underscore.js", + vendorsRoot + "underscore/underscore.string.js", + vendorsRoot + "jquery/jquery.addeasing.js", + vendorsRoot + "jquery-ui/effect.js", + vendorsRoot + "jquery-ui/effect-transfer.js", + vendorsRoot + "jquery-ui/position.js", + vendorsRoot + "jquery/jquery.unobtrusive-ajax.js", + vendorsRoot + "jquery/jquery.validate.js", + vendorsRoot + "jquery/jquery.validate.unobtrusive.js", + vendorsRoot + "jquery/jquery.scrollTo.js", + vendorsRoot + "jquery/jquery.sortable.js", + vendorsRoot + "moment/moment.js", + vendorsRoot + "datetimepicker/js/tempusdominus-bootstrap-4.js", + vendorsRoot + "colorpicker/js/bootstrap-colorpicker.js", + vendorsRoot + "colorpicker/js/bootstrap-colorpicker-globalinit.js", + vendorsRoot + "select2/js/select2.js", + vendorsRoot + "pnotify/js/pnotify.js", + vendorsRoot + "pnotify/js/pnotify.mobile.js", + vendorsRoot + "pnotify/js/pnotify.buttons.js", + vendorsRoot + "pnotify/js/pnotify.animate.js", + contentRoot + "bs4/js/bootstrap.bundle.js", + // Common + jsRoot + "underscore.mixins.js", + jsRoot + "smartstore.system.js", + jsRoot + "smartstore.touchevents.js", + jsRoot + "smartstore.jquery.utils.js", + jsRoot + "smartstore.globalization.js", + jsRoot + "jquery.validate.unobtrusive.custom.js", + jsRoot + "smartstore.viewport.js", + jsRoot + "smartstore.doajax.js", + jsRoot + "smartstore.eventbroker.js", + jsRoot + "smartstore.hacks.js", + jsRoot + "smartstore.common.js", + jsRoot + "smartstore.selectwrapper.js", + jsRoot + "smartstore.throbber.js", + jsRoot + "smartstore.thumbzoomer.js", + jsRoot + "smartstore.entitypicker.js", + jsRoot + "smartstore.rangeslider.js", + // Admin + adminJsRoot + "admin.common.js", + adminJsRoot + "admin.globalinit.js"); +} + + + + @adminPageTitle + + + + + @{ Html.RenderPartial("_ClientRes"); } + @{ Html.RenderPartial("ConditionalComments"); } + @Html.MetaAcceptLanguage() + + @Html.SmartCssFiles(this.Url, ResourceLocation.Head) + @Html.SmartScripts(this.Url, ResourceLocation.Head) + + + + @Html.CustomHead() + + + + @{ Html.RenderZone("head"); } + + + @{ Html.RenderZone("start"); } + @RenderBody() + @{ Html.RenderZone("aftercontent"); } + + @RenderSection("foot", required: false) + @Html.SmartCssFiles(this.Url, ResourceLocation.Foot) + @Html.SmartScripts(this.Url, ResourceLocation.Foot) + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "select2/js/i18n", "*.js", null) + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "moment/locale", "*.js", null) + + + @(Html.Telerik().ScriptRegistrar() + .jQuery(false) + .jQueryValidation(false) + .Globalization(true) + ) + @{ Html.RenderZone("end"); } + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Menu.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Menu.cshtml deleted file mode 100644 index 0a4800137a..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Menu.cshtml +++ /dev/null @@ -1,163 +0,0 @@ -@model TreeNode - -@*@using Telerik.Web.Mvc.UI;*@ -@using SmartStore.Services.Security; -@using SmartStore.Core.Domain.Security; -@using SmartStore.Collections; -@using SmartStore.Web.Framework.UI; - -@functions { - public string GetNodeLink(TreeNode node) - { - if (node.HasChildren) - { - return "javascript:void(0)"; - } - return node.Value.GenerateUrl(this.ViewContext.RequestContext); - } - - public string GetItemText(TreeNode node) - { - string result = null; - - if (node.Value.ResKey.HasValue()) - { - result = T(node.Value.ResKey).ToHtmlString(); - } - - if (!result.HasValue() || result.IsCaseInsensitiveEqual(node.Value.ResKey)) - { - result = node.Value.Text; - } - - return result; - } -} - - - - -@helper CreateDropdown(IEnumerable> nodes, bool isRoot) -{ - var isFirst = true; - var hasIcons = nodes.Any(x => x.Value.Icon.HasValue()); - - -} - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Menus/Admin.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Menus/Admin.cshtml new file mode 100644 index 0000000000..7ac4baa7e3 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Menus/Admin.cshtml @@ -0,0 +1,123 @@ +@model MenuModel + +@using SmartStore.Collections; +@using SmartStore.Web.Framework.UI; + +@{ + if (Model.Root == null) + { + return; + } +} + +@functions { + public string GetNodeLink(TreeNode node) + { + if (node.HasChildren) + { + return "javascript:void(0)"; + } + return node.Value.GenerateUrl(this.ViewContext.RequestContext); + } +} + + + +@helper CreateDropdown(IEnumerable> nodes, bool isRoot) +{ + var isFirst = true; + var hasIcons = nodes.Any(x => x.Value.Icon.HasValue()); + + +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml deleted file mode 100644 index eae6d73f32..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml +++ /dev/null @@ -1,167 +0,0 @@ -@using SmartStore.Core.Domain.Stores; -@using SmartStore.Services.Customers; -@using SmartStore.Services.Stores; -@using SmartStore.Core; - -@{ - var currentCustomer = this.WorkContext.CurrentCustomer; - var currentLanguage = this.WorkContext.WorkingLanguage; - var userName = (string)ViewBag.UserName; - var stores = (IList)ViewBag.Stores; - string currentLanguageCode = currentLanguage.UniqueSeoCode.EmptyNull().ToLower(); - string communityUrl = (currentLanguageCode == "de" ? - "http://community.smartstore.com" : - "http://community.smartstore.com/index.php?/&k=880ea6a14ea49e853634fbdc5015a024&setlanguage=1&cal_id=&langid=1"); - string docsUrl = GenerateHelpUrl(""); - var checkUpdateResult = ViewBag.CheckUpdateResult as CheckUpdateResult; -} - - - - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/AclSelector.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/AclSelector.cshtml new file mode 100644 index 0000000000..9abc2c7a00 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/AclSelector.cshtml @@ -0,0 +1,37 @@ +@model IAclSelector + +
            +
            +
            + @Html.SmartLabelFor(model => model.SubjectToAcl) +
            +
            + @Html.CheckBoxFor(model => model.SubjectToAcl, new { data_toggler_for = "#pnl-acl" }) + @Html.ValidationMessageFor(model => model.SubjectToAcl) +
            +
            +
            +
            + @Html.SmartLabelFor(model => model.AvailableCustomerRoles) +
            +
            + @if (Model.AvailableCustomerRoles != null && Model.AvailableCustomerRoles.Any()) + { + foreach (var role in Model.AvailableCustomerRoles) + { +
            + + +
            + } + } + else + { +
            + @T("Admin.Configuration.Acl.NoRolesDefined") +
            + } +
            +
            +
            \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/CsvConfiguration.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml similarity index 85% rename from src/Presentation/SmartStore.Web/Administration/Views/Shared/CsvConfiguration.cshtml rename to src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml index 9a1a8f1622..b616b32317 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/CsvConfiguration.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml @@ -1,5 +1,6 @@ @using SmartStore.Admin.Models.DataExchange; @model CsvConfigurationModel +
            + @Html.SmartLabelFor(model => model.Title) + + @Html.EditorFor(model => model.Title) + @Html.ValidationMessageFor(model => model.Title) +
            - @Html.DropDownListFor(model => model.CountryId, Model.AvailableCountries, T("Admin.Address.SelectCountry").Text) + @Html.DropDownList("CountryId", Model.AvailableCountries, + new + { + @class = "form-control country-input country-selector", + data_region_control_selector = "#" + Html.FieldIdFor(m => m.StateProvinceId), + data_states_ajax_url = Url.Action("GetStatesByCountryId", "Country"), + data_addEmptyStateIfRequired = "true" + }) @Html.ValidationMessageFor(model => model.CountryId) + +
            @if (ViewData["ShowGroupCaption"] != null && (bool)ViewData["ShowGroupCaption"]) { @@ -43,7 +44,7 @@ @Html.SmartLabelFor(x => x.Delimiter) @@ -52,7 +53,7 @@ @Html.SmartLabelFor(x => x.Quote) @@ -61,7 +62,7 @@ @Html.SmartLabelFor(x => x.Escape) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Delete.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Delete.cshtml new file mode 100644 index 0000000000..bc276716bc --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Delete.cshtml @@ -0,0 +1,15 @@ +@model SmartStore.Web.Framework.Modelling.DeleteConfirmationModel + +@using (Html.BeginForm(Model.ActionName, Model.ControllerName, new { id = Model.Id }, FormMethod.Post, new { style = "margin:0;" })) { +
            + @T("Admin.Common.DeleteConfirmation") +
            +
            + + +
            +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/FontAwesomeIcon.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/FontAwesomeIcon.cshtml new file mode 100644 index 0000000000..16acb3c558 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/FontAwesomeIcon.cshtml @@ -0,0 +1,58 @@ +@model IIcon + +@using Newtonsoft.Json; +@using SmartStore.Web.Framework; + +@{ + Html.AppendScriptParts(false, "~/Administration/Scripts/smartstore.iconchooser.js"); + + var iconExplorer = EngineContext.Current.Resolve(); +} + +
            +
            + +
            + +
            +
            + + + +
            +
            +
            \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml new file mode 100644 index 0000000000..5661064520 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Navbar.cshtml @@ -0,0 +1,159 @@ +@using SmartStore.Core.Domain.Stores; +@using SmartStore.Services.Customers; +@using SmartStore.Services.Stores; +@using SmartStore.Core; + +@{ + var currentCustomer = this.WorkContext.CurrentCustomer; + var currentLanguage = this.WorkContext.WorkingLanguage; + var userName = (string)ViewBag.UserName; + var stores = ((IList)ViewBag.Stores) ?? new List(); + string currentLanguageCode = currentLanguage.UniqueSeoCode.EmptyNull().ToLower(); + string communityUrl = (currentLanguageCode == "de" ? + "http://community.smartstore.com" : + "http://community.smartstore.com/index.php?/&k=880ea6a14ea49e853634fbdc5015a024&setlanguage=1&cal_id=&langid=1"); + string docsUrl = GenerateHelpUrl(""); + var checkUpdateResult = ViewBag.CheckUpdateResult as CheckUpdateResult; +} + + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/StoreSelector.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/StoreSelector.cshtml new file mode 100644 index 0000000000..df75e97aa4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/StoreSelector.cshtml @@ -0,0 +1,37 @@ +@model IStoreSelector + +
            +
            +
            + @Html.SmartLabelFor(model => model.LimitedToStores) +
            +
            + @Html.CheckBoxFor(model => model.LimitedToStores, new { data_toggler_for = "#pnl-available-stores" }) + @Html.ValidationMessageFor(model => model.LimitedToStores) +
            +
            +
            +
            + @Html.SmartLabelFor(model => model.AvailableStores) +
            +
            + @if (Model.AvailableStores != null && Model.AvailableStores.Any()) + { + foreach (var store in Model.AvailableStores) + { +
            + + +
            + } + } + else + { +
            + @T("Admin.Configuration.Stores.NoStoresDefined") +
            + } +
            +
            +
            \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/_Providers.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/_Providers.cshtml new file mode 100644 index 0000000000..ff547b1999 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/_Providers.cshtml @@ -0,0 +1,173 @@ +@model ProviderModelList +@using SmartStore.Core +@using SmartStore.Web.Framework.Plugins + +@helper ActiveLabel(ProviderModel item) +{ + var activatable = item as IActivatable; + if (activatable == null) + { + return; + } + if (activatable.IsActive) + { + @T("Common.Active") + } + else + { + @T("Common.Inactive") + } +} + +@functions { + private object RenderTemplate(object template) + { + var result = template as HelperResult; + if (result != null) + { + result.WriteTo(Html.ViewContext.Writer); + } + return null; + } + + private bool IsActive(ProviderModel item) + { + var activatable = item as IActivatable; + if (activatable == null) + { + return true; + } + + return activatable.IsActive; + } + + private string GetColClass() + { + return "col-" + (12 / (Model.ExtraColumns.Count + 1)); + } +} + +
            + @foreach (var item in Model.Data) + { +
            + @if (item.IsEditable) + { +
            + } +
            + +
            +
            + +
            +
            +
            +
            +
            @item.FriendlyName
            +
            + @ActiveLabel(item) +
            +
            + @if (item.Description.HasValue()) + { +
            + @item.Description +
            + } +
            + @{ + string tip = item.ProvidingPluginFriendlyName.HasValue() ? T("Admin.Providers.ProvidingPlugin") + ": " + item.ProvidingPluginFriendlyName : ""; + } + + @T("Common.SystemName"): + @item.SystemName + +
            +
            + @if (item.IsEditable || item.IsConfigurable) + { + + } +
            +
            + @foreach (var col in Model.ExtraColumns) + { +
            + @RenderTemplate(col(item)) +
            + } +
            +
            + } +
            + + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminLayout.cshtml deleted file mode 100644 index 2ac0f3243b..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminLayout.cshtml +++ /dev/null @@ -1,64 +0,0 @@ -@using Telerik.Web.Mvc; -@using SmartStore.Services.Customers; -@using SmartStore.Web.Framework.UI; -@using SmartStore.Core; -@using SmartStore.Services.Localization; -@using SmartStore.Core.Domain.Localization; -@using SmartStore; - -@* Please keep in mind that this layout view is also referenced and used by plugins! *@ - -@{ - Layout = "~/Administration/Views/Shared/_AdminRoot.cshtml"; -} - -
            - @if (ViewData["warning.panel.message"] != null) - { -
            - - @Html.Raw(ViewData["warning.panel.message"]) -
            - } - - @if (IsSectionDefined("header")) - { - @RenderSection("header") - } - else - { -
            - @if (IsSectionDefined("navbar")) - { - @RenderSection("navbar") - } - else - { - @Html.Widget("admin_navbar_before") - @Html.Action("Navbar", "Common", new { area = "Admin" }) - @Html.Widget("admin_navbar_after") - } - - -
            - } - - @Html.Widget("admin_content_before") -
            - @Html.Partial("_Notifications") -
            - @RenderBody() -
            -
            -
            -
            -
            -
            -
            - @Html.Widget("admin_content_after") - -
            - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminPopupLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminPopupLayout.cshtml deleted file mode 100644 index f744870ab8..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminPopupLayout.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@{ - Layout = "~/Administration/Views/Shared/_AdminLayout.cshtml"; - Html.AddBodyCssClass("popup"); -} -@section navbar -{ -} -@RenderBody() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminRoot.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminRoot.cshtml deleted file mode 100644 index f5a5e22b33..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_AdminRoot.cshtml +++ /dev/null @@ -1,133 +0,0 @@ -@using SmartStore; -@using SmartStore.Core; -@using SmartStore.Web.Framework.UI; - -@{ - var currentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture; - - // page title - string adminPageTitle = ""; - if (!string.IsNullOrWhiteSpace(ViewBag.Title)) - { - adminPageTitle = ViewBag.Title + " / "; - } - adminPageTitle += T("Admin.PageTitle").Text; - - // add css assets - var telerikCssRootPath = "~/Content/2012.2.607/"; - Html.AppendCssFileParts( - "~/Content/font-awesome.css", - telerikCssRootPath + "telerik.common.css", - "~/Content/jquery.pnotify.default.css", - "~/Content/jquery.pnotify.default.icons.css", - "~/Content/smartstore.entitypicker.css", - "~/Administration/Content/theme.less"); - - // add js assets (Head) - Html.AppendScriptParts(ResourceLocation.Head, - "~/Scripts/modernizr-2.7.2.js", - // Telerik doesn't like jQuery 1.9+, a shame! - "~/Administration/Scripts/jquery-1.8.3.js", - "~/Scripts/globalize/globalize.js", - "~/Scripts/smartstore.globalize.adapter.js"); - - Html.AddScriptParts(ResourceLocation.Head, true, - "~/Scripts/globalize/cultures/globalize.culture.{0}.js".FormatInvariant(currentUICulture.ToString())); - - // add js assets (Foot) - var bootstrapJsRoot = "~/Content/bootstrap/js/"; - Html.AppendScriptParts(ResourceLocation.Foot, - // jQuery UI Core - "~/Scripts/jquery-ui/effect.js", - "~/Scripts/jquery-ui/effect-transfer.js", - "~/Scripts/jquery-ui/effect-shake.js", - "~/Scripts/jquery-ui/position.js", - // jQuery Validation - "~/Scripts/jquery.unobtrusive-ajax.js", - "~/Scripts/jquery.validate.js", - "~/Scripts/jquery.validate.unobtrusive.js", - // SmartStore system - "~/Scripts/smartstore.system.js", - "~/Scripts/underscore.js", - "~/Scripts/underscore.string.js", - "~/Scripts/underscore.mixins.js", - "~/Scripts/smartstore.jquery.utils.js", - "~/Scripts/jquery.ba-outside-events.js", - "~/Scripts/jquery.preload.js", - "~/Scripts/jquery.transit.js", - "~/Scripts/jquery.menu-aim.js", - "~/Scripts/smartstore.doAjax.js", - "~/Scripts/smartstore.entityPicker.js", - "~/Scripts/jquery.addeasing.js", - "~/Scripts/smartstore.eventbroker.js", - "~/Scripts/smartstore.hacks.js", - "~/Scripts/smartstore.common.js", - // Bootstrap - bootstrapJsRoot + "bootstrap-transition.js", - bootstrapJsRoot + "bootstrap-alert.js", - bootstrapJsRoot + "bootstrap-button.js", - bootstrapJsRoot + "bootstrap-collapse.js", - bootstrapJsRoot + "bootstrap-dropdown.js", - bootstrapJsRoot + "bootstrap-modal.js", - bootstrapJsRoot + "bootstrap-tooltip.js", - bootstrapJsRoot + "bootstrap-popover.js", - bootstrapJsRoot + "bootstrap-tab.js", - bootstrapJsRoot + "bootstrap-typeahead.js", - // Bootstrap custom - bootstrapJsRoot + "custom/bootstrap-datetimepicker.js", - // Shared UI - "~/Scripts/smartstore.placeholder.js", - "~/Scripts/select2.js", - "~/Scripts/smartstore.selectwrapper.js", - "~/Scripts/smartstore.throbber.js", - "~/Scripts/smartstore.navbar.js", - "~/Scripts/smartstore.thumbzoomer.js", - "~/Scripts/smartstore.column-equalizer.js", - "~/Scripts/smartstore.shrinkmenu.js", - "~/Scripts/smartstore.scrollbutton.js", - "~/Scripts/smartstore.tabselector.js", - "~/Scripts/jquery.pnotify.js", - "~/Scripts/jquery.scrollTo.js", - "~/Scripts/jquery.serialScroll.js", - "~/Scripts/jquery.sortable.js", - // Admin - "~/Administration/Scripts/admin.common.js", - "~/Administration/Scripts/admin.globalinit.js"); -} - - - - @adminPageTitle - - - - - - @Html.Partial("ConditionalComments") - @Html.MetaAcceptLanguage() - - @Html.SmartCssFiles(this.Url, ResourceLocation.Head) - @Html.SmartScripts(this.Url, ResourceLocation.Head) - - - - - - @RenderBody() - @RenderSection("foot", required: false) - @Html.SmartCssFiles(this.Url, ResourceLocation.Foot) - @Html.SmartScripts(this.Url, ResourceLocation.Foot) - - - @(Html.Telerik().ScriptRegistrar() - .jQuery(false) - .jQueryValidation(false) - .Globalization(true) - ) - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_Providers.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/_Providers.cshtml deleted file mode 100644 index d0305ed5fc..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/_Providers.cshtml +++ /dev/null @@ -1,163 +0,0 @@ -@model ProviderModelList -@using SmartStore.Core -@using SmartStore.Web.Framework.Plugins - -@helper ActiveLabel(ProviderModel item) -{ - var activatable = item as IActivatable; - if (activatable == null) - { - return; - } - if (activatable.IsActive) - { - @T("Common.Active") - } - else - { - @T("Common.Inactive") - } -} - -@functions { - private object RenderTemplate(object template) - { - var result = template as HelperResult; - if (result != null) - { - result.WriteTo(Html.ViewContext.Writer); - } - return null; - } - - private bool IsActive(ProviderModel item) - { - var activatable = item as IActivatable; - if (activatable == null) - { - return true; - } - - return activatable.IsActive; - } - - private string GetColumnWidth() - { - return "{0}%".FormatInvariant(100 / (Model.ExtraColumns.Count + 1)); - } -} - -
            - @foreach (var item in Model.Data) - { -
            - @if (item.IsEditable) - { -
            - } -
            - -
            -
            - -
            -
            - @item.FriendlyName - @ActiveLabel(item) -
            -
            - @{ - string tip = item.ProvidingPluginFriendlyName.HasValue() ? T("Admin.Providers.ProvidingPlugin") + ": " + item.ProvidingPluginFriendlyName : ""; - } - @T("Common.SystemName"): - @item.SystemName - -
            - @if (item.Description.HasValue()) - { -
            - @item.Description -
            - } - @if (item.IsEditable || item.IsConfigurable) - { -
            -
            - @if (item.IsEditable) - { - if (item.IsPaymentMethod) - { - - - @T("Admin.Common.Edit") - - } - else - { - - - @T("Admin.Common.Edit") - - } - } - @if (item.IsConfigurable) - { - - - @T("Admin.Common.Configure") - - } -
            -
            - } -
            - @foreach (var col in Model.ExtraColumns) - { -
            - @RenderTemplate(col(item)) -
            - } -
            -
            - } -
            - - - - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/CreateMethod.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/CreateMethod.cshtml index b38be7f973..ac9c8c4557 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/CreateMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/CreateMethod.cshtml @@ -1,6 +1,5 @@ @model ShippingMethodModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Shipping.Methods.AddNew").Text; } @using (Html.BeginForm()) @@ -10,9 +9,15 @@ @T("Admin.Configuration.Shipping.Methods.AddNew") @Html.ActionLink("(" + T("Admin.Configuration.Shipping.Methods.BackToList") + ")", "Methods")
            - - + +
            - @Html.Partial("_CreateOrUpdateMethod", Model) + + Html.RenderPartial("_CreateOrUpdateMethod", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/EditMethod.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/EditMethod.cshtml index a07638227c..dcda1f31ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/EditMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/EditMethod.cshtml @@ -9,15 +9,24 @@ @T("Admin.Configuration.Shipping.Methods.EditMethodDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.Configuration.Shipping.Methods.BackToList") + ")", "Methods")
            - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } - - - + + + - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
            - @Html.Partial("_CreateOrUpdateMethod", Model) + + Html.RenderPartial("_CreateOrUpdateMethod", Model); } @Html.DeleteConfirmation("DeleteMethod", "method-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Methods.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Methods.cshtml index f74ed89677..6f588c9a34 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Methods.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Methods.cshtml @@ -1,6 +1,6 @@ -@model Telerik.Web.Mvc.GridModel -@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI; @using System.Linq; +@model Telerik.Web.Mvc.GridModel @{ ViewBag.Title = T("Admin.Configuration.Shipping.Methods").Text; } @@ -11,36 +11,41 @@ @T("Admin.Configuration.Shipping.Methods") -
            - @Html.TextBoxFor(x => x.Delimiter, new { style = "width: 30px;", maxlength = "2" }) + @Html.TextBoxFor(x => x.Delimiter, new { maxlength = "2" }) @Html.ValidationMessageFor(x => x.Delimiter)
            - @Html.TextBoxFor(x => x.Quote, new { style = "width: 30px;", maxlength = "2" }) + @Html.TextBoxFor(x => x.Quote, new { maxlength = "2" }) @Html.ValidationMessageFor(x => x.Quote)
            - @Html.TextBoxFor(x => x.Escape, new { style = "width: 30px;", maxlength = "2" }) + @Html.TextBoxFor(x => x.Escape, new { maxlength = "2" }) @Html.ValidationMessageFor(x => x.Escape)
            - - - -
            - @(Html.Telerik().Grid(Model.Data) - .Name("shippingmethod-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "EditMethod", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.Description); - columns.Bound(x => x.IgnoreCharges) - .Template(item => @Html.SymbolForBool(item.IgnoreCharges)) - .ClientTemplate(@Html.SymbolForBool("IgnoreCharges")) - .Width(250) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(160) - .Centered(); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("Methods", "Shipping")) - .EnableCustomBinding(true)) -
            +
            + @(Html.Telerik().Grid(Model.Data) + .Name("shippingmethod-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Template(x => Html.ActionLink(x.Name, "EditMethod", new { id = x.Id })) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.Description); + columns.Bound(x => x.LimitedToStores) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Width(180) + .Centered(); + columns.Bound(x => x.IgnoreCharges) + .Template(item => @Html.SymbolForBool(item.IgnoreCharges)) + .ClientTemplate(@Html.SymbolForBool("IgnoreCharges")) + .Width(250) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(180) + .Centered(); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("Methods", "Shipping")) + .EnableCustomBinding(true)) +
            + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Providers.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Providers.cshtml index 33faf37843..90afd92704 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Providers.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/Providers.cshtml @@ -9,21 +9,18 @@ @T("Admin.Configuration.Shipping.Providers")
            - @Html.Widget("admin_button_toolbar_before") - - @Html.Widget("admin_button_toolbar_after") + @{ + Html.RenderWidget("admin_button_toolbar_before"); + Html.Widget("admin_button_toolbar_after"); + }
            - - - - -
            - @Html.ProviderList(Model, - @) -
            + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml index 8d63b6e67b..90bc2d6d19 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml @@ -4,10 +4,11 @@ @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) -@Html.SmartStore().TabStrip().Name("shipping-method-edit").Items(x => +@Html.SmartStore().TabStrip().Name("shipping-method-edit").Style(TabsStyle.Material).Items(x => { x.Add().Text(T("Admin.Common.General").Text).Content(TabGeneral()).Selected(true); x.Add().Text(T("Admin.Common.Restrictions").Text).Content(TabRestrictions()); + x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "shipping-method-edit", this.Html, this.Model)); }) @@ -21,6 +22,9 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name) @@ -29,16 +33,11 @@ @Html.SmartLabelFor(model => model.Locales[item].Description) - - @Html.EditorFor(model => model.Locales[item].Description, Html.RichEditorFlavor()) + + @Html.EditorFor(model => model.Locales[item].Description, "Html") @Html.ValidationMessageFor(model => model.Locales[item].Description) - - - @Html.HiddenFor(model => model.Locales[item].LanguageId) - - , @ @@ -55,8 +54,8 @@ - @@ -105,6 +104,11 @@ } +@helper TabStores() +{ + @Html.Partial("StoreSelector", Model) +} + \ No newline at end of file + Html.RenderPartial("_CreateOrUpdate", Model); +} +@Html.DeleteConfirmation("specificationattribute-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml index 966cc46457..9ced42c054 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/List.cshtml @@ -10,49 +10,57 @@ @T("Admin.Catalog.Attributes.SpecificationAttributes")
            -  @T("Admin.Common.AddNew") - + + + @T("Admin.Common.AddNew") +
            - -
            @Html.SmartLabelFor(model => model.Description) - @Html.EditorFor(model => model.Description, Html.RichEditorFlavor()) + + @Html.EditorFor(model => model.Description, "Html") @Html.ValidationMessageFor(model => model.Description)
            - - - -
            - @(Html.Telerik().Grid() - .Name("specificationattributes-grid") - .ClientEvents(events => events.OnDataBound("onDataBound")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .Filterable(false) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.OptionCount) - .Filterable(false) - .Width(200) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Filterable(false) - .Width(200) - .Centered(); - }) - .Filterable() - .Pageable(settings => settings.PageSize((int)ViewData["GridPageSize"]).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "SpecificationAttribute")) - .PreserveGridState() - .EnableCustomBinding(true)) -
            +
            + @(Html.Telerik().Grid() + .Name("specificationattributes-grid") + .ClientEvents(events => events.OnDataBound("onDataBound")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .Filterable(false) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Name) + .ClientTemplate("<#= Name #>"); + columns.Bound(x => x.Alias); + columns.Bound(x => x.OptionCount) + .Filterable(false) + .Width(200) + .Centered(); + columns.Bound(x => x.AllowFiltering) + .Filterable(false) + .Width(200) + .Centered() + .ClientTemplate(@Html.SymbolForBool("AllowFiltering")); + columns.Bound(x => x.ShowOnProductPage) + .Filterable(false) + .Width(240) + .Centered() + .ClientTemplate(@Html.SymbolForBool("ShowOnProductPage")); + columns.Bound(x => x.DisplayOrder) + .Filterable(false) + .Width(200) + .Centered(); + }) + .Filterable() + .Pageable(settings => settings.PageSize((int)ViewData["GridPageSize"]).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "SpecificationAttribute")) + .PreserveGridState() + .EnableCustomBinding(true)) +
            } +} + @helper TabOptions() - { - if (Model.Id > 0) - { - - - - - - - -
            - @(Html.Telerik().Grid() - .Name("specificationattributeoptions-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("optionId"); - x.Add(y => y.SpecificationAttributeId).RouteKey("specificationAttributeId"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name); - //TODO display localized values here - columns.Bound(x => x.DisplayOrder) - .Width(100) - .Centered(); - columns.Bound(x => x.Id) - .Width(120) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=specificationattribute-form', 800, 250, true); return false;\" class='btn' />") - .Title(T("Admin.Common.Edit").Text); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) - .Width(100) - .Title(T("Admin.Common.Delete").Text); - }) - .DataBinding(dataBinding => - dataBinding.Ajax().Select("OptionList", "SpecificationAttribute", new { specificationAttributeId = Model.Id }) - .Delete("OptionDelete", "SpecificationAttribute")) - .EnableCustomBinding(true)) -
            - - - -
            - } - else - { - @T("Admin.Catalog.Attributes.SpecificationAttributes.Options.SaveBeforeEdit") - } - } \ No newline at end of file + return false; + }); + }); + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdateOption.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdateOption.cshtml index fe0ab19d36..961f495190 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdateOption.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdateOption.cshtml @@ -1,21 +1,10 @@ -@model SpecificationAttributeOptionModel - -@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI; +@model SpecificationAttributeOptionModel @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) @Html.HiddenFor(model => model.SpecificationAttributeId) - -@if (ViewBag.RefreshPage == true) -{ - -} - @(Html.LocalizedEditor("specificationattribute-localized", @ @@ -23,13 +12,20 @@ @Html.SmartLabelFor(model => model.Locales[item].Name) - +
            + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + @Html.EditorFor(model => Model.Locales[item].Name) @Html.ValidationMessageFor(model => model.Locales[item].Name)
            - @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.SmartLabelFor(model => model.Locales[item].Alias) + + @Html.EditorFor(model => Model.Locales[item].Alias) + @Html.ValidationMessageFor(model => model.Locales[item].Alias)
            @@ -43,10 +39,28 @@ @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) + + + + @Html.SmartLabelFor(model => model.Alias) + + + @Html.EditorFor(model => model.Alias) + @Html.ValidationMessageFor(model => model.Alias) + )) + + + + -@*codehint: sm-add*@ @if (ViewBag.MultipleEnabled == true) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/Create.cshtml index 16d56e2c7a..8cc60acc6a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/Create.cshtml @@ -1,6 +1,5 @@ @model StoreModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Stores.AddNew").Text; } @using (Html.BeginForm()) @@ -10,9 +9,15 @@ @T("Admin.Configuration.Stores.AddNew") @Html.ActionLink("(" + T("Admin.Configuration.Stores.BackToList") + ")", "List")
            - - + +
            - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/Edit.cshtml index 40fa879108..238f22628c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/Edit.cshtml @@ -1,8 +1,8 @@ @model StoreModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Stores.EditStoreDetails").Text; } + @using (Html.BeginForm()) {
            @@ -10,11 +10,20 @@ @T("Admin.Configuration.Stores.EditStoreDetails") - @Model.Name @Html.ActionLink("(" + T("Admin.Configuration.Stores.BackToList") + ")", "List")
            - - - + + +
            - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("store-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml index b3832779c4..1c320f37b8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml @@ -8,37 +8,38 @@ @T("Admin.Common.Stores") -
            + @Html.SmartLabelFor(model => model.NumberValue) + + @Html.EditorFor(model => model.NumberValue) + @Html.ValidationMessageFor(model => model.NumberValue) +
            @Html.SmartLabelFor(model => model.DisplayOrder) @@ -56,7 +70,6 @@ @Html.ValidationMessageFor(model => model.DisplayOrder)
            - - - -
            - @(Html.Telerik().Grid() - .Name("stores-grid") - .Columns(columns => - { - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.Url) - .ClientTemplate("\" target=\"_blank\"><#= Url #>"); - columns.Bound(x => x.Hosts) - .Encoded(false); - columns.Bound(x => x.PrimaryStoreCurrencyName); - columns.Bound(x => x.PrimaryExchangeRateCurrencyName); - columns.Bound(x => x.ContentDeliveryNetwork) - .ClientTemplate("\" target=\"_blank\"><#= ContentDeliveryNetwork #>"); - columns.Bound(x => x.SslEnabled) - .Template(item => @Html.SymbolForBool(item.SslEnabled)) - .ClientTemplate(@Html.SymbolForBool("SslEnabled")) - .Centered() - .Width(80); - columns.Bound(x => x.DisplayOrder) - .Centered(); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Store")) - .EnableCustomBinding(true)) -
            + +
            + @(Html.Telerik().Grid() + .Name("stores-grid") + .Columns(columns => + { + columns.Bound(x => x.Name) + .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.Url) + .ClientTemplate("\" target=\"_blank\"><#= Url #>"); + columns.Bound(x => x.Hosts) + .Encoded(false); + columns.Bound(x => x.PrimaryStoreCurrencyName); + columns.Bound(x => x.PrimaryExchangeRateCurrencyName); + columns.Bound(x => x.ContentDeliveryNetwork) + .ClientTemplate("\" target=\"_blank\"><#= ContentDeliveryNetwork #>"); + columns.Bound(x => x.SslEnabled) + .Template(item => @Html.SymbolForBool(item.SslEnabled)) + .ClientTemplate(@Html.SymbolForBool("SslEnabled")) + .Centered() + .Width(80); + columns.Bound(x => x.DisplayOrder) + .Centered(); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Store")) + .EnableCustomBinding(true)) +
            + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml index 4b3855f9ab..3e158a9359 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml @@ -3,24 +3,6 @@ @Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) - - @@ -90,19 +72,30 @@ @Html.SmartLabelFor(model => model.SslEnabled) - - - - + + + + + + + + + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml index 0ff05dd998..8fdd5069ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml @@ -2,7 +2,6 @@ @using Telerik.Web.Mvc.UI; @using System.Linq; @{ - //page title ViewBag.Title = T("Admin.Configuration.Tax.Categories").Text; }
            @@ -10,48 +9,44 @@ @T("Admin.Configuration.Tax.Categories")
            -
            @@ -54,7 +36,7 @@ @Html.SmartLabelFor(model => model.Hosts) - @Html.TextBoxFor(model => model.Hosts, new { @class = "input-large" }) + @Html.TextBoxFor(model => model.Hosts) @Html.ValidationMessageFor(model => model.Hosts)
            - @Html.EditorFor(model => model.SslEnabled) + @Html.CheckBoxFor(model => model.SslEnabled, new { data_toggler_for = "#pnlSecureUrl" }) @Html.ValidationMessageFor(model => model.SslEnabled)
            - @Html.SmartLabelFor(model => model.SecureUrl) - - @Html.EditorFor(model => model.SecureUrl) - @Html.ValidationMessageFor(model => model.SecureUrl) -
            + @Html.SmartLabelFor(model => model.SecureUrl) + + @Html.EditorFor(model => model.SecureUrl) + @Html.ValidationMessageFor(model => model.SecureUrl) +
            + @Html.SmartLabelFor(model => model.ForceSslForAllPages) + + @Html.EditorFor(model => model.ForceSslForAllPages) + @Html.ValidationMessageFor(model => model.ForceSslForAllPages) +
            @Html.SmartLabelFor(model => model.HtmlBodyId) @@ -128,7 +121,7 @@ @Html.SmartLabelFor(model => model.Id) - @Model.Id + @Html.TextBoxFor(model => model.Id, new { @readonly = "readonly", @class = "form-control-plaintext" })
            - - - -
            - @(Html.Telerik().Grid(Model.Data) - .Name("taxcategory-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name); - columns.Bound(x => x.DisplayOrder) - .Centered() - .Width(120); - columns.Command(commands => - { - commands.Edit().Localize(T); - commands.Delete().Localize(T); - }).Width(180); +
            + @(Html.Telerik().Grid(Model.Data) + .Name("taxcategory-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Width("70%"); + columns.Bound(x => x.DisplayOrder) + .Centered() + .Width("10%"); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }) + .HtmlAttributes(new { align = "right", @class= "omega" }) + .Width("20%"); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("Categories", "Tax") + .Update("CategoryUpdate", "Tax") + .Delete("CategoryDelete", "Tax") + .Insert("CategoryAdd", "Tax"); + }) + .ToolBar(x => x.Insert()) + .Editable(x => { x.Mode(GridEditMode.InLine); }) + .ClientEvents(x => x.OnError("grid_onError")) + .EnableCustomBinding(true)) +
            - }) - .ToolBar(x => x.Insert()) - .Editable(x => - { - x.Mode(GridEditMode.InLine); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("Categories", "Tax") - .Update("CategoryUpdate", "Tax") - .Delete("CategoryDelete", "Tax") - .Insert("CategoryAdd", "Tax"); - }) - .ClientEvents(x => x.OnError("grid_onError")) - .EnableCustomBinding(true)) - -
            + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Tax/Providers.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Tax/Providers.cshtml index f531a9112d..a4384f4ddb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Tax/Providers.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Tax/Providers.cshtml @@ -1,6 +1,5 @@ @model IEnumerable @{ - //page title ViewBag.Title = T("Admin.Configuration.Tax.Providers").Text; }
            @@ -8,21 +7,19 @@ @T("Admin.Configuration.Tax.Providers")
            - - - - -
            - @Html.ProviderList(Model, - @ - @if (item.IsPrimaryTaxProvider) - { - - @T("Admin.Configuration.Tax.Providers.Fields.IsPrimaryTaxProvider") - } - else - { - @T("Admin.Configuration.Tax.Providers.Fields.MarkAsPrimaryProvider") - } - ) -
            + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml index 627eb3ba88..2de28b5241 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml @@ -3,13 +3,13 @@ @{ ViewBag.Title = string.Format(T("Admin.Configuration.Themes.Title"), Model.ThemeName); - string parsingError = (string)TempData["LessParsingError"]; + string parsingError = (string)TempData["SassParsingError"]; } @using (Html.BeginForm()) { -
            - +
            +
            '; - while (dowCnt < this.weekStart + 7) { - html += ''; - } - html += ''; - this.picker.find('.datepicker-days thead').append(html); - }, - - fillMonths: function(){ - var html = '', - i = 0; - while (i < 12) { - html += ''+dates[this.language].monthsShort[i++]+''; - } - this.picker.find('.datepicker-months td').html(html); - }, - - fill: function() { - var d = new Date(this.viewDate), - year = d.getUTCFullYear(), - month = d.getUTCMonth(), - startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity, - startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity, - endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity, - endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity, - currentDate = this.date && this.date.valueOf(), - today = new Date(); - this.picker.find('.datepicker-days thead th:eq(1)') - .text(dates[this.language].months[month]+' '+year); - this.picker.find('tfoot th.today') - .text(dates[this.language].today) - .toggle(this.todayBtn !== false); - this.updateNavArrows(); - this.fillMonths(); - var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), - day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); - prevMonth.setUTCDate(day); - prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7); - var nextMonth = new Date(prevMonth); - nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); - nextMonth = nextMonth.valueOf(); - var html = []; - var clsName; - while(prevMonth.valueOf() < nextMonth) { - if (prevMonth.getUTCDay() == this.weekStart) { - html.push(''); - } - clsName = ''; - if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) { - clsName += ' old'; - } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) { - clsName += ' new'; - } - // Compare internal UTC date with local today, not UTC today - if (this.todayHighlight && - prevMonth.getUTCFullYear() == today.getFullYear() && - prevMonth.getUTCMonth() == today.getMonth() && - prevMonth.getUTCDate() == today.getDate()) { - clsName += ' today'; - } - if (currentDate && prevMonth.valueOf() == currentDate) { - clsName += ' active'; - } - if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate || - $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) { - clsName += ' disabled'; - } - html.push(''); - if (prevMonth.getUTCDay() == this.weekEnd) { - html.push(''); - } - prevMonth.setUTCDate(prevMonth.getUTCDate()+1); - } - this.picker.find('.datepicker-days tbody').empty().append(html.join('')); - var currentYear = this.date && this.date.getUTCFullYear(); - - var months = this.picker.find('.datepicker-months') - .find('th:eq(1)') - .text(year) - .end() - .find('span').removeClass('active'); - if (currentYear && currentYear == year) { - months.eq(this.date.getUTCMonth()).addClass('active'); - } - if (year < startYear || year > endYear) { - months.addClass('disabled'); - } - if (year == startYear) { - months.slice(0, startMonth).addClass('disabled'); - } - if (year == endYear) { - months.slice(endMonth+1).addClass('disabled'); - } - - html = ''; - year = parseInt(year/10, 10) * 10; - var yearCont = this.picker.find('.datepicker-years') - .find('th:eq(1)') - .text(year + '-' + (year + 9)) - .end() - .find('td'); - year -= 1; - for (var i = -1; i < 11; i++) { - html += ''+year+''; - year += 1; - } - yearCont.html(html); - }, - - updateNavArrows: function() { - var d = new Date(this.viewDate), - year = d.getUTCFullYear(), - month = d.getUTCMonth(); - switch (this.viewMode) { - case 0: - if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) { - this.picker.find('.prev').css({visibility: 'hidden'}); - } else { - this.picker.find('.prev').css({visibility: 'visible'}); - } - if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) { - this.picker.find('.next').css({visibility: 'hidden'}); - } else { - this.picker.find('.next').css({visibility: 'visible'}); - } - break; - case 1: - case 2: - if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) { - this.picker.find('.prev').css({visibility: 'hidden'}); - } else { - this.picker.find('.prev').css({visibility: 'visible'}); - } - if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) { - this.picker.find('.next').css({visibility: 'hidden'}); - } else { - this.picker.find('.next').css({visibility: 'visible'}); - } - break; - } - }, - - click: function(e) { - e.stopPropagation(); - e.preventDefault(); - var target = $(e.target).closest('span, td, th'); - if (target.length == 1) { - switch(target[0].nodeName.toLowerCase()) { - case 'th': - switch(target[0].className) { - case 'switch': - this.showMode(1); - break; - case 'prev': - case 'next': - var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); - switch(this.viewMode){ - case 0: - this.viewDate = this.moveMonth(this.viewDate, dir); - break; - case 1: - case 2: - this.viewDate = this.moveYear(this.viewDate, dir); - break; - } - this.fill(); - break; - case 'today': - var date = new Date(); - date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); - - this.showMode(-2); - var which = this.todayBtn == 'linked' ? null : 'view'; - this._setDate(date, which); - break; - } - break; - case 'span': - if (!target.is('.disabled')) { - this.viewDate.setUTCDate(1); - if (target.is('.month')) { - var month = target.parent().find('span').index(target); - this.viewDate.setUTCMonth(month); - this.element.trigger({ - type: 'changeMonth', - date: this.viewDate - }); - } else { - var year = parseInt(target.text(), 10)||0; - this.viewDate.setUTCFullYear(year); - this.element.trigger({ - type: 'changeYear', - date: this.viewDate - }); - } - this.showMode(-1); - this.fill(); - } - break; - case 'td': - if (target.is('.day') && !target.is('.disabled')){ - var day = parseInt(target.text(), 10)||1; - var year = this.viewDate.getUTCFullYear(), - month = this.viewDate.getUTCMonth(); - if (target.is('.old')) { - if (month === 0) { - month = 11; - year -= 1; - } else { - month -= 1; - } - } else if (target.is('.new')) { - if (month == 11) { - month = 0; - year += 1; - } else { - month += 1; - } - } - this._setDate(UTCDate(year, month, day,0,0,0,0)); - } - break; - } - } - }, - - _setDate: function(date, which){ - if (!which || which == 'date') - this.date = date; - if (!which || which == 'view') - this.viewDate = date; - this.fill(); - this.setValue(); - this.element.trigger({ - type: 'changeDate', - date: this.date - }); - var element; - if (this.isInput) { - element = this.element; - } else if (this.component){ - element = this.element.find('input'); - } - if (element) { - element.change(); - if (this.autoclose && (!which || which == 'date')) { - this.hide(); - } - } - }, - - moveMonth: function(date, dir){ - if (!dir) return date; - var new_date = new Date(date.valueOf()), - day = new_date.getUTCDate(), - month = new_date.getUTCMonth(), - mag = Math.abs(dir), - new_month, test; - dir = dir > 0 ? 1 : -1; - if (mag == 1){ - test = dir == -1 - // If going back one month, make sure month is not current month - // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) - ? function(){ return new_date.getUTCMonth() == month; } - // If going forward one month, make sure month is as expected - // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) - : function(){ return new_date.getUTCMonth() != new_month; }; - new_month = month + dir; - new_date.setUTCMonth(new_month); - // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 - if (new_month < 0 || new_month > 11) - new_month = (new_month + 12) % 12; - } else { - // For magnitudes >1, move one month at a time... - for (var i=0; i= this.startDate && date <= this.endDate; - }, - - keydown: function(e){ - if (this.picker.is(':not(:visible)')){ - if (e.keyCode == 27) // allow escape to hide and re-show picker - this.show(); - return; - } - var dateChanged = false, - dir, day, month, - newDate, newViewDate; - switch(e.keyCode){ - case 27: // escape - this.hide(); - e.preventDefault(); - break; - case 37: // left - case 39: // right - if (!this.keyboardNavigation) break; - dir = e.keyCode == 37 ? -1 : 1; - if (e.ctrlKey){ - newDate = this.moveYear(this.date, dir); - newViewDate = this.moveYear(this.viewDate, dir); - } else if (e.shiftKey){ - newDate = this.moveMonth(this.date, dir); - newViewDate = this.moveMonth(this.viewDate, dir); - } else { - newDate = new Date(this.date); - newDate.setUTCDate(this.date.getUTCDate() + dir); - newViewDate = new Date(this.viewDate); - newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); - } - if (this.dateWithinRange(newDate)){ - this.date = newDate; - this.viewDate = newViewDate; - this.setValue(); - this.update(); - e.preventDefault(); - dateChanged = true; - } - break; - case 38: // up - case 40: // down - if (!this.keyboardNavigation) break; - dir = e.keyCode == 38 ? -1 : 1; - if (e.ctrlKey){ - newDate = this.moveYear(this.date, dir); - newViewDate = this.moveYear(this.viewDate, dir); - } else if (e.shiftKey){ - newDate = this.moveMonth(this.date, dir); - newViewDate = this.moveMonth(this.viewDate, dir); - } else { - newDate = new Date(this.date); - newDate.setUTCDate(this.date.getUTCDate() + dir * 7); - newViewDate = new Date(this.viewDate); - newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); - } - if (this.dateWithinRange(newDate)){ - this.date = newDate; - this.viewDate = newViewDate; - this.setValue(); - this.update(); - e.preventDefault(); - dateChanged = true; - } - break; - case 13: // enter - this.hide(); - e.preventDefault(); - break; - case 9: // tab - this.hide(); - break; - } - if (dateChanged){ - this.element.trigger({ - type: 'changeDate', - date: this.date - }); - var element; - if (this.isInput) { - element = this.element; - } else if (this.component){ - element = this.element.find('input'); - } - if (element) { - element.change(); - } - } - }, - - showMode: function(dir) { - if (dir) { - this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); - } - /* - vitalets: fixing bug of very special conditions: - jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover. - Method show() does not set display css correctly and datepicker is not shown. - Changed to .css('display', 'block') solve the problem. - See https://github.com/vitalets/x-editable/issues/37 - - In jquery 1.7.2+ everything works fine. - */ - //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); - this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block'); - this.updateNavArrows(); - } - }; - - $.fn.datepicker = function ( option ) { - var args = Array.apply(null, arguments); - args.shift(); - return this.each(function () { - var $this = $(this), - data = $this.data('datepicker'), - options = typeof option == 'object' && option; - if (!data) { - $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options)))); - } - if (typeof option == 'string' && typeof data[option] == 'function') { - data[option].apply(data, args); - } - }); - }; - - $.fn.datepicker.defaults = { - }; - $.fn.datepicker.Constructor = Datepicker; - var dates = $.fn.datepicker.dates = { - en: { - days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], - daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], - months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - today: "Today" - } - }; - - var DPGlobal = { - modes: [ - { - clsName: 'days', - navFnc: 'Month', - navStep: 1 - }, - { - clsName: 'months', - navFnc: 'FullYear', - navStep: 1 - }, - { - clsName: 'years', - navFnc: 'FullYear', - navStep: 10 - }], - isLeapYear: function (year) { - return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); - }, - getDaysInMonth: function (year, month) { - return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; - }, - validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, - nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, - parseFormat: function(format){ - // IE treats \0 as a string end in inputs (truncating the value), - // so it's a bad format delimiter, anyway - var separators = format.replace(this.validParts, '\0').split('\0'), - parts = format.match(this.validParts); - if (!separators || !separators.length || !parts || parts.length === 0){ - throw new Error("Invalid date format."); - } - return {separators: separators, parts: parts}; - }, - parseDate: function(date, format, language) { - if (date instanceof Date) return date; - if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) { - var part_re = /([\-+]\d+)([dmwy])/, - parts = date.match(/([\-+]\d+)([dmwy])/g), - part, dir; - date = new Date(); - for (var i=0; i'+ - ''+ - ''+ - ''+ - '' + - ''+ - '', - contTemplate: '', - footTemplate: '' - }; - DPGlobal.template = '
            '+ - '
            '+ - '
            @Html.SmartLabelFor(model => model.StoreId) @@ -27,46 +27,46 @@ @ViewBag.Title @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List", new { storeId = Model.StoreId })
            - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } - - - - - @T("Common.Import") + + + @T("Common.Import") - - - @T("Common.Export") + + + @T("Common.Export") - - @T("Admin.Common.Reset") + + @T("Admin.Common.Reset") - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
            if (parsingError.HasValue()) { -
            -

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

            -
            -				@parsingError
            -			
            - + } - - @Html.Action("ConfigureTheme", "Theme", new { theme = Model.ThemeName, storeId = Model.StoreId, area = (string)null }) + +
            + @{ Html.RenderAction("ConfigureTheme", "Theme", new { theme = Model.ThemeName, storeId = Model.StoreId, area = (string)null }); } +
            } @*import variables window*@ @@ -75,23 +75,24 @@ .Title(T("Admin.Configuration.Themes.ImportVars")) .Content( @ - @using (Html.BeginForm("ImportVariables", "Theme", new { theme = Model.ThemeName, storeId = Model.StoreId }, FormMethod.Post, new { enctype = "multipart/form-data" })) - { -
            +
            +

            @T("Admin.Configuration.Themes.ImportVars.Note") -

            +

            @T("Admin.Configuration.Themes.ImportVars.XmlFile"):
            - } +
            ) .FooterContent( @ - - + + ) - .Modal(true) - .Visible(false) .Render(); } @@ -101,23 +102,25 @@ .Title(T("Admin.Configuration.Themes.ExportVars")) .Content( @ - @using (Html.BeginForm("ExportVariables", "Theme", new { theme = Model.ThemeName, storeId = Model.StoreId }, FormMethod.Post)) - { -
            +
            +

            @T("Admin.Configuration.Themes.ExportVars.Note") +

            +
            + + @Html.TextBox("exportprofilename", "", new { placeholder = T("Common.Optional") })
            -
            - @T("Admin.Configuration.Themes.ExportVars.ProfileName"): @Html.TextBox("exportprofilename", "", new { placeholder = T("Common.Optional") }) -
            - } +
            ) .FooterContent( @ - - + + ) - .Modal(true) - .Visible(false) .Render(); } @@ -134,7 +137,7 @@ $('#exportxmlsubmit').on('click', function (e) { e.preventDefault(); $('#exportvariables-window').find("form").submit(); - $('#exportvariables-window').data('modal').hide(); + $('#exportvariables-window').modal('hide'); }); }); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml index 15e975c4ce..147399bcc4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml @@ -5,38 +5,38 @@ @using SmartStore.Core.Themes; @{ - ViewBag.Title = T("Admin.Configuration.Themes").Text; + ViewBag.Title = T("Admin.Configuration.Themes").Text; } @using (Html.BeginForm()) {
            - + @T("Admin.Configuration.Themes")
            - @Html.Widget("admin_button_toolbar_before") + @{ Html.RenderWidget("admin_button_toolbar_before"); } - - - - @T("Admin.Configuration.Themes.Reload") + + + @T("Admin.Configuration.Themes.Reload") - + - @T("Admin.Packaging.UploadTheme") + @T("Admin.Packaging.UploadTheme") - @Html.Widget("admin_button_toolbar_after") + @{ Html.RenderWidget("admin_button_toolbar_after"); }
            - -
            - + +
            +
            @Html.SmartLabelFor(model => model.StoreId) @@ -47,44 +47,29 @@
            - - @Html.SmartStore().TabStrip().Name("themes-tab").Items(x => - { - x.Add().Text(T("Admin.Configuration.Themes")).Content(ThemesTab()).Selected(true); - x.Add().Text(T("Admin.Common.Settings")).Content(ThemeSettingsTab()); - }) + + @Html.SmartStore().TabStrip().Name("themes-tab").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Admin.Configuration.Themes")).Content(ThemesTab()).Selected(true); + x.Add().Text(T("Admin.Common.Settings")).Content(ThemeSettingsTab()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "themes-config", this.Html, this.Model)); + }) } @helper ThemesTab() { - if (Model.DesktopThemes.Count > 0) + if (Model.Themes.Count > 0) { - @ThemeList(Model.DesktopThemes, T("Admin.Configuration.Themes.AvailableDesktopThemes"), false) - } - - if (Model.MobileThemes.Count > 0) - { - @ThemeList(Model.MobileThemes, T("Admin.Configuration.Themes.AvailableMobileThemes"), true) + @ThemeList(Model.Themes, T("Admin.Configuration.Themes.AvailableDesktopThemes")) } } -@helper ThemeList(IList themes, string caption, bool mobile) +@helper ThemeList(IList themes, string caption) { -
            - @caption +
            + @caption - +
            } @helper ThemeSettingsTab() { - - - @Html.HiddenFor(x => x.DefaultDesktopTheme) - @Html.HiddenFor(x => x.DefaultMobileTheme) + @Html.HiddenFor(x => x.DefaultTheme) @Html.ValidationSummary(false) @@ -207,7 +174,7 @@ @Html.SmartLabelFor(model => model.AllowCustomerToSelectTheme) @@ -220,15 +187,6 @@ @Html.ValidationMessageFor(model => model.SaveThemeChoiceInCookie) - - - - + + + + + + + +
            - @Html.EditorFor(model => model.AllowCustomerToSelectTheme) + @Html.CheckBoxFor(model => model.AllowCustomerToSelectTheme, new { data_toggler_for = "#pnlSaveThemeChoiceInCookie" }) @Html.ValidationMessageFor(model => model.AllowCustomerToSelectTheme)
            - @Html.SmartLabelFor(model => model.MobileDevicesSupported) - - @Html.EditorFor(model => model.MobileDevicesSupported) - @Html.ValidationMessageFor(model => model.MobileDevicesSupported) -

            @@ -236,8 +194,8 @@
            -
            - +
            +

            @T("Admin.Configuration.Themes.Options.Title")

            @T("Admin.Configuration.Themes.Options.Info")

            @@ -252,7 +210,26 @@ @Html.ValidationMessageFor(model => model.BundleOptimizationEnabled)
            + @Html.SmartLabelFor(model => model.AssetCachingEnabled) + + @Html.DropDownListFor(model => model.AssetCachingEnabled, Model.AvailableAssetCachingValues) + @Html.ValidationMessageFor(model => model.AssetCachingEnabled) +
            +   + + + @T("Admin.Configuration.Themes.ClearAssetCache") + +
            } -@Html.Action("UploadPackage", "Packaging", new { isTheme = true }) \ No newline at end of file +@{ Html.RenderAction("UploadPackage", "Packaging", new { isTheme = true }); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Theme/PreviewTool.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Theme/PreviewTool.cshtml index 7fd45162f2..0dfe52dd0f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/PreviewTool.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/PreviewTool.cshtml @@ -85,50 +85,21 @@ } #previewmode .close { - font-size: 28px; + font-size: 20px; font-weight: bold; - margin-top: -2px; - } - - #previewmode select { - width: 100%; - border-radius: 0; } #previewmode .btn { - border-radius: 2px; text-transform: uppercase; } - #previewmode .btn-applytheme, - #previewmode .btn-exitpreview { + #previewmode select { border-radius: 0; - border: none !important; - padding-top: 12px; - padding-bottom: 12px; - margin: 0; - color: #fff; } #previewmode .btn-applytheme { - background: #fbb450; margin-bottom: 1px; } - #previewmode .btn-applytheme:hover { - background: #fa9f1e; - } - #previewmode .btn-applytheme:active { - background: #f99406; - } - #previewmode .btn-exitpreview { - background: #ee5f5b; - } - #previewmode .btn-exitpreview:hover { - background: #e9322d; - } - #previewmode .btn-exitpreview:active { - background: #e51d18; - }
            @@ -140,26 +111,26 @@

            @T("Admin.Configuration.Themes.PreviewMode")

            - @Html.DropDownList("theme", themes, new { @class = "noskin" }) + @Html.DropDownList("theme", themes, new { @class = "form-control noskin" }) - - @Html.DropDownList("storeId", stores, new { @class = "noskin" }) + + @Html.DropDownList("storeId", stores, new { @class = "form-control noskin" }) @Html.Hidden("returnUrl", Request.RawUrl)
            - - }
            - + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/Create.cshtml index b89fa5513e..44b81e80b7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/Create.cshtml @@ -1,6 +1,5 @@ @model TopicModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Topics.AddNew").Text; } @using (Html.BeginForm()) @@ -10,9 +9,15 @@ @T("Admin.ContentManagement.Topics.AddNew") @Html.ActionLink("(" + T("Admin.ContentManagement.Topics.BackToList") + ")", "List")
            - - + +
            - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/Edit.cshtml index 1dea736715..6f8b2cbded 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/Edit.cshtml @@ -1,6 +1,5 @@ @model TopicModel @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Topics.EditTopicDetails").Text; } @using (Html.BeginForm()) @@ -10,11 +9,20 @@ @T("Admin.ContentManagement.Topics.EditTopicDetails") - @Model.SystemName @Html.ActionLink("(" + T("Admin.ContentManagement.Topics.BackToList") + ")", "List")
            - - - + + +
            - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("topic-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 18fda90102..3da1caad9d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -1,96 +1,106 @@ @model TopicListModel + @using Telerik.Web.Mvc.UI + @{ - ViewBag.Title = T("Admin.ContentManagement.Topics").Text; + ViewBag.Title = T("Admin.ContentManagement.Topics").Text; + var gridPageSize = EngineContext.Current.Resolve().GridPageSize; } +
            - + @T("Admin.ContentManagement.Topics")
            -@if (Model.AvailableStores.Count > 1) -{ - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) -
            -   - - -
            -} +
            + @if (Model.AvailableStores.Count > 1) + { +
            + @Html.SmartLabelFor(model => model.SearchStoreId) + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
            + } +
            + @Html.SmartLabelFor(model => model.SystemName) + @Html.TextBoxFor(model => Model.SystemName, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.Title) + @Html.TextBoxFor(model => Model.Title, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.WidgetZone) + @Html.TextBoxFor(model => Model.WidgetZone, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.RenderAsWidget) + @Html.EditorFor(model => Model.RenderAsWidget, new { @class = "form-control" }) +
            -

            +
            + @if (Model.AvailableStores.Count > 1) + { + @Html.SmartLabel(string.Empty, string.Empty) + } + + +
            +
            - - - - -
            - @(Html.Telerik().Grid() - .Name("topics-grid") - .Columns(columns => - { - columns.Bound(x => x.SystemName) - .Width(280) - .Template(x => Html.ActionLink(x.SystemName, "Edit", new { id = x.Id })) - .ClientTemplate("<#= SystemName #>"); - columns.Bound(x => x.Title); - columns.Bound(x => x.IsPasswordProtected) - .Template(item => @Html.SymbolForBool(item.IsPasswordProtected)) - .ClientTemplate(@Html.SymbolForBool("IsPasswordProtected")) - .Centered(); - columns.Bound(x => x.IncludeInSitemap) - .Template(item => @Html.SymbolForBool(item.IncludeInSitemap)) - .ClientTemplate(@Html.SymbolForBool("IncludeInSitemap")) - .Centered(); - columns.Bound(x => x.RenderAsWidget) - .Template(item => @Html.SymbolForBool(item.RenderAsWidget)) - .ClientTemplate(@Html.SymbolForBool("RenderAsWidget")) - .Centered(); - columns.Bound(x => x.WidgetZone); - columns.Bound(x => x.LimitedToStores) - .Template(item => @Html.SymbolForBool(item.LimitedToStores)) - .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) - .Hidden(Model.AvailableStores.Count <= 1) - .Centered(); - columns.Bound(x => x.Priority) - .Centered(); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Topic")) - .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .EnableCustomBinding(true)) -
            +
            + @(Html.Telerik().Grid() + .Name("topics-grid") + .Columns(columns => + { + columns.Bound(x => x.SystemName) + .Width(280) + .Template(x => Html.ActionLink(x.SystemName, "Edit", new { id = x.Id })) + .ClientTemplate("<#= SystemName #>"); + columns.Bound(x => x.Title); + columns.Bound(x => x.IsPasswordProtected) + .Template(item => @Html.SymbolForBool(item.IsPasswordProtected)) + .ClientTemplate(@Html.SymbolForBool("IsPasswordProtected")) + .Centered(); + columns.Bound(x => x.IncludeInSitemap) + .Template(item => @Html.SymbolForBool(item.IncludeInSitemap)) + .ClientTemplate(@Html.SymbolForBool("IncludeInSitemap")) + .Centered(); + columns.Bound(x => x.RenderAsWidget) + .Template(item => @Html.SymbolForBool(item.RenderAsWidget)) + .ClientTemplate(@Html.SymbolForBool("RenderAsWidget")) + .Centered(); + columns.Bound(x => x.WidgetZone); + columns.Bound(x => x.LimitedToStores) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + columns.Bound(x => x.Priority) + .Centered(); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Topic")) + .ClientEvents(events => events.OnDataBinding("onDataBinding")) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true)) +
            @Html.HiddenFor(model => model.Id) -@Html.SmartStore().TabStrip().Name("topic-edit").Items(x => + +@Html.SmartStore().TabStrip().Name("topic-edit").Style(TabsStyle.Material).Items(x => { - x.Add().Text(T("Admin.ContentManagement.Topics.Info").Text).Content(TabInfo()).Selected(true); - x.Add().Text(T("Admin.Common.SEO").Text).Content(TabSeo()); + x.Add().Text(T("Admin.Common.General").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Common.SEO").Text).Content(TabSeo()); + x.Add().Text(T("Admin.Catalog.Categories.Acl").Text).Content(TabAcl()); x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); - - //generate an event - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "topic-edit", this.Html, this.Model)); + + if (Model.Id > 0) + { + x.Add().Text(T("Admin.Common.GenericAttributes").Text).Content(TabGenericAttributes()); + } + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "topic-edit", this.Html, this.Model)); }) + @helper TabInfo() - { - +{ +
            + + + + + + + + @@ -118,7 +84,9 @@ @Html.SmartLabelFor(model => model.Url) } @@ -132,235 +100,265 @@ @Html.SmartLabelFor(model => model.RenderAsWidget) - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            @Html.SmartLabelFor(model => model.SystemName) - @Html.EditorFor(model => model.SystemName) + @Html.TextBoxFor(model => model.SystemName, Model.IsSystemTopic ? new { @readonly = "readonly" } : null ) @Html.ValidationMessageFor(model => model.SystemName)
            + @Html.SmartLabelFor(model => model.IsPublished) + + @Html.CheckBoxFor(model => model.IsPublished, Model.IsSystemTopic ? new { @readonly = "readonly" } : null ) + @Html.ValidationMessageFor(model => model.IsPublished) +
            + @Html.SmartLabelFor(model => model.Priority) + + @Html.EditorFor(x => x.Priority) + @Html.ValidationMessageFor(model => model.Priority) +
            @Html.SmartLabelFor(model => model.IsPasswordProtected) - @Html.EditorFor(model => model.IsPasswordProtected) + @Html.CheckBoxFor(model => model.IsPasswordProtected, new { data_toggler_for = "#pnlPasswordEnabled" }) @Html.ValidationMessageFor(model => model.IsPasswordProtected)
            - @Model.Url +
            - @Html.EditorFor(x => x.RenderAsWidget) + @Html.CheckBoxFor(model => model.RenderAsWidget, new { data_toggler_for = "#pnlRenderAsWidget, #pnlTopicAttrValues" }) @Html.ValidationMessageFor(model => model.RenderAsWidget)
            - @Html.SmartLabelFor(model => model.WidgetZone) - - @Html.EditorFor(model => model.WidgetZone, "WidgetZone") - @Html.ValidationMessageFor(model => model.WidgetZone) -
            - @Html.SmartLabelFor(model => model.Priority) - - @Html.EditorFor(x => x.Priority) - @Html.ValidationMessageFor(model => model.Priority) -
            - @Html.SmartLabelFor(model => model.WidgetWrapContent) - - @Html.EditorFor(x => x.WidgetWrapContent) - @Html.ValidationMessageFor(model => model.WidgetWrapContent) -
            - @Html.SmartLabelFor(model => model.WidgetShowTitle) - - @Html.EditorFor(x => x.WidgetShowTitle) - @Html.ValidationMessageFor(model => model.WidgetShowTitle) -
            - @Html.SmartLabelFor(model => model.TitleTag) - - @Html.DropDownListFor(model => model.TitleTag, Model.AvailableTitleTags, new { @class = "autowidth", data_select_min_results_for_search = 100 }) - @Html.ValidationMessageFor(model => model.TitleTag) -
            - @Html.SmartLabelFor(model => model.WidgetBordered) - - @Html.EditorFor(x => x.WidgetBordered) - @Html.ValidationMessageFor(model => model.WidgetBordered) -
            + @Html.SmartLabelFor(model => model.HtmlId) + + @Html.EditorFor(x => x.HtmlId) + @Html.ValidationMessageFor(model => model.HtmlId) +
            + @Html.SmartLabelFor(model => model.BodyCssClass) + + @Html.EditorFor(x => x.BodyCssClass) + @Html.ValidationMessageFor(model => model.BodyCssClass) +
            + @Html.SmartLabelFor(model => model.WidgetZone) + + @Html.EditorFor(model => model.WidgetZone, "WidgetZone") + @Html.ValidationMessageFor(model => model.WidgetZone) +
            + @Html.SmartLabelFor(model => model.WidgetWrapContent) + + @Html.CheckBoxFor(model => model.WidgetWrapContent, new { data_toggler_for = "#pnlWidgetWrapContent" }) + @Html.ValidationMessageFor(model => model.WidgetWrapContent) +
            + @Html.SmartLabelFor(model => model.WidgetShowTitle) + + @Html.CheckBoxFor(model => model.WidgetShowTitle, new { data_toggler_for = "#pnlTitleTag" }) + @Html.ValidationMessageFor(model => model.WidgetShowTitle) +
            + @Html.SmartLabelFor(model => model.TitleTag) + + @Html.DropDownListFor(model => model.TitleTag, Model.AvailableTitleTags, new { @class = "autowidth", data_select_min_results_for_search = 100 }) + @Html.ValidationMessageFor(model => model.TitleTag) +
            + @Html.SmartLabelFor(model => model.WidgetBordered) + + @Html.EditorFor(x => x.WidgetBordered) + @Html.ValidationMessageFor(model => model.WidgetBordered) +
            -
            - @(Html.LocalizedEditor("topic-info-localized", - @ - - - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.Locales[item].Title) - - @Html.EditorFor(model => Model.Locales[item].Title) - @Html.ValidationMessageFor(model => model.Locales[item].Title) -
            - @Html.SmartLabelFor(model => model.Locales[item].Body) - - @Html.EditorFor(model => model.Locales[item].Body, Html.RichEditorFlavor(), new { ForceRootBlock = false }) - @Html.ValidationMessageFor(model => model.Locales[item].Body) -
            - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
            - , - @ - - - - - - - - -
            - @Html.SmartLabelFor(model => model.Title) - - @Html.EditorFor(model => model.Title) - @Html.ValidationMessageFor(model => model.Title) -
            - @Html.SmartLabelFor(model => model.Body) - - @Html.EditorFor(x => x.Body, Html.RichEditorFlavor(), new { ForceRootBlock = false }) - @Html.ValidationMessageFor(model => model.Body) -
            - )) + @ + + + + + + + + + + + + + + + + +
            + @Html.SmartLabelFor(model => model.Locales[item].ShortTitle) + + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId, new { id = Html.FieldIdFor(model => model.Locales[item].LanguageId) + "_1" }) + + @Html.EditorFor(model => model.Locales[item].ShortTitle) + @Html.ValidationMessageFor(model => model.Locales[item].ShortTitle) +
            + @Html.SmartLabelFor(model => model.Locales[item].Title) + + @Html.EditorFor(model => Model.Locales[item].Title) + @Html.ValidationMessageFor(model => model.Locales[item].Title) +
            + @Html.SmartLabelFor(model => model.Locales[item].Intro) + + @Html.TextAreaFor(model => Model.Locales[item].Intro, new { rows = 1 }) + @Html.ValidationMessageFor(model => model.Locales[item].Intro) +
            + @Html.SmartLabelFor(model => model.Locales[item].Body) + + @Html.EditorFor(model => model.Locales[item].Body, "Html", new { ForceRootBlock = false }) + @Html.ValidationMessageFor(model => model.Locales[item].Body) +
            + , + @ + + + + + + + + + + + + + + + + +
            + @Html.SmartLabelFor(model => model.ShortTitle) + + @Html.EditorFor(model => model.ShortTitle) + @Html.ValidationMessageFor(model => model.ShortTitle) +
            + @Html.SmartLabelFor(model => model.Title) + + @Html.EditorFor(model => model.Title) + @Html.ValidationMessageFor(model => model.Title) +
            + @Html.SmartLabelFor(model => model.Intro) + + @Html.TextAreaFor(model => model.Intro, new { rows = 1 }) + @Html.ValidationMessageFor(model => model.Intro) +
            + @Html.SmartLabelFor(model => model.Body) + + @Html.EditorFor(x => x.Body, "Html", new { ForceRootBlock = false }) + @Html.ValidationMessageFor(model => model.Body) +
            + )) } + @helper TabSeo() - { +{ @(Html.LocalizedEditor("topic-seo-localized", - @ - - - - - - - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.Locales[item].MetaKeywords) - - @Html.EditorFor(model => model.Locales[item].MetaKeywords) - @Html.ValidationMessageFor(model => model.Locales[item].MetaKeywords) -
            - @Html.SmartLabelFor(model => model.Locales[item].MetaDescription) - - @Html.TextAreaFor(model => model.Locales[item].MetaDescription) - @Html.ValidationMessageFor(model => model.Locales[item].MetaDescription) -
            - @Html.SmartLabelFor(model => model.Locales[item].MetaTitle) - - @Html.TextAreaFor(model => model.Locales[item].MetaTitle) - @Html.ValidationMessageFor(model => model.Locales[item].MetaTitle) -
            - @Html.HiddenFor(model => model.Locales[item].LanguageId) -
            - , - @ - - - - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.MetaKeywords) - - @Html.EditorFor(x => x.MetaKeywords) - @Html.ValidationMessageFor(model => model.MetaKeywords) -
            - @Html.SmartLabelFor(model => model.MetaDescription) - - @Html.TextAreaFor(x => x.MetaDescription) - @Html.ValidationMessageFor(model => model.MetaDescription) -
            - @Html.SmartLabelFor(model => model.MetaTitle) - - @Html.TextAreaFor(x => x.MetaTitle) - @Html.ValidationMessageFor(model => model.MetaTitle) -
            )) + @ + + + + + + + + + + + + + + + + +
            + @Html.SmartLabelFor(model => model.Locales[item].MetaKeywords) + + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId, new { id = Html.FieldIdFor(model => model.Locales[item].LanguageId) + "_2" }) + + @Html.EditorFor(model => model.Locales[item].MetaKeywords) + @Html.ValidationMessageFor(model => model.Locales[item].MetaKeywords) +
            + @Html.SmartLabelFor(model => model.Locales[item].MetaDescription) + + @Html.TextAreaFor(model => model.Locales[item].MetaDescription) + @Html.ValidationMessageFor(model => model.Locales[item].MetaDescription) +
            + @Html.SmartLabelFor(model => model.Locales[item].MetaTitle) + + @Html.TextAreaFor(model => model.Locales[item].MetaTitle) + @Html.ValidationMessageFor(model => model.Locales[item].MetaTitle) +
            + @Html.SmartLabelFor(model => model.Locales[item].SeName) + + @Html.EditorFor(model => model.Locales[item].SeName) + @Html.ValidationMessageFor(model => model.Locales[item].SeName) +
            + , + @ + + + + + + + + + + + + + + + + +
            + @Html.SmartLabelFor(model => model.MetaKeywords) + + @Html.EditorFor(x => x.MetaKeywords) + @Html.ValidationMessageFor(model => model.MetaKeywords) +
            + @Html.SmartLabelFor(model => model.MetaDescription) + + @Html.TextAreaFor(x => x.MetaDescription) + @Html.ValidationMessageFor(model => model.MetaDescription) +
            + @Html.SmartLabelFor(model => model.MetaTitle) + + @Html.TextAreaFor(x => x.MetaTitle) + @Html.ValidationMessageFor(model => model.MetaTitle) +
            + @Html.SmartLabelFor(model => model.SeName) + + @Html.EditorFor(model => model.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Topic", entityId = Model.Id }) + } + @Html.ValidationMessageFor(model => model.SeName) +
            )) } + @helper TabStores() { - - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.LimitedToStores) - - @Html.EditorFor(model => model.LimitedToStores) - @Html.ValidationMessageFor(model => model.LimitedToStores) -
            - @Html.SmartLabelFor(model => model.AvailableStores) - - @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) - { - foreach (var store in Model.AvailableStores) - { - - } - } - else - { -
            @T("Admin.Configuration.Stores.NoStoresDefined")
            - }
            +@helper TabGenericAttributes() +{ + @Html.Action("GenericAttributes", "Common", new { entityName = "Topic", entityId = Model.Id }) } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml index 4914d5c1ea..32dc95eb85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml @@ -11,14 +11,23 @@ @title - @Model.Slug @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List")
            - -  @T("Admin.Common.Entity") + +  @T("Admin.Common.Entity") - - - + + +
            - @Html.Partial("_CreateOrUpdate", Model) + + Html.RenderPartial("_CreateOrUpdate", Model); } @Html.DeleteConfirmation("urlrecord-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml index fa2a560a7d..5cada1e264 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml @@ -14,109 +14,81 @@
            - - - - - - - - - - - - - - - - - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.SeName) - - @Html.EditorFor(model => Model.SeName) -
            - @Html.SmartLabelFor(model => model.EntityName) - - @Html.EditorFor(model => Model.EntityName) -
            - @Html.SmartLabelFor(model => model.EntityId) - - @Html.EditorFor(model => Model.EntityId) -
            - @Html.SmartLabelFor(model => model.IsActive) - - @Html.EditorFor(model => Model.IsActive) -
            - @Html.SmartLabelFor(x => x.LanguageId) - - @Html.DropDownListFor(x => x.LanguageId, Model.AvailableLanguages, T("Common.Unspecified")) -
            -   - - -
            - -

            - - - - - -
            - @(Html.Telerik().Grid() - .Name("urlrecord-grid") - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id) - .Width(100) - .Centered(); - columns.Bound(x => x.Slug) - .ClientTemplate("<#= Slug #>"); - columns.Bound(x => x.SlugsPerEntity) - .Centered() - .Width(180) - .ClientTemplate("<#= SlugsPerEntity #>"); - columns.Bound(x => x.EntityName) - .Width(200); - columns.Bound(x => x.EntityId) - .Width(140) - .Centered() - .ClientTemplate("<#= EntityId #>"); - columns.Bound(x => x.IsActive) - .Template(item => @Html.SymbolForBool(item.IsActive)) - .ClientTemplate(@Html.SymbolForBool("IsActive")) - .Width(100) - .Centered(); - columns.Bound(x => x.Language) - .Width(200); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "UrlRecord")) - .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) - .EnableCustomBinding(true)) -
            +
            +
            + @Html.SmartLabelFor(model => model.SeName) + @Html.TextBoxFor(model => Model.SeName, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.EntityName) + @Html.TextBoxFor(model => model.EntityName, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.EntityId) + @Html.TextBoxFor(model => model.EntityId, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.IsActive) + @Html.EditorFor(model => model.IsActive, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.LanguageId) + @Html.DropDownListFor(x => x.LanguageId, Model.AvailableLanguages, T("Common.Unspecified")) +
            + +
            + @Html.SmartLabel(string.Empty, string.Empty) + +
            +
            + +
            + @(Html.Telerik().Grid() + .Name("urlrecord-grid") + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(100) + .Centered(); + columns.Bound(x => x.Slug) + .ClientTemplate("<#= Slug #>"); + columns.Bound(x => x.SlugsPerEntity) + .Centered() + .Width(180) + .ClientTemplate("<#= SlugsPerEntity #>"); + columns.Bound(x => x.EntityName) + .Width(200); + columns.Bound(x => x.EntityId) + .Width(140) + .Centered() + .ClientTemplate("<#= EntityId #>"); + columns.Bound(x => x.IsActive) + .Template(item => @Html.SymbolForBool(item.IsActive)) + .ClientTemplate(@Html.SymbolForBool("IsActive")) + .Width(100) + .Centered(); + columns.Bound(x => x.Language) + .Width(200); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "UrlRecord")) + .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) + .EnableCustomBinding(true)) +
            } - -

            - diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/tmpFrameset.html b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/tmpFrameset.html deleted file mode 100644 index c2d82aa402..0000000000 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/tmpFrameset.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.css b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.css deleted file mode 100644 index 496d731250..0000000000 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.css +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.html or http://ckeditor.com/license -*/ - -html, body -{ - background-color: transparent; - margin: 0px; - padding: 0px; -} - -body -{ - padding: 10px; -} - -body, td, input, select, textarea -{ - font-size: 11px; - font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana; -} - -.midtext -{ - padding:0px; - margin:10px; -} - -.midtext p -{ - padding:0px; - margin:10px; -} - -.Button -{ - border: #737357 1px solid; - color: #3b3b1f; - background-color: #c7c78f; -} - -.PopupTabArea -{ - color: #737357; - background-color: #e3e3c7; -} - -.PopupTitleBorder -{ - border-bottom: #d5d59d 1px solid; -} -.PopupTabEmptyArea -{ - padding-left: 10px; - border-bottom: #d5d59d 1px solid; -} - -.PopupTab, .PopupTabSelected -{ - border-right: #d5d59d 1px solid; - border-top: #d5d59d 1px solid; - border-left: #d5d59d 1px solid; - padding: 3px 5px 3px 5px; - color: #737357; -} - -.PopupTab -{ - margin-top: 1px; - border-bottom: #d5d59d 1px solid; - cursor: pointer; -} - -.PopupTabSelected -{ - font-weight: bold; - cursor: default; - padding-top: 4px; - border-bottom: #f1f1e3 1px solid; - background-color: #f1f1e3; -} diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.js b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.js deleted file mode 100644 index b53a48cf47..0000000000 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - For licensing, see LICENSE.html or http://ckeditor.com/license -*/ -(function(){function q(a){return a&&a.domId&&a.getInputElement().$?a.getInputElement():a&&a.$?a:!1}function z(a){if(!a)throw"Languages-by-groups list are required for construct selectbox";var c=[],d="",f;for(f in a)for(var g in a[f]){var h=a[f][g];"en_US"==h?d=h:c.push(h)}c.sort();d&&c.unshift(d);return{getCurrentLangGroup:function(c){a:{for(var d in a)for(var f in a[d])if(f.toUpperCase()===c.toUpperCase()){c=d;break a}c=""}return c},setLangList:function(){var c={},d;for(d in a)for(var f in a[d])c[a[d][f]]= -f;return c}()}}var e=function(){var a=function(a,b,f){var f=f||{},g=f.expires;if("number"==typeof g&&g){var h=new Date;h.setTime(h.getTime()+1E3*g);g=f.expires=h}g&&g.toUTCString&&(f.expires=g.toUTCString());var b=encodeURIComponent(b),a=a+"="+b,e;for(e in f)b=f[e],a+="; "+e,!0!==b&&(a+="="+b);document.cookie=a};return{postMessage:{init:function(a){window.addEventListener?window.addEventListener("message",a,!1):window.attachEvent("onmessage",a)},send:function(a){var b=Object.prototype.toString,f= -a.fn||null,g=a.id||"",e=a.target||window,i=a.message||{id:g};a.message&&"[object Object]"==b.call(a.message)&&(a.message.id||(a.message.id=g),i=a.message);a=window.JSON.stringify(i,f);e.postMessage(a,"*")},unbindHandler:function(a){window.removeEventListener?window.removeEventListener("message",a,!1):window.detachEvent("onmessage",a)}},hash:{create:function(){},parse:function(){}},cookie:{set:a,get:function(a){return(a=document.cookie.match(RegExp("(?:^|; )"+a.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, -"\\$1")+"=([^;]*)")))?decodeURIComponent(a[1]):void 0},remove:function(c){a(c,"",{expires:-1})}},misc:{findFocusable:function(a){var b=null;a&&(b=a.find("a[href], area[href], input, select, textarea, button, *[tabindex], *[contenteditable]"));return b},isVisible:function(a){return!(0===a.offsetWidth||0==a.offsetHeight||"none"===(document.defaultView&&document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(a,null).display:a.currentStyle?a.currentStyle.display:a.style.display))}, -hasClass:function(a,b){return!(!a.className||!a.className.match(RegExp("(\\s|^)"+b+"(\\s|$)")))}}}}(),a=a||{};a.TextAreaNumber=null;a.load=!0;a.cmd={SpellTab:"spell",Thesaurus:"thes",GrammTab:"grammar"};a.dialog=null;a.optionNode=null;a.selectNode=null;a.grammerSuggest=null;a.textNode={};a.iframeMain=null;a.dataTemp="";a.div_overlay=null;a.textNodeInfo={};a.selectNode={};a.selectNodeResponce={};a.langList=null;a.langSelectbox=null;a.banner="";a.show_grammar=null;a.div_overlay_no_check=null;a.targetFromFrame= -{};a.onLoadOverlay=null;a.LocalizationComing={};a.OverlayPlace=null;a.LocalizationButton={ChangeTo:{instance:null,text:"Change to"},ChangeAll:{instance:null,text:"Change All"},IgnoreWord:{instance:null,text:"Ignore word"},IgnoreAllWords:{instance:null,text:"Ignore all words"},Options:{instance:null,text:"Options",optionsDialog:{instance:null}},AddWord:{instance:null,text:"Add word"},FinishChecking:{instance:null,text:"Finish Checking"}};a.LocalizationLabel={ChangeTo:{instance:null,text:"Change to"}, -Suggestions:{instance:null,text:"Suggestions"}};var A=function(b){var c,d;for(d in b)c=b[d].instance.getElement().getFirst()||b[d].instance.getElement(),c.setText(a.LocalizationComing[d])},B=function(b){for(var c in b){if(!b[c].instance.setLabel)break;b[c].instance.setLabel(a.LocalizationComing[c])}},j,r;a.framesetHtml=function(b){return"'}; -a.setIframe=function(b,c){var d;d=a.framesetHtml(c);var f=a.iframeNumber+"_"+c;b.getElement().setHtml(d);d=document.getElementById(f);d=d.contentWindow?d.contentWindow:d.contentDocument.document?d.contentDocument.document:d.contentDocument;d.document.open();d.document.write('iframe
            + + @property value + @type mixed + @default element's text + **/ + value: null, + /** + Callback to perform custom displaying of value in element's text. + If `null`, default input's display used. + If `false`, no displaying methods will be called, element's text will never change. + Runs under element's scope. + _**Parameters:**_ + + * `value` current value to be displayed + * `response` server response (if display called after ajax submit), since 1.4.0 + + For _inputs with source_ (select, checklist) parameters are different: + + * `value` current value to be displayed + * `sourceData` array of items for current input (e.g. dropdown items) + * `response` server response (if display called after ajax submit), since 1.4.0 + + To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`. + + @property display + @type function|boolean + @default null + @since 1.2.0 + @example + display: function(value, sourceData) { + //display checklist as comma-separated values + var html = [], + checked = $.fn.editableutils.itemsByValue(value, sourceData); + + if(checked.length) { + $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); }); + $(this).html(html.join(', ')); + } else { + $(this).empty(); + } + } + **/ + display: null, + /** + Css class applied when editable text is empty. + + @property emptyclass + @type string + @since 1.4.1 + @default editable-empty + **/ + emptyclass: 'editable-empty', + /** + Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`). + You may set it to `null` if you work with editables locally and submit them together. + + @property unsavedclass + @type string + @since 1.4.1 + @default editable-unsaved + **/ + unsavedclass: 'editable-unsaved', + /** + If selector is provided, editable will be delegated to the specified targets. + Usefull for dynamically generated DOM elements. + **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, + as they actually become editable only after first click. + You should manually set class `editable-click` to these elements. + Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element: + + @property selector + @type string + @since 1.4.1 + @default null + @example +
            + + Empty + + Operator +
            + + + **/ + selector: null, + /** + Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers. + + @property highlight + @type string|boolean + @since 1.4.5 + @default #FFFF80 + **/ + highlight: '#FFFF80' + }; + +}(window.jQuery)); + +/** +AbstractInput - base class for all editable inputs. +It defines interface to be implemented by any input type. +To create your own input you can inherit from this class. + +@class abstractinput +**/ +(function ($) { + "use strict"; + + //types + $.fn.editabletypes = {}; + + var AbstractInput = function () { }; + + AbstractInput.prototype = { + /** + Initializes input + + @method init() + **/ + init: function (type, options, defaults) { + this.type = type; + this.options = $.extend({}, defaults, options); + }, + + /* + this method called before render to init $tpl that is inserted in DOM + */ + prerender: function () { + this.$tpl = $(this.options.tpl); //whole tpl as jquery object + this.$input = this.$tpl; //control itself, can be changed in render method + this.$clear = null; //clear button + this.error = null; //error message, if input cannot be rendered + }, + + /** + Renders input from tpl. Can return jQuery deferred object. + Can be overwritten in child objects + + @method render() + **/ + render: function () { + + }, + + /** + Sets element's html by value. + + @method value2html(value, element) + @param {mixed} value + @param {DOMElement} element + **/ + value2html: function (value, element) { + $(element)[this.options.escape ? 'text' : 'html']($.trim(value)); + }, + + /** + Converts element's html to value + + @method html2value(html) + @param {string} html + @returns {mixed} + **/ + html2value: function (html) { + return $('
            ').html(html).text(); + }, + + /** + Converts value to string (for internal compare). For submitting to server used value2submit(). + + @method value2str(value) + @param {mixed} value + @returns {string} + **/ + value2str: function (value) { + return String(value); + }, + + /** + Converts string received from server into value. Usually from `data-value` attribute. + + @method str2value(str) + @param {string} str + @returns {mixed} + **/ + str2value: function (str) { + return str; + }, + + /** + Converts value for submitting to server. Result can be string or object. + + @method value2submit(value) + @param {mixed} value + @returns {mixed} + **/ + value2submit: function (value) { + return value; + }, + + /** + Sets value of input. + + @method value2input(value) + @param {mixed} value + **/ + value2input: function (value) { + this.$input.val(value); + }, + + /** + Returns value of input. Value can be object (e.g. datepicker) + + @method input2value() + **/ + input2value: function () { + return this.$input.val(); + }, + + /** + Activates input. For text it sets focus. + + @method activate() + **/ + activate: function () { + if (this.$input.is(':visible')) { + this.$input.focus(); + } + }, + + /** + Creates input. + + @method clear() + **/ + clear: function () { + this.$input.val(null); + }, + + /** + method to escape html. + **/ + escape: function (str) { + return $('
            ').text(str).html(); + }, + + /** + attach handler to automatically submit form when value changed (useful when buttons not shown) + **/ + autosubmit: function () { + + }, + + /** + Additional actions when destroying element + **/ + destroy: function () { + }, + + // -------- helper functions -------- + setClass: function () { + if (this.options.inputclass) { + this.$input.addClass(this.options.inputclass); + } + }, + + setAttr: function (attr) { + if (this.options[attr] !== undefined && this.options[attr] !== null) { + this.$input.attr(attr, this.options[attr]); + } + }, + + option: function (key, value) { + this.options[key] = value; + } + + }; + + AbstractInput.defaults = { + /** + HTML template of input. Normally you should not change it. + + @property tpl + @type string + @default '' + **/ + tpl: '', + /** + CSS class automatically applied to input + + @property inputclass + @type string + @default null + **/ + inputclass: null, + + /** + If `true` - html will be escaped in content of element via $.text() method. + If `false` - html will not be escaped, $.html() used. + When you use own `display` function, this option obviosly has no effect. + + @property escape + @type boolean + @since 1.5.0 + @default true + **/ + escape: true, + + //scope for external methods (e.g. source defined as function) + //for internal use only + scope: null, + + //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults) + showbuttons: true + }; + + $.extend($.fn.editabletypes, { abstractinput: AbstractInput }); + +}(window.jQuery)); + +/** +List - abstract class for inputs that have source option loaded from js array or via ajax + +@class list +@extends abstractinput +**/ +(function ($) { + "use strict"; + + var List = function (options) { + + }; + + $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput); + + $.extend(List.prototype, { + render: function () { + var deferred = $.Deferred(); + + this.error = null; + this.onSourceReady(function () { + this.renderList(); + deferred.resolve(); + }, function () { + this.error = this.options.sourceError; + deferred.resolve(); + }); + + return deferred.promise(); + }, + + html2value: function (html) { + return null; //can't set value by text + }, + + value2html: function (value, element, display, response) { + var deferred = $.Deferred(), + success = function () { + if (typeof display === 'function') { + //custom display method + display.call(element, value, this.sourceData, response); + } else { + this.value2htmlFinal(value, element); + } + deferred.resolve(); + }; + + //for null value just call success without loading source + if (value === null) { + success.call(this); + } else { + this.onSourceReady(success, function () { deferred.resolve(); }); + } + + return deferred.promise(); + }, + + // ------------- additional functions ------------ + + onSourceReady: function (success, error) { + //run source if it function + var source; + if ($.isFunction(this.options.source)) { + source = this.options.source.call(this.options.scope); + this.sourceData = null; + //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed + } else { + source = this.options.source; + } + + //if allready loaded just call success + if (this.options.sourceCache && $.isArray(this.sourceData)) { + success.call(this); + return; + } + + //try parse json in single quotes (for double quotes jquery does automatically) + try { + source = $.fn.editableutils.tryParseJson(source, false); + } catch (e) { + error.call(this); + return; + } + + //loading from url + if (typeof source === 'string') { + //try to get sourceData from cache + if (this.options.sourceCache) { + var cacheID = source, + cache; + + if (!$(document).data(cacheID)) { + $(document).data(cacheID, {}); + } + cache = $(document).data(cacheID); + + //check for cached data + if (cache.loading === false && cache.sourceData) { //take source from cache + this.sourceData = cache.sourceData; + this.doPrepend(); + success.call(this); + return; + } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later + cache.callbacks.push($.proxy(function () { + this.sourceData = cache.sourceData; + this.doPrepend(); + success.call(this); + }, this)); + + //also collecting error callbacks + cache.err_callbacks.push($.proxy(error, this)); + return; + } else { //no cache yet, activate it + cache.loading = true; + cache.callbacks = []; + cache.err_callbacks = []; + } + } + + //ajaxOptions for source. Can be overwritten bt options.sourceOptions + var ajaxOptions = $.extend({ + url: source, + type: 'get', + cache: false, + dataType: 'json', + success: $.proxy(function (data) { + if (cache) { + cache.loading = false; + } + this.sourceData = this.makeArray(data); + if ($.isArray(this.sourceData)) { + if (cache) { + //store result in cache + cache.sourceData = this.sourceData; + //run success callbacks for other fields waiting for this source + $.each(cache.callbacks, function () { this.call(); }); + } + this.doPrepend(); + success.call(this); + } else { + error.call(this); + if (cache) { + //run error callbacks for other fields waiting for this source + $.each(cache.err_callbacks, function () { this.call(); }); + } + } + }, this), + error: $.proxy(function () { + error.call(this); + if (cache) { + cache.loading = false; + //run error callbacks for other fields + $.each(cache.err_callbacks, function () { this.call(); }); + } + }, this) + }, this.options.sourceOptions); + + //loading sourceData from server + $.ajax(ajaxOptions); + + } else { //options as json/array + this.sourceData = this.makeArray(source); + + if ($.isArray(this.sourceData)) { + this.doPrepend(); + success.call(this); + } else { + error.call(this); + } + } + }, + + doPrepend: function () { + if (this.options.prepend === null || this.options.prepend === undefined) { + return; + } + + if (!$.isArray(this.prependData)) { + //run prepend if it is function (once) + if ($.isFunction(this.options.prepend)) { + this.options.prepend = this.options.prepend.call(this.options.scope); + } + + //try parse json in single quotes + this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); + + //convert prepend from string to object + if (typeof this.options.prepend === 'string') { + this.options.prepend = { '': this.options.prepend }; + } + + this.prependData = this.makeArray(this.options.prepend); + } + + if ($.isArray(this.prependData) && $.isArray(this.sourceData)) { + this.sourceData = this.prependData.concat(this.sourceData); + } + }, + + /* + renders input list + */ + renderList: function () { + // this method should be overwritten in child class + }, + + /* + set element's html by value + */ + value2htmlFinal: function (value, element) { + // this method should be overwritten in child class + }, + + /** + * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}] + */ + makeArray: function (data) { + var count, obj, result = [], item, iterateItem; + if (!data || typeof data === 'string') { + return null; + } + + if ($.isArray(data)) { //array + /* + function to iterate inside item of array if item is object. + Caclulates count of keys in item and store in obj. + */ + iterateItem = function (k, v) { + obj = { value: k, text: v }; + if (count++ >= 2) { + return false;// exit from `each` if item has more than one key. + } + }; + + for (var i = 0; i < data.length; i++) { + item = data[i]; + if (typeof item === 'object') { + count = 0; //count of keys inside item + $.each(item, iterateItem); + //case: [{val1: 'text1'}, {val2: 'text2} ...] + if (count === 1) { + result.push(obj); + //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...] + } else if (count > 1) { + //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text') + if (item.children) { + item.children = this.makeArray(item.children); + } + result.push(item); + } + } else { + //case: ['text1', 'text2' ...] + result.push({ value: item, text: item }); + } + } + } else { //case: {val1: 'text1', val2: 'text2, ...} + $.each(data, function (k, v) { + result.push({ value: k, text: v }); + }); + } + return result; + }, + + option: function (key, value) { + this.options[key] = value; + if (key === 'source') { + this.sourceData = null; + } + if (key === 'prepend') { + this.prependData = null; + } + } + + }); + + List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + /** + Source data for list. + If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]` + For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order. + + If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option. + + If **function**, it should return data in format above (since 1.4.0). + + Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only): + @example + [ + {text: "group1", children: [ + {value: 1, text: "text1"}, + {value: 2, text: "text2"} + ]}, + ... + ] + + + @property source + @type string | array | object | function + @default null + **/ + source: null, + /** + Data automatically prepended to the beginning of dropdown list. + + @property prepend + @type string | array | object | function + @default false + **/ + prepend: false, + /** + Error message when list cannot be loaded (e.g. ajax error) + + @property sourceError + @type string + @default Error when loading list + **/ + sourceError: 'Error when loading list', + /** + if `true` and source is **string url** - results will be cached for fields with the same source. + Usefull for editable column in grid to prevent extra requests. + + @property sourceCache + @type boolean + @default true + @since 1.2.0 + **/ + sourceCache: true, + /** + Additional ajax options to be used in $.ajax() when loading list from server. + Useful to send extra parameters or change request method. + + @property sourceOptions + @type object|function + @default null + @since 1.5.0 + @example + sourceOptions: { + data: {param: 123}, + type: 'post' + } + + **/ + sourceOptions: null + }); + + $.fn.editabletypes.list = List; + +}(window.jQuery)); + +/** +Text input + +@class text +@extends abstractinput +@final +@example +awesome + +**/ +(function ($) { + "use strict"; + + var Text = function (options) { + this.init('text', options, Text.defaults); + }; + + $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput); + + $.extend(Text.prototype, { + render: function () { + this.renderClear(); + this.setClass(); + this.setAttr('placeholder'); + }, + + activate: function () { + if (this.$input.is(':visible')) { + this.$input.focus(); + // if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"],[type="number"],[type="email"]')) { + if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"]')) { + $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length); + } + + if (this.toggleClear) { + this.toggleClear(); + } + } + }, + + //render clear button + renderClear: function () { + if (this.options.clear) { + this.$clear = $(''); + this.$input.after(this.$clear) + .css('padding-right', 24) + .keyup($.proxy(function (e) { + //arrows, enter, tab, etc + if (~$.inArray(e.keyCode, [40, 38, 9, 13, 27])) { + return; + } + + clearTimeout(this.t); + var that = this; + this.t = setTimeout(function () { + that.toggleClear(e); + }, 100); + + }, this)) + .parent().css('position', 'relative'); + + this.$clear.click($.proxy(this.clear, this)); + } + }, + + postrender: function () { + /* + //now `clear` is positioned via css + if(this.$clear) { + //can position clear button only here, when form is shown and height can be calculated +// var h = this.$input.outerHeight(true) || 20, + var h = this.$clear.parent().height(), + delta = (h - this.$clear.height()) / 2; + + //this.$clear.css({bottom: delta, right: delta}); + } + */ + }, + + //show / hide clear button + toggleClear: function (e) { + if (!this.$clear) { + return; + } + + var len = this.$input.val().length, + visible = this.$clear.is(':visible'); + + if (len && !visible) { + this.$clear.show(); + } + + if (!len && visible) { + this.$clear.hide(); + } + }, + + clear: function () { + this.$clear.hide(); + this.$input.val('').focus(); + } + }); + + Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + /** + @property tpl + @default + **/ + tpl: '', + /** + Placeholder attribute of input. Shown when input is empty. + + @property placeholder + @type string + @default null + **/ + placeholder: null, + + /** + Whether to show `clear` button + + @property clear + @type boolean + @default true + **/ + clear: true + }); + + $.fn.editabletypes.text = Text; + +}(window.jQuery)); + +/** +Textarea input + +@class textarea +@extends abstractinput +@final +@example +awesome comment! + +**/ +(function ($) { + "use strict"; + + var Textarea = function (options) { + this.init('textarea', options, Textarea.defaults); + }; + + $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput); + + $.extend(Textarea.prototype, { + render: function () { + this.setClass(); + this.setAttr('placeholder'); + this.setAttr('rows'); + + //ctrl + enter + this.$input.keydown(function (e) { + if (e.ctrlKey && e.which === 13) { + $(this).closest('form').submit(); + } + }); + }, + + //using `white-space: pre-wrap` solves \n <--> BR conversion very elegant! + /* + value2html: function(value, element) { + var html = '', lines; + if(value) { + lines = value.split("\n"); + for (var i = 0; i < lines.length; i++) { + lines[i] = $('
            ').text(lines[i]).html(); + } + html = lines.join('
            '); + } + $(element).html(html); + }, + + html2value: function(html) { + if(!html) { + return ''; + } + + var regex = new RegExp(String.fromCharCode(10), 'g'); + var lines = html.split(//i); + for (var i = 0; i < lines.length; i++) { + var text = $('
            ').html(lines[i]).text(); + + // Remove newline characters (\n) to avoid them being converted by value2html() method + // thus adding extra
            tags + text = text.replace(regex, ''); + + lines[i] = text; + } + return lines.join("\n"); + }, + */ + activate: function () { + $.fn.editabletypes.text.prototype.activate.call(this); + } + }); + + Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + /** + @property tpl + @default + **/ + tpl: '', + /** + @property inputclass + @default input-large + **/ + inputclass: 'input-large', + /** + Placeholder attribute of input. Shown when input is empty. + + @property placeholder + @type string + @default null + **/ + placeholder: null, + /** + Number of rows in textarea + + @property rows + @type integer + @default 7 + **/ + rows: 7 + }); + + $.fn.editabletypes.textarea = Textarea; + +}(window.jQuery)); + +/** +Select (dropdown) + +@class select +@extends list +@final +@example + + +**/ +(function ($) { + "use strict"; + + var Select = function (options) { + this.init('select', options, Select.defaults); + }; + + $.fn.editableutils.inherit(Select, $.fn.editabletypes.list); + + $.extend(Select.prototype, { + renderList: function () { + this.$input.empty(); + var escape = this.options.escape; + + var fillItems = function ($el, data) { + var attr; + if ($.isArray(data)) { + for (var i = 0; i < data.length; i++) { + attr = {}; + if (data[i].children) { + attr.label = data[i].text; + $el.append(fillItems($('', attr), data[i].children)); + } else { + attr.value = data[i].value; + if (data[i].disabled) { + attr.disabled = true; + } + var $option = $('
            '+dates[this.language].daysMin[(dowCnt++)%7]+'
            '+prevMonth.getUTCDate() + '
            '+ - DPGlobal.headTemplate+ - ''+ - DPGlobal.footTemplate+ - '
            '+ - '
            '+ - '
            '+ - ''+ - DPGlobal.headTemplate+ - DPGlobal.contTemplate+ - DPGlobal.footTemplate+ - '
            '+ - '
            '+ - '
            '+ - ''+ - DPGlobal.headTemplate+ - DPGlobal.contTemplate+ - DPGlobal.footTemplate+ - '
            '+ - '
            '+ - ''; - - $.fn.datepicker.DPGlobal = DPGlobal; - -}( window.jQuery ); - -/** -Typeahead input (bootstrap only). Based on Twitter Bootstrap [typeahead](http://twitter.github.com/bootstrap/javascript.html#typeahead). -Depending on `source` format typeahead operates in two modes: - -* **strings**: - When `source` defined as array of strings, e.g. `['text1', 'text2', 'text3' ...]`. - User can submit one of these strings or any text entered in input (even if it is not matching source). - -* **objects**: - When `source` defined as array of objects, e.g. `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`. - User can submit only values that are in source (otherwise `null` is submitted). This is more like *dropdown* behavior. - -@class typeahead -@extends list -@since 1.4.1 -@final -@example - - -**/ -(function ($) { - - var Constructor = function (options) { - this.init('typeahead', options, Constructor.defaults); - - //overriding objects in config (as by default jQuery extend() is not recursive) - this.options.typeahead = $.extend({}, Constructor.defaults.typeahead, { - //set default methods for typeahead to work with objects - matcher: this.matcher, - sorter: this.sorter, - highlighter: this.highlighter, - updater: this.updater - }, options.typeahead); - }; - - $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.list); - - $.extend(Constructor.prototype, { - renderList: function() { - this.$input = this.$tpl.is('input') ? this.$tpl : this.$tpl.find('input[type="text"]'); - - //set source of typeahead - this.options.typeahead.source = this.sourceData; - - //apply typeahead - this.$input.typeahead(this.options.typeahead); - - //attach own render method - this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead')); - - this.renderClear(); - this.setClass(); - this.setAttr('placeholder'); - }, - - value2htmlFinal: function(value, element) { - if(this.getIsObjects()) { - var items = $.fn.editableutils.itemsByValue(value, this.sourceData); - $(element).text(items.length ? items[0].text : ''); - } else { - $(element).text(value); - } - }, - - html2value: function (html) { - return html ? html : null; - }, - - value2input: function(value) { - if(this.getIsObjects()) { - var items = $.fn.editableutils.itemsByValue(value, this.sourceData); - this.$input.data('value', value).val(items.length ? items[0].text : ''); - } else { - this.$input.val(value); - } - }, - - input2value: function() { - if(this.getIsObjects()) { - var value = this.$input.data('value'), - items = $.fn.editableutils.itemsByValue(value, this.sourceData); - - if(items.length && items[0].text.toLowerCase() === this.$input.val().toLowerCase()) { - return value; - } else { - return null; //entered string not found in source - } - } else { - return this.$input.val(); - } - }, - - /* - if in sourceData values <> texts, typeahead in "objects" mode: - user must pick some value from list, otherwise `null` returned. - if all values == texts put typeahead in "strings" mode: - anything what entered is submited. - */ - getIsObjects: function() { - if(this.isObjects === undefined) { - this.isObjects = false; - for(var i=0; i - **/ - tpl:'', - /** - Configuration of typeahead. [Full list of options](http://twitter.github.com/bootstrap/javascript.html#typeahead). - - @property typeahead - @type object - @default null - **/ - typeahead: null, - /** - Whether to show `clear` button - - @property clear - @type boolean - @default true - **/ - clear: true - }); - - $.fn.editabletypes.typeahead = Constructor; - -}(window.jQuery)); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/x-editable/bootstrap-editable.min.js b/src/Presentation/SmartStore.Web/Content/x-editable/bootstrap-editable.min.js deleted file mode 100644 index f9b38983ee..0000000000 --- a/src/Presentation/SmartStore.Web/Content/x-editable/bootstrap-editable.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! X-editable - v1.4.1 -* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery -* http://github.com/vitalets/x-editable -* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ -(function (e) { var t = function (t, n) { this.options = e.extend({}, e.fn.editableform.defaults, n), this.$div = e(t), this.options.scope || (this.options.scope = this) }; t.prototype = { constructor: t, initInput: function () { this.input = this.options.input, this.value = this.input.str2value(this.options.value) }, initTemplate: function () { this.$form = e(e.fn.editableform.template) }, initButtons: function () { this.$form.find(".editable-buttons").append(e.fn.editableform.buttons) }, render: function () { this.$loading = e(e.fn.editableform.loading), this.$div.empty().append(this.$loading), this.initTemplate(), this.options.showbuttons ? this.initButtons() : this.$form.find(".editable-buttons").remove(), this.showLoading(), this.$div.triggerHandler("rendering"), this.initInput(), this.input.prerender(), this.$form.find("div.editable-input").append(this.input.$tpl), this.$div.append(this.$form), e.when(this.input.render()).then(e.proxy(function () { this.options.showbuttons || this.input.autosubmit(), this.$form.find(".editable-cancel").click(e.proxy(this.cancel, this)), this.input.error ? (this.error(this.input.error), this.$form.find(".editable-submit").attr("disabled", !0), this.input.$input.attr("disabled", !0), this.$form.submit(function (e) { e.preventDefault() })) : (this.error(!1), this.input.$input.removeAttr("disabled"), this.$form.find(".editable-submit").removeAttr("disabled"), this.input.value2input(this.value), this.$form.submit(e.proxy(this.submit, this))), this.$div.triggerHandler("rendered"), this.showForm(), this.input.postrender && this.input.postrender() }, this)) }, cancel: function () { this.$div.triggerHandler("cancel") }, showLoading: function () { var e, t; this.$form ? (e = this.$form.outerWidth(), t = this.$form.outerHeight(), e && this.$loading.width(e), t && this.$loading.height(t), this.$form.hide()) : (e = this.$loading.parent().width(), e && this.$loading.width(e)), this.$loading.show() }, showForm: function (e) { this.$loading.hide(), this.$form.show(), e !== !1 && this.input.activate(), this.$div.triggerHandler("show") }, error: function (t) { var n = this.$form.find(".control-group"), r = this.$form.find(".editable-error-block"), i; if (t === !1) n.removeClass(e.fn.editableform.errorGroupClass), r.removeClass(e.fn.editableform.errorBlockClass).empty().hide(); else { if (t) { i = t.split("\n"); for (var s = 0; s < i.length; s++) i[s] = e("
            ").text(i[s]).html(); t = i.join("
            ") } n.addClass(e.fn.editableform.errorGroupClass), r.addClass(e.fn.editableform.errorBlockClass).html(t).show() } }, submit: function (t) { t.stopPropagation(), t.preventDefault(); var n, r = this.input.input2value(); if (n = this.validate(r)) { this.error(n), this.showForm(); return } if (!this.options.savenochange && this.input.value2str(r) == this.input.value2str(this.value)) { this.$div.triggerHandler("nochange"); return } e.when(this.save(r)).done(e.proxy(function (e) { var t = typeof this.options.success == "function" ? this.options.success.call(this.options.scope, e, r) : null; if (t === !1) { this.error(!1), this.showForm(!1); return } if (typeof t == "string") { this.error(t), this.showForm(); return } t && typeof t == "object" && t.hasOwnProperty("newValue") && (r = t.newValue), this.error(!1), this.value = r, this.$div.triggerHandler("save", { newValue: r, response: e }) }, this)).fail(e.proxy(function (e) { this.error(typeof e == "string" ? e : e.responseText || e.statusText || "Unknown error!"), this.showForm() }, this)) }, save: function (t) { var n = this.input.value2submit(t); this.options.pk = e.fn.editableutils.tryParseJson(this.options.pk, !0); var r = typeof this.options.pk == "function" ? this.options.pk.call(this.options.scope) : this.options.pk, i = !!(typeof this.options.url == "function" || this.options.url && (this.options.send === "always" || this.options.send === "auto" && r)), s; if (i) return this.showLoading(), s = { name: this.options.name || "", value: n, pk: r }, typeof this.options.params == "function" ? s = this.options.params.call(this.options.scope, s) : (this.options.params = e.fn.editableutils.tryParseJson(this.options.params, !0), e.extend(s, this.options.params)), typeof this.options.url == "function" ? this.options.url.call(this.options.scope, s) : e.ajax(e.extend({ url: this.options.url, data: s, type: "POST" }, this.options.ajaxOptions)) }, validate: function (e) { e === undefined && (e = this.value); if (typeof this.options.validate == "function") return this.options.validate.call(this.options.scope, e) }, option: function (e, t) { e in this.options && (this.options[e] = t), e === "value" && this.setValue(t) }, setValue: function (e, t) { t ? this.value = this.input.str2value(e) : this.value = e, this.$form && this.$form.is(":visible") && this.input.value2input(this.value) } }, e.fn.editableform = function (n) { var r = arguments; return this.each(function () { var i = e(this), s = i.data("editableform"), o = typeof n == "object" && n; s || i.data("editableform", s = new t(this, o)), typeof n == "string" && s[n].apply(s, Array.prototype.slice.call(r, 1)) }) }, e.fn.editableform.Constructor = t, e.fn.editableform.defaults = { type: "text", url: null, params: null, name: null, pk: null, value: null, send: "auto", validate: null, success: null, ajaxOptions: null, showbuttons: !0, scope: null, savenochange: !1 }, e.fn.editableform.template = '
            ', e.fn.editableform.loading = '
            ', e.fn.editableform.buttons = '', e.fn.editableform.errorGroupClass = null, e.fn.editableform.errorBlockClass = "editable-error" })(window.jQuery), function (e) { e.fn.editableutils = { inherit: function (e, t) { var n = function () { }; n.prototype = t.prototype, e.prototype = new n, e.prototype.constructor = e, e.superclass = t.prototype }, setCursorPosition: function (e, t) { if (e.setSelectionRange) e.setSelectionRange(t, t); else if (e.createTextRange) { var n = e.createTextRange(); n.collapse(!0), n.moveEnd("character", t), n.moveStart("character", t), n.select() } }, tryParseJson: function (e, t) { if (typeof e == "string" && e.length && e.match(/^[\{\[].*[\}\]]$/)) if (t) try { e = (new Function("return " + e))() } catch (n) { } finally { return e } else e = (new Function("return " + e))(); return e }, sliceObj: function (t, n, r) { var i, s, o = {}; if (!e.isArray(n) || !n.length) return o; for (var u = 0; u < n.length; u++) { i = n[u], t.hasOwnProperty(i) && (o[i] = t[i]); if (r === !0) continue; s = i.toLowerCase(), t.hasOwnProperty(s) && (o[i] = t[s]) } return o }, getConfigData: function (t) { var n = {}; return e.each(t.data(), function (e, t) { if (typeof t != "object" || t && typeof t == "object" && (t.constructor === Object || t.constructor === Array)) n[e] = t }), n }, objectKeys: function (e) { if (Object.keys) return Object.keys(e); if (e !== Object(e)) throw new TypeError("Object.keys called on a non-object"); var t = [], n; for (n in e) Object.prototype.hasOwnProperty.call(e, n) && t.push(n); return t }, escape: function (t) { return e("
            ").text(t).html() }, itemsByValue: function (t, n, r) { if (!n || t === null) return []; r = r || "value"; var i = e.isArray(t), s = [], o = this; return e.each(n, function (n, u) { u.children ? s = s.concat(o.itemsByValue(t, u.children)) : i ? e.grep(t, function (e) { return e == (u && typeof u === "object" ? u[r] : u) }).length && s.push(u) : t == (u && typeof u === "object" ? u[r] : u) && s.push(u) }), s }, createInput: function (t) { var n, r, i, s = t.type; return s === "date" && (t.mode === "inline" ? e.fn.editabletypes.datefield ? s = "datefield" : e.fn.editabletypes.dateuifield && (s = "dateuifield") : e.fn.editabletypes.date ? s = "date" : e.fn.editabletypes.dateui && (s = "dateui"), s === "date" && !e.fn.editabletypes.date && (s = "combodate")), s === "wysihtml5" && !e.fn.editabletypes[s] && (s = "textarea"), typeof e.fn.editabletypes[s] == "function" ? (n = e.fn.editabletypes[s], r = this.sliceObj(t, this.objectKeys(n.defaults)), i = new n(r), i) : (e.error("Unknown type: " + s), !1) } } }(window.jQuery), function (e) { var t = function (e, t) { this.init(e, t) }, n = function (e, t) { this.init(e, t) }; t.prototype = { containerName: null, innerCss: null, init: function (n, r) { this.$element = e(n), this.options = e.extend({}, e.fn.editableContainer.defaults, r), this.splitOptions(), this.formOptions.scope = this.$element[0], this.initContainer(), this.$element.on("destroyed", e.proxy(function () { this.destroy() }, this)), e(document).data("editable-handlers-attached") || (e(document).on("keyup.editable", function (t) { t.which === 27 && e(".editable-open").editableContainer("hide") }), e(document).on("click.editable", function (n) { var r = e(n.target), i, s = [".editable-container", ".ui-datepicker-header", ".modal-backdrop", ".bootstrap-wysihtml5-insert-image-modal", ".bootstrap-wysihtml5-insert-link-modal"]; for (i = 0; i < s.length; i++) if (r.is(s[i]) || r.parents(s[i]).length) return; t.prototype.closeOthers(n.target) }), e(document).data("editable-handlers-attached", !0)) }, splitOptions: function () { this.containerOptions = {}, this.formOptions = {}; var t = e.fn[this.containerName].defaults; for (var n in this.options) n in t ? this.containerOptions[n] = this.options[n] : this.formOptions[n] = this.options[n] }, tip: function () { return this.container() ? this.container().$tip : null }, container: function () { return this.$element.data(this.containerName) }, call: function () { this.$element[this.containerName].apply(this.$element, arguments) }, initContainer: function () { this.call(this.containerOptions) }, renderForm: function () { this.$form.editableform(this.formOptions).on({ save: e.proxy(this.save, this), nochange: e.proxy(function () { this.hide("nochange") }, this), cancel: e.proxy(function () { this.hide("cancel") }, this), show: e.proxy(this.setPosition, this), rendering: e.proxy(this.setPosition, this), resize: e.proxy(this.setPosition, this), rendered: e.proxy(function () { this.$element.triggerHandler("shown") }, this) }).editableform("render") }, show: function (t) { this.$element.addClass("editable-open"), t !== !1 && this.closeOthers(this.$element[0]), this.innerShow(), this.tip().addClass("editable-container"), this.$form, this.$form = e("
            "), this.tip().is(this.innerCss) ? this.tip().append(this.$form) : this.tip().find(this.innerCss).append(this.$form), this.renderForm() }, hide: function (e) { if (!this.tip() || !this.tip().is(":visible") || !this.$element.hasClass("editable-open")) return; this.$element.removeClass("editable-open"), this.innerHide(), this.$element.triggerHandler("hidden", e) }, innerShow: function () { }, innerHide: function () { }, toggle: function (e) { this.container() && this.tip() && this.tip().is(":visible") ? this.hide() : this.show(e) }, setPosition: function () { }, save: function (e, t) { this.$element.triggerHandler("save", t), this.hide("save") }, option: function (e, t) { this.options[e] = t, e in this.containerOptions ? (this.containerOptions[e] = t, this.setContainerOption(e, t)) : (this.formOptions[e] = t, this.$form && this.$form.editableform("option", e, t)) }, setContainerOption: function (e, t) { this.call("option", e, t) }, destroy: function () { this.hide(), this.innerDestroy(), this.$element.off("destroyed"), this.$element.removeData("editableContainer") }, innerDestroy: function () { }, closeOthers: function (t) { e(".editable-open").each(function (n, r) { if (r === t || e(r).find(t).length) return; var i = e(r), s = i.data("editableContainer"); if (!s) return; s.options.onblur === "cancel" ? i.data("editableContainer").hide("onblur") : s.options.onblur === "submit" && i.data("editableContainer").tip().find("form").submit() }) }, activate: function () { this.tip && this.tip().is(":visible") && this.$form && this.$form.data("editableform").input.activate() } }, e.fn.editableContainer = function (r) { var i = arguments; return this.each(function () { var s = e(this), o = "editableContainer", u = s.data(o), a = typeof r == "object" && r, f = a.mode === "inline" ? n : t; u || s.data(o, u = new f(this, a)), typeof r == "string" && u[r].apply(u, Array.prototype.slice.call(i, 1)) }) }, e.fn.editableContainer.Popup = t, e.fn.editableContainer.Inline = n, e.fn.editableContainer.defaults = { value: null, placement: "top", autohide: !0, onblur: "cancel", anim: "fast", mode: "popup" }, jQuery.event.special.destroyed = { remove: function (e) { e.handler && e.handler() } } }(window.jQuery), function (e) { e.extend(e.fn.editableContainer.Inline.prototype, e.fn.editableContainer.Popup.prototype, { containerName: "editableform", innerCss: ".editable-inline", initContainer: function () { this.$tip = e("").addClass("editable-inline"), this.options.anim || (this.options.anim = 0) }, splitOptions: function () { this.containerOptions = {}, this.formOptions = this.options }, tip: function () { return this.$tip }, innerShow: function () { this.$element.hide(), this.tip().insertAfter(this.$element).show() }, innerHide: function () { this.$tip.hide(this.options.anim, e.proxy(function () { this.$element.show(), this.innerDestroy() }, this)) }, innerDestroy: function () { this.tip() && this.tip().empty().remove() } }) }(window.jQuery), function (e) { var t = function (t, n) { this.$element = e(t), this.options = e.extend({}, e.fn.editable.defaults, n, e.fn.editableutils.getConfigData(this.$element)), this.options.selector ? this.initLive() : this.init() }; t.prototype = { constructor: t, init: function () { var t = !1, n, r; this.options.name = this.options.name || this.$element.attr("id"), this.input = e.fn.editableutils.createInput(this.options); if (!this.input) return; this.options.value === undefined || this.options.value === null ? (this.value = this.input.html2value(e.trim(this.$element.html())), t = !0) : (this.options.value = e.fn.editableutils.tryParseJson(this.options.value, !0), typeof this.options.value == "string" ? this.value = this.input.str2value(this.options.value) : this.value = this.options.value), this.$element.addClass("editable"), this.options.toggle !== "manual" ? (this.$element.addClass("editable-click"), this.$element.on(this.options.toggle + ".editable", e.proxy(function (e) { e.preventDefault(); if (this.options.toggle === "mouseenter") this.show(); else { var t = this.options.toggle !== "click"; this.toggle(t) } }, this))) : this.$element.attr("tabindex", -1), n = !t && this.value !== null && this.value !== undefined, n &= this.options.autotext === "always" || this.options.autotext === "auto" && !this.$element.text().length, e.when(n ? this.render() : !0).then(e.proxy(function () { this.options.disabled ? this.disable() : this.enable(), this.$element.triggerHandler("init", this) }, this)) }, initLive: function () { var t = this.options.selector; this.options.selector = !1, this.options.autotext = "never", this.$element.on(this.options.toggle + ".editable", t, e.proxy(function (t) { var n = e(t.target); n.data("editable") || n.editable(this.options).trigger(t) }, this)) }, render: function (e) { if (this.options.display === !1) return; return this.input.value2htmlFinal ? this.input.value2html(this.value, this.$element[0], this.options.display, e) : typeof this.options.display == "function" ? this.options.display.call(this.$element[0], this.value, e) : this.input.value2html(this.value, this.$element[0]) }, enable: function () { this.options.disabled = !1, this.$element.removeClass("editable-disabled"), this.handleEmpty(this.isEmpty), this.options.toggle !== "manual" && this.$element.attr("tabindex") === "-1" && this.$element.removeAttr("tabindex") }, disable: function () { this.options.disabled = !0, this.hide(), this.$element.addClass("editable-disabled"), this.handleEmpty(this.isEmpty), this.$element.attr("tabindex", -1) }, toggleDisabled: function () { this.options.disabled ? this.enable() : this.disable() }, option: function (t, n) { if (t && typeof t == "object") { e.each(t, e.proxy(function (t, n) { this.option(e.trim(t), n) }, this)); return } this.options[t] = n; if (t === "disabled") return n ? this.disable() : this.enable(); t === "value" && this.setValue(n), this.container && this.container.option(t, n), this.input.option && this.input.option(t, n) }, handleEmpty: function (t) { if (this.options.display === !1) return; this.isEmpty = t !== undefined ? t : e.trim(this.$element.text()) === "", this.options.disabled ? this.isEmpty && (this.$element.empty(), this.options.emptyclass && this.$element.removeClass(this.options.emptyclass)) : this.isEmpty ? (this.$element.text(this.options.emptytext), this.options.emptyclass && this.$element.addClass(this.options.emptyclass)) : this.options.emptyclass && this.$element.removeClass(this.options.emptyclass) }, show: function (t) { if (this.options.disabled) return; if (!this.container) { var n = e.extend({}, this.options, { value: this.value, input: this.input }); this.$element.editableContainer(n), this.$element.on("save.internal", e.proxy(this.save, this)), this.container = this.$element.data("editableContainer") } else if (this.container.tip().is(":visible")) return; this.container.show(t) }, hide: function () { this.container && this.container.hide() }, toggle: function (e) { this.container && this.container.tip().is(":visible") ? this.hide() : this.show(e) }, save: function (e, t) { if (this.options.unsavedclass) { var n = !1; n = n || typeof this.options.url == "function", n = n || this.options.display === !1, n = n || t.response !== undefined, n = n || this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(t.newValue), n ? this.$element.removeClass(this.options.unsavedclass) : this.$element.addClass(this.options.unsavedclass) } this.setValue(t.newValue, !1, t.response) }, validate: function () { if (typeof this.options.validate == "function") return this.options.validate.call(this, this.value) }, setValue: function (t, n, r) { n ? this.value = this.input.str2value(t) : this.value = t, this.container && this.container.option("value", this.value), e.when(this.render(r)).then(e.proxy(function () { this.handleEmpty() }, this)) }, activate: function () { this.container && this.container.activate() }, destroy: function () { this.container && this.container.destroy(), this.options.toggle !== "manual" && (this.$element.removeClass("editable-click"), this.$element.off(this.options.toggle + ".editable")), this.$element.off("save.internal"), this.$element.removeClass("editable"), this.$element.removeClass("editable-open"), this.$element.removeData("editable") } }, e.fn.editable = function (n) { var r = {}, i = arguments, s = "editable"; switch (n) { case "validate": return this.each(function () { var t = e(this), n = t.data(s), i; n && (i = n.validate()) && (r[n.options.name] = i) }), r; case "getValue": return this.each(function () { var t = e(this), n = t.data(s); n && n.value !== undefined && n.value !== null && (r[n.options.name] = n.input.value2submit(n.value)) }), r; case "submit": var o = arguments[1] || {}, u = this, a = this.editable("validate"), f; return e.isEmptyObject(a) ? (f = this.editable("getValue"), o.data && e.extend(f, o.data), e.ajax(e.extend({ url: o.url, data: f, type: "POST" }, o.ajaxOptions)).success(function (e) { typeof o.success == "function" && o.success.call(u, e, o) }).error(function () { typeof o.error == "function" && o.error.apply(u, arguments) })) : typeof o.error == "function" && o.error.call(u, a), this } return this.each(function () { var r = e(this), o = r.data(s), u = typeof n == "object" && n; o || r.data(s, o = new t(this, u)), typeof n == "string" && o[n].apply(o, Array.prototype.slice.call(i, 1)) }) }, e.fn.editable.defaults = { type: "text", disabled: !1, toggle: "click", emptytext: "Empty", autotext: "auto", value: null, display: null, emptyclass: "editable-empty", unsavedclass: "editable-unsaved", selector: null } }(window.jQuery), function (e) { e.fn.editabletypes = {}; var t = function () { }; t.prototype = { init: function (t, n, r) { this.type = t, this.options = e.extend({}, r, n) }, prerender: function () { this.$tpl = e(this.options.tpl), this.$input = this.$tpl, this.$clear = null, this.error = null }, render: function () { }, value2html: function (t, n) { e(n).text(t) }, html2value: function (t) { return e("
            ").html(t).text() }, value2str: function (e) { return e }, str2value: function (e) { return e }, value2submit: function (e) { return e }, value2input: function (e) { this.$input.val(e) }, input2value: function () { return this.$input.val() }, activate: function () { this.$input.is(":visible") && this.$input.focus() }, clear: function () { this.$input.val(null) }, escape: function (t) { return e("
            ").text(t).html() }, autosubmit: function () { }, setClass: function () { this.options.inputclass && this.$input.addClass(this.options.inputclass) }, setAttr: function (e) { this.options[e] && this.$input.attr(e, this.options[e]) }, option: function (e, t) { this.options[e] = t } }, t.defaults = { tpl: "", inputclass: "input-medium" }, e.extend(e.fn.editabletypes, { abstractinput: t }) }(window.jQuery), function (e) { var t = function (e) { }; e.fn.editableutils.inherit(t, e.fn.editabletypes.abstractinput), e.extend(t.prototype, { render: function () { var t = e.Deferred(); return this.error = null, this.onSourceReady(function () { this.renderList(), t.resolve() }, function () { this.error = this.options.sourceError, t.resolve() }), t.promise() }, html2value: function (e) { return null }, value2html: function (t, n, r, i) { var s = e.Deferred(), o = function () { typeof r == "function" ? r.call(n, t, this.sourceData, i) : this.value2htmlFinal(t, n), s.resolve() }; return t === null ? o.call(this) : this.onSourceReady(o, function () { s.resolve() }), s.promise() }, onSourceReady: function (t, n) { if (e.isArray(this.sourceData)) { t.call(this); return } try { this.options.source = e.fn.editableutils.tryParseJson(this.options.source, !1) } catch (r) { n.call(this); return } if (typeof this.options.source == "string") { if (this.options.sourceCache) { var i = this.options.source, s; e(document).data(i) || e(document).data(i, {}), s = e(document).data(i); if (s.loading === !1 && s.sourceData) { this.sourceData = s.sourceData, this.doPrepend(), t.call(this); return } if (s.loading === !0) { s.callbacks.push(e.proxy(function () { this.sourceData = s.sourceData, this.doPrepend(), t.call(this) }, this)), s.err_callbacks.push(e.proxy(n, this)); return } s.loading = !0, s.callbacks = [], s.err_callbacks = [] } e.ajax({ url: this.options.source, type: "get", cache: !1, dataType: "json", success: e.proxy(function (r) { s && (s.loading = !1), this.sourceData = this.makeArray(r), e.isArray(this.sourceData) ? (s && (s.sourceData = this.sourceData, e.each(s.callbacks, function () { this.call() })), this.doPrepend(), t.call(this)) : (n.call(this), s && e.each(s.err_callbacks, function () { this.call() })) }, this), error: e.proxy(function () { n.call(this), s && (s.loading = !1, e.each(s.err_callbacks, function () { this.call() })) }, this) }) } else e.isFunction(this.options.source) ? this.sourceData = this.makeArray(this.options.source()) : this.sourceData = this.makeArray(this.options.source), e.isArray(this.sourceData) ? (this.doPrepend(), t.call(this)) : n.call(this) }, doPrepend: function () { if (this.options.prepend === null || this.options.prepend === undefined) return; e.isArray(this.prependData) || (this.options.prepend = e.fn.editableutils.tryParseJson(this.options.prepend, !0), typeof this.options.prepend == "string" && (this.options.prepend = { "": this.options.prepend }), typeof this.options.prepend == "function" ? this.prependData = this.makeArray(this.options.prepend()) : this.prependData = this.makeArray(this.options.prepend)), e.isArray(this.prependData) && e.isArray(this.sourceData) && (this.sourceData = this.prependData.concat(this.sourceData)) }, renderList: function () { }, value2htmlFinal: function (e, t) { }, makeArray: function (t) { var n, r, i = [], s, o; if (!t || typeof t == "string") return null; if (e.isArray(t)) { o = function (e, t) { r = { value: e, text: t }; if (n++ >= 2) return !1 }; for (var u = 0; u < t.length; u++) s = t[u], typeof s == "object" ? (n = 0, e.each(s, o), n === 1 ? i.push(r) : n > 1 && (s.children && (s.children = this.makeArray(s.children)), i.push(s))) : i.push({ value: s, text: s }) } else e.each(t, function (e, t) { i.push({ value: e, text: t }) }); return i }, option: function (e, t) { this.options[e] = t, e === "source" && (this.sourceData = null), e === "prepend" && (this.prependData = null) } }), t.defaults = e.extend({}, e.fn.editabletypes.abstractinput.defaults, { source: null, prepend: !1, sourceError: "Error when loading list", sourceCache: !0 }), e.fn.editabletypes.list = t }(window.jQuery), function (e) { var t = function (e) { this.init("text", e, t.defaults) }; e.fn.editableutils.inherit(t, e.fn.editabletypes.abstractinput), e.extend(t.prototype, { render: function () { this.renderClear(), this.setClass(), this.setAttr("placeholder") }, activate: function () { this.$input.is(":visible") && (this.$input.focus(), e.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length), this.toggleClear && this.toggleClear()) }, renderClear: function () { this.options.clear && (this.$clear = e(''), this.$input.after(this.$clear).css("padding-right", 20).keyup(e.proxy(this.toggleClear, this)).parent().css("position", "relative"), this.$clear.click(e.proxy(this.clear, this))) }, postrender: function () { if (this.$clear) { var e = this.$input.outerHeight() || 20, t = (e - this.$clear.height()) / 2; t < 3 && (t = 3), this.$clear.css({ top: t, right: t }) } }, toggleClear: function () { if (!this.$clear) return; this.$input.val().length ? this.$clear.show() : this.$clear.hide() }, clear: function () { this.$clear.hide(), this.$input.val("").focus() } }), t.defaults = e.extend({}, e.fn.editabletypes.abstractinput.defaults, { tpl: '', placeholder: null, clear: !0 }), e.fn.editabletypes.text = t }(window.jQuery), function (e) { var t = function (e) { this.init("textarea", e, t.defaults) }; e.fn.editableutils.inherit(t, e.fn.editabletypes.abstractinput), e.extend(t.prototype, { render: function () { this.setClass(), this.setAttr("placeholder"), this.setAttr("rows"), this.$input.keydown(function (t) { t.ctrlKey && t.which === 13 && e(this).closest("form").submit() }) }, value2html: function (t, n) { var r = "", i; if (t) { i = t.split("\n"); for (var s = 0; s < i.length; s++) i[s] = e("
            ").text(i[s]).html(); r = i.join("
            ") } e(n).html(r) }, html2value: function (t) { if (!t) return ""; var n = new RegExp(String.fromCharCode(10), "g"), r = t.split(//i); for (var i = 0; i < r.length; i++) { var s = e("
            ").html(r[i]).text(); s = s.replace(n, ""), r[i] = s } return r.join("\n") }, activate: function () { e.fn.editabletypes.text.prototype.activate.call(this) } }), t.defaults = e.extend({}, e.fn.editabletypes.abstractinput.defaults, { tpl: "", inputclass: "input-large", placeholder: null, rows: 7 }), e.fn.editabletypes.textarea = t }(window.jQuery), function (e) { var t = function (e) { this.init("select", e, t.defaults) }; e.fn.editableutils.inherit(t, e.fn.editabletypes.list), e.extend(t.prototype, { renderList: function () { this.$input.empty(); var t = function (n, r) { if (e.isArray(r)) for (var i = 0; i < r.length; i++) r[i].children ? n.append(t(e("", { label: r[i].text }), r[i].children)) : n.append(e("